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.json;
018
019import java.io.BufferedReader;
020import java.io.Closeable;
021import java.io.File;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.InputStreamReader;
025import java.io.Reader;
026import java.io.StringReader;
027import java.lang.reflect.Field;
028
029import org.assertj.core.api.Assertions;
030
031import org.springframework.beans.factory.ObjectFactory;
032import org.springframework.core.ResolvableType;
033import org.springframework.core.io.ByteArrayResource;
034import org.springframework.core.io.ClassPathResource;
035import org.springframework.core.io.FileSystemResource;
036import org.springframework.core.io.InputStreamResource;
037import org.springframework.core.io.Resource;
038import org.springframework.util.Assert;
039import org.springframework.util.ReflectionUtils;
040
041/**
042 * Base class for AssertJ based JSON marshal testers. Exposes specific Asserts following a
043 * {@code read}, {@code write} or {@code parse} of JSON content. Typically used in
044 * combination with an AssertJ {@link Assertions#assertThat(Object) assertThat} call. For
045 * example: <pre class="code">
046 * public class ExampleObjectJsonTests {
047 *
048 *     private AbstractJsonTester&lt;ExampleObject&gt; json = //...
049 *
050 *     &#064;Test
051 *     public void testWriteJson() {
052 *         ExampleObject object = //...
053 *         assertThat(json.write(object)).isEqualToJson("expected.json");
054 *         assertThat(json.read("expected.json")).isEqualTo(object);
055 *     }
056 *
057 * }
058 * </pre> For a complete list of supported assertions see {@link JsonContentAssert} and
059 * {@link ObjectContentAssert}.
060 * <p>
061 * To use this library JSONAssert must be on the test classpath.
062 *
063 * @param <T> the type under test
064 * @author Phillip Webb
065 * @since 1.4.0
066 * @see JsonContentAssert
067 * @see ObjectContentAssert
068 */
069public abstract class AbstractJsonMarshalTester<T> {
070
071        private Class<?> resourceLoadClass;
072
073        private ResolvableType type;
074
075        /**
076         * Create a new uninitialized {@link AbstractJsonMarshalTester} instance.
077         */
078        protected AbstractJsonMarshalTester() {
079        }
080
081        /**
082         * Create a new {@link AbstractJsonMarshalTester} instance.
083         * @param resourceLoadClass the source class used when loading relative classpath
084         * resources
085         * @param type the type under test
086         */
087        public AbstractJsonMarshalTester(Class<?> resourceLoadClass, ResolvableType type) {
088                Assert.notNull(resourceLoadClass, "ResourceLoadClass must not be null");
089                Assert.notNull(type, "Type must not be null");
090                initialize(resourceLoadClass, type);
091        }
092
093        /**
094         * Initialize the marshal tester for use.
095         * @param resourceLoadClass the source class used when loading relative classpath
096         * resources
097         * @param type the type under test
098         */
099        protected final void initialize(Class<?> resourceLoadClass, ResolvableType type) {
100                if (this.resourceLoadClass == null && this.type == null) {
101                        this.resourceLoadClass = resourceLoadClass;
102                        this.type = type;
103                }
104        }
105
106        /**
107         * Return the type under test.
108         * @return the type under test
109         */
110        protected final ResolvableType getType() {
111                return this.type;
112        }
113
114        /**
115         * Return class used to load relative resources.
116         * @return the resource load class
117         */
118        protected final Class<?> getResourceLoadClass() {
119                return this.resourceLoadClass;
120        }
121
122        /**
123         * Return {@link JsonContent} from writing the specific value.
124         * @param value the value to write
125         * @return the {@link JsonContent}
126         * @throws IOException on write error
127         */
128        public JsonContent<T> write(T value) throws IOException {
129                verify();
130                Assert.notNull(value, "Value must not be null");
131                String json = writeObject(value, this.type);
132                return new JsonContent<>(this.resourceLoadClass, this.type, json);
133        }
134
135        /**
136         * Return the object created from parsing the specific JSON bytes.
137         * @param jsonBytes the source JSON bytes
138         * @return the resulting object
139         * @throws IOException on parse error
140         */
141        public T parseObject(byte[] jsonBytes) throws IOException {
142                verify();
143                return parse(jsonBytes).getObject();
144        }
145
146        /**
147         * Return {@link ObjectContent} from parsing the specific JSON bytes.
148         * @param jsonBytes the source JSON bytes
149         * @return the {@link ObjectContent}
150         * @throws IOException on parse error
151         */
152        public ObjectContent<T> parse(byte[] jsonBytes) throws IOException {
153                verify();
154                Assert.notNull(jsonBytes, "JsonBytes must not be null");
155                return read(new ByteArrayResource(jsonBytes));
156        }
157
158        /**
159         * Return the object created from parsing the specific JSON String.
160         * @param jsonString the source JSON string
161         * @return the resulting object
162         * @throws IOException on parse error
163         */
164        public T parseObject(String jsonString) throws IOException {
165                verify();
166                return parse(jsonString).getObject();
167        }
168
169        /**
170         * Return {@link ObjectContent} from parsing the specific JSON String.
171         * @param jsonString the source JSON string
172         * @return the {@link ObjectContent}
173         * @throws IOException on parse error
174         */
175        public ObjectContent<T> parse(String jsonString) throws IOException {
176                verify();
177                Assert.notNull(jsonString, "JsonString must not be null");
178                return read(new StringReader(jsonString));
179        }
180
181        /**
182         * Return the object created from reading from the specified classpath resource.
183         * @param resourcePath the source resource path. May be a full path or a path relative
184         * to the {@code resourceLoadClass} passed to the constructor
185         * @return the resulting object
186         * @throws IOException on read error
187         */
188        public T readObject(String resourcePath) throws IOException {
189                verify();
190                return read(resourcePath).getObject();
191        }
192
193        /**
194         * Return {@link ObjectContent} from reading from the specified classpath resource.
195         * @param resourcePath the source resource path. May be a full path or a path relative
196         * to the {@code resourceLoadClass} passed to the constructor
197         * @return the {@link ObjectContent}
198         * @throws IOException on read error
199         */
200        public ObjectContent<T> read(String resourcePath) throws IOException {
201                verify();
202                Assert.notNull(resourcePath, "ResourcePath must not be null");
203                return read(new ClassPathResource(resourcePath, this.resourceLoadClass));
204        }
205
206        /**
207         * Return the object created from reading from the specified file.
208         * @param file the source file
209         * @return the resulting object
210         * @throws IOException on read error
211         */
212        public T readObject(File file) throws IOException {
213                verify();
214                return read(file).getObject();
215        }
216
217        /**
218         * Return {@link ObjectContent} from reading from the specified file.
219         * @param file the source file
220         * @return the {@link ObjectContent}
221         * @throws IOException on read error
222         */
223        public ObjectContent<T> read(File file) throws IOException {
224                verify();
225                Assert.notNull(file, "File must not be null");
226                return read(new FileSystemResource(file));
227        }
228
229        /**
230         * Return the object created from reading from the specified input stream.
231         * @param inputStream the source input stream
232         * @return the resulting object
233         * @throws IOException on read error
234         */
235        public T readObject(InputStream inputStream) throws IOException {
236                verify();
237                return read(inputStream).getObject();
238        }
239
240        /**
241         * Return {@link ObjectContent} from reading from the specified input stream.
242         * @param inputStream the source input stream
243         * @return the {@link ObjectContent}
244         * @throws IOException on read error
245         */
246        public ObjectContent<T> read(InputStream inputStream) throws IOException {
247                verify();
248                Assert.notNull(inputStream, "InputStream must not be null");
249                return read(new InputStreamResource(inputStream));
250        }
251
252        /**
253         * Return the object created from reading from the specified resource.
254         * @param resource the source resource
255         * @return the resulting object
256         * @throws IOException on read error
257         */
258        public T readObject(Resource resource) throws IOException {
259                verify();
260                return read(resource).getObject();
261        }
262
263        /**
264         * Return {@link ObjectContent} from reading from the specified resource.
265         * @param resource the source resource
266         * @return the {@link ObjectContent}
267         * @throws IOException on read error
268         */
269        public ObjectContent<T> read(Resource resource) throws IOException {
270                verify();
271                Assert.notNull(resource, "Resource must not be null");
272                InputStream inputStream = resource.getInputStream();
273                T object = readObject(inputStream, this.type);
274                closeQuietly(inputStream);
275                return new ObjectContent<>(this.type, object);
276        }
277
278        /**
279         * Return the object created from reading from the specified reader.
280         * @param reader the source reader
281         * @return the resulting object
282         * @throws IOException on read error
283         */
284        public T readObject(Reader reader) throws IOException {
285                verify();
286                return read(reader).getObject();
287        }
288
289        /**
290         * Return {@link ObjectContent} from reading from the specified reader.
291         * @param reader the source reader
292         * @return the {@link ObjectContent}
293         * @throws IOException on read error
294         */
295        public ObjectContent<T> read(Reader reader) throws IOException {
296                verify();
297                Assert.notNull(reader, "Reader must not be null");
298                T object = readObject(reader, this.type);
299                closeQuietly(reader);
300                return new ObjectContent<>(this.type, object);
301        }
302
303        private void closeQuietly(Closeable closeable) {
304                try {
305                        closeable.close();
306                }
307                catch (IOException ex) {
308                }
309        }
310
311        private void verify() {
312                Assert.state(this.resourceLoadClass != null,
313                                "Uninitialized JsonMarshalTester (ResourceLoadClass is null)");
314                Assert.state(this.type != null, "Uninitialized JsonMarshalTester (Type is null)");
315        }
316
317        /**
318         * Write the specified object to a JSON string.
319         * @param value the source value (never {@code null})
320         * @param type the resulting type (never {@code null})
321         * @return the JSON string
322         * @throws IOException on write error
323         */
324        protected abstract String writeObject(T value, ResolvableType type)
325                        throws IOException;
326
327        /**
328         * Read from the specified input stream to create an object of the specified type. The
329         * default implementation delegates to {@link #readObject(Reader, ResolvableType)}.
330         * @param inputStream the source input stream (never {@code null})
331         * @param type the resulting type (never {@code null})
332         * @return the resulting object
333         * @throws IOException on read error
334         */
335        protected T readObject(InputStream inputStream, ResolvableType type)
336                        throws IOException {
337                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
338                return readObject(reader, type);
339        }
340
341        /**
342         * Read from the specified reader to create an object of the specified type.
343         * @param reader the source reader (never {@code null})
344         * @param type the resulting type (never {@code null})
345         * @return the resulting object
346         * @throws IOException on read error
347         */
348        protected abstract T readObject(Reader reader, ResolvableType type)
349                        throws IOException;
350
351        /**
352         * Utility class used to support field initialization. Used by subclasses to support
353         * {@code initFields}.
354         *
355         * @param <M> the marshaller type
356         */
357        protected abstract static class FieldInitializer<M> {
358
359                private final Class<?> testerClass;
360
361                @SuppressWarnings("rawtypes")
362                protected FieldInitializer(
363                                Class<? extends AbstractJsonMarshalTester> testerClass) {
364                        Assert.notNull(testerClass, "TesterClass must not be null");
365                        this.testerClass = testerClass;
366                }
367
368                public void initFields(Object testInstance, M marshaller) {
369                        Assert.notNull(testInstance, "TestInstance must not be null");
370                        Assert.notNull(marshaller, "Marshaller must not be null");
371                        initFields(testInstance, () -> marshaller);
372                }
373
374                public void initFields(Object testInstance, final ObjectFactory<M> marshaller) {
375                        Assert.notNull(testInstance, "TestInstance must not be null");
376                        Assert.notNull(marshaller, "Marshaller must not be null");
377                        ReflectionUtils.doWithFields(testInstance.getClass(),
378                                        (field) -> doWithField(field, testInstance, marshaller));
379                }
380
381                protected void doWithField(Field field, Object test,
382                                ObjectFactory<M> marshaller) {
383                        if (this.testerClass.isAssignableFrom(field.getType())) {
384                                ReflectionUtils.makeAccessible(field);
385                                Object existingValue = ReflectionUtils.getField(field, test);
386                                if (existingValue == null) {
387                                        setupField(field, test, marshaller);
388                                }
389                        }
390                }
391
392                private void setupField(Field field, Object test, ObjectFactory<M> marshaller) {
393                        ResolvableType type = ResolvableType.forField(field).getGeneric();
394                        ReflectionUtils.setField(field, test,
395                                        createTester(test.getClass(), type, marshaller.getObject()));
396                }
397
398                protected abstract AbstractJsonMarshalTester<Object> createTester(
399                                Class<?> resourceLoadClass, ResolvableType type, M marshaller);
400
401        }
402
403}