1
16 package org.springframework.data.web;
17
18 import lombok.RequiredArgsConstructor;
19 import net.minidev.json.JSONArray;
20 import net.minidev.json.JSONObject;
21
22 import java.io.InputStream;
23 import java.lang.reflect.Method;
24 import java.lang.reflect.Type;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.List;
29 import java.util.Map;
30
31 import org.aopalliance.intercept.MethodInterceptor;
32 import org.aopalliance.intercept.MethodInvocation;
33 import org.springframework.core.ResolvableType;
34 import org.springframework.core.annotation.AnnotationUtils;
35 import org.springframework.data.projection.Accessor;
36 import org.springframework.data.projection.MethodInterceptorFactory;
37 import org.springframework.data.util.ClassTypeInformation;
38 import org.springframework.data.util.TypeInformation;
39 import org.springframework.lang.Nullable;
40 import org.springframework.util.Assert;
41
42 import com.fasterxml.jackson.databind.ObjectMapper;
43 import com.jayway.jsonpath.Configuration;
44 import com.jayway.jsonpath.DocumentContext;
45 import com.jayway.jsonpath.JsonPath;
46 import com.jayway.jsonpath.Option;
47 import com.jayway.jsonpath.ParseContext;
48 import com.jayway.jsonpath.PathNotFoundException;
49 import com.jayway.jsonpath.TypeRef;
50 import com.jayway.jsonpath.spi.mapper.MappingProvider;
51
52
59 public class JsonProjectingMethodInterceptorFactory implements MethodInterceptorFactory {
60
61 private final ParseContext context;
62
63
68 public JsonProjectingMethodInterceptorFactory(MappingProvider mappingProvider) {
69
70 Assert.notNull(mappingProvider, "MappingProvider must not be null!");
71
72 Configuration build = Configuration.builder()
73 .options(Option.ALWAYS_RETURN_LIST)
74 .mappingProvider(mappingProvider)
75 .build();
76
77 this.context = JsonPath.using(build);
78 }
79
80
84 @Override
85 public MethodInterceptor createMethodInterceptor(Object source, Class<?> targetType) {
86
87 DocumentContext context = InputStream.class.isInstance(source) ? this.context.parse((InputStream) source)
88 : this.context.parse(source);
89
90 return new InputMessageProjecting(context);
91 }
92
93
97 @Override
98 public boolean supports(Object source, Class<?> targetType) {
99
100 if (InputStream.class.isInstance(source) || JSONObject.class.isInstance(source)
101 || JSONArray.class.isInstance(source)) {
102 return true;
103 }
104
105 return Map.class.isInstance(source) && hasJsonPathAnnotation(targetType);
106 }
107
108
114 private static boolean hasJsonPathAnnotation(Class<?> type) {
115
116 for (Method method : type.getMethods()) {
117 if (AnnotationUtils.findAnnotation(method, org.springframework.data.web.JsonPath.class) != null) {
118 return true;
119 }
120 }
121
122 return false;
123 }
124
125 @RequiredArgsConstructor
126 private static class InputMessageProjecting implements MethodInterceptor {
127
128 private final DocumentContext context;
129
130
134 @Nullable
135 @Override
136 public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
137
138 Method method = invocation.getMethod();
139 TypeInformation<Object> returnType = ClassTypeInformation.fromReturnTypeOf(method);
140 ResolvableType type = ResolvableType.forMethodReturnType(method);
141 boolean isCollectionResult = Collection.class.isAssignableFrom(type.getRawClass());
142 type = isCollectionResult ? type : ResolvableType.forClassWithGenerics(List.class, type);
143
144 Iterable<String> jsonPaths = getJsonPaths(method);
145
146 for (String jsonPath : jsonPaths) {
147
148 try {
149
150 if (returnType.getRequiredActualType().getType().isInterface()) {
151
152 List<?> result = context.read(jsonPath);
153 return result.isEmpty() ? null : result.get(0);
154 }
155
156 type = isCollectionResult && JsonPath.isPathDefinite(jsonPath)
157 ? ResolvableType.forClassWithGenerics(List.class, type)
158 : type;
159
160 List<?> result = (List<?>) context.read(jsonPath, new ResolvableTypeRef(type));
161
162 if (isCollectionResult && JsonPath.isPathDefinite(jsonPath)) {
163 result = (List<?>) result.get(0);
164 }
165
166 return isCollectionResult ? result : result.isEmpty() ? null : result.get(0);
167
168 } catch (PathNotFoundException o_O) {
169
170 }
171 }
172
173 return null;
174 }
175
176
182 private static Collection<String> getJsonPaths(Method method) {
183
184 org.springframework.data.web.JsonPath annotation = AnnotationUtils.findAnnotation(method,
185 org.springframework.data.web.JsonPath.class);
186
187 if (annotation != null) {
188 return Arrays.asList(annotation.value());
189 }
190
191 return Collections.singletonList("$.".concat(new Accessor(method).getPropertyName()));
192 }
193
194 @RequiredArgsConstructor
195 private static class ResolvableTypeRef extends TypeRef<Object> {
196
197 private final ResolvableType type;
198
199
203 @Override
204 public Type getType() {
205 return type.getType();
206 }
207 }
208 }
209 }
210