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}