001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.test.autoconfigure.json; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.Field; 021 022import javax.json.bind.Jsonb; 023 024import com.fasterxml.jackson.databind.ObjectMapper; 025import com.google.gson.Gson; 026 027import org.springframework.beans.BeanUtils; 028import org.springframework.beans.BeansException; 029import org.springframework.beans.factory.FactoryBean; 030import org.springframework.beans.factory.config.BeanPostProcessor; 031import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; 032import org.springframework.boot.autoconfigure.AutoConfigureAfter; 033import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 034import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 035import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 036import org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration; 037import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration; 038import org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration; 039import org.springframework.boot.test.json.AbstractJsonMarshalTester; 040import org.springframework.boot.test.json.BasicJsonTester; 041import org.springframework.boot.test.json.GsonTester; 042import org.springframework.boot.test.json.JacksonTester; 043import org.springframework.boot.test.json.JsonbTester; 044import org.springframework.context.annotation.Bean; 045import org.springframework.context.annotation.Configuration; 046import org.springframework.context.annotation.Scope; 047import org.springframework.core.ResolvableType; 048import org.springframework.test.util.ReflectionTestUtils; 049import org.springframework.util.ReflectionUtils; 050 051/** 052 * Auto-configuration for Json testers. 053 * 054 * @author Phillip Webb 055 * @author EddĂș MelĂ©ndez 056 * @see AutoConfigureJsonTesters 057 * @since 1.4.0 058 */ 059@Configuration 060@ConditionalOnClass(name = "org.assertj.core.api.Assert") 061@ConditionalOnProperty("spring.test.jsontesters.enabled") 062@AutoConfigureAfter({ JacksonAutoConfiguration.class, GsonAutoConfiguration.class, 063 JsonbAutoConfiguration.class }) 064public class JsonTestersAutoConfiguration { 065 066 @Bean 067 public static JsonMarshalTestersBeanPostProcessor jsonMarshalTestersBeanPostProcessor() { 068 return new JsonMarshalTestersBeanPostProcessor(); 069 } 070 071 @Bean 072 @Scope("prototype") 073 public FactoryBean<BasicJsonTester> basicJsonTesterFactoryBean() { 074 return new JsonTesterFactoryBean<BasicJsonTester, Void>(BasicJsonTester.class, 075 null); 076 } 077 078 @Configuration 079 @ConditionalOnClass(ObjectMapper.class) 080 static class JacksonJsonTestersConfiguration { 081 082 @Bean 083 @Scope("prototype") 084 @ConditionalOnBean(ObjectMapper.class) 085 public FactoryBean<JacksonTester<?>> jacksonTesterFactoryBean( 086 ObjectMapper mapper) { 087 return new JsonTesterFactoryBean<>(JacksonTester.class, mapper); 088 } 089 090 } 091 092 @Configuration 093 @ConditionalOnClass(Gson.class) 094 static class GsonJsonTestersConfiguration { 095 096 @Bean 097 @Scope("prototype") 098 @ConditionalOnBean(Gson.class) 099 public FactoryBean<GsonTester<?>> gsonTesterFactoryBean(Gson gson) { 100 return new JsonTesterFactoryBean<>(GsonTester.class, gson); 101 } 102 103 } 104 105 @Configuration 106 @ConditionalOnClass(Jsonb.class) 107 static class JsonbJsonTesterConfiguration { 108 109 @Bean 110 @Scope("prototype") 111 @ConditionalOnBean(Jsonb.class) 112 public FactoryBean<JsonbTester<?>> jsonbTesterFactoryBean(Jsonb jsonb) { 113 return new JsonTesterFactoryBean<>(JsonbTester.class, jsonb); 114 } 115 116 } 117 118 /** 119 * {@link FactoryBean} used to create JSON Tester instances. 120 */ 121 private static class JsonTesterFactoryBean<T, M> implements FactoryBean<T> { 122 123 private final Class<?> objectType; 124 125 private final M marshaller; 126 127 JsonTesterFactoryBean(Class<?> objectType, M marshaller) { 128 this.objectType = objectType; 129 this.marshaller = marshaller; 130 131 } 132 133 @Override 134 public boolean isSingleton() { 135 return false; 136 } 137 138 @Override 139 @SuppressWarnings("unchecked") 140 public T getObject() throws Exception { 141 if (this.marshaller == null) { 142 Constructor<?> constructor = this.objectType.getDeclaredConstructor(); 143 ReflectionUtils.makeAccessible(constructor); 144 return (T) BeanUtils.instantiateClass(constructor); 145 } 146 Constructor<?>[] constructors = this.objectType.getDeclaredConstructors(); 147 for (Constructor<?> constructor : constructors) { 148 if (constructor.getParameterCount() == 1 149 && constructor.getParameterTypes()[0] 150 .isInstance(this.marshaller)) { 151 ReflectionUtils.makeAccessible(constructor); 152 return (T) BeanUtils.instantiateClass(constructor, this.marshaller); 153 } 154 } 155 throw new IllegalStateException( 156 this.objectType + " does not have a usable constructor"); 157 } 158 159 @Override 160 public Class<?> getObjectType() { 161 return this.objectType; 162 } 163 164 } 165 166 /** 167 * {@link BeanPostProcessor} used to initialize JSON testers. 168 */ 169 private static class JsonMarshalTestersBeanPostProcessor 170 extends InstantiationAwareBeanPostProcessorAdapter { 171 172 @Override 173 public Object postProcessAfterInitialization(Object bean, String beanName) 174 throws BeansException { 175 ReflectionUtils.doWithFields(bean.getClass(), 176 (field) -> processField(bean, field)); 177 return bean; 178 } 179 180 private void processField(Object bean, Field field) { 181 if (AbstractJsonMarshalTester.class.isAssignableFrom(field.getType())) { 182 initializeTester(bean, field, bean.getClass(), 183 ResolvableType.forField(field).getGeneric()); 184 } 185 else if (BasicJsonTester.class.isAssignableFrom(field.getType())) { 186 initializeTester(bean, field, bean.getClass()); 187 } 188 } 189 190 private void initializeTester(Object bean, Field field, Object... args) { 191 ReflectionUtils.makeAccessible(field); 192 Object tester = ReflectionUtils.getField(field, bean); 193 if (tester != null) { 194 ReflectionTestUtils.invokeMethod(tester, "initialize", args); 195 } 196 } 197 198 } 199 200}