Skip to content

Commit 2a18166

Browse files
committed
Create ProjectingJackson2JsonDecoder.java
1 parent 3a6b04d commit 2a18166

File tree

1 file changed

+117
-0
lines changed

1 file changed

+117
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package org.springframework.data.web;
2+
3+
import com.fasterxml.jackson.databind.JavaType;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
6+
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
7+
import org.springframework.beans.BeansException;
8+
import org.springframework.beans.factory.BeanClassLoaderAware;
9+
import org.springframework.beans.factory.BeanFactory;
10+
import org.springframework.beans.factory.BeanFactoryAware;
11+
import org.springframework.core.ResolvableType;
12+
import org.springframework.core.annotation.AnnotationUtils;
13+
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
14+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
15+
import org.springframework.lang.Nullable;
16+
import org.springframework.util.Assert;
17+
import org.springframework.util.ConcurrentReferenceHashMap;
18+
import org.springframework.util.MimeType;
19+
20+
import java.util.Map;
21+
import java.util.concurrent.atomic.AtomicReference;
22+
23+
/**
24+
* {@link org.springframework.http.codec.HttpMessageDecoder} implementation to enable projected JSON binding to interfaces annotated with
25+
* {@link ProjectedPayload}.
26+
*
27+
* @author Matías Hermosilla
28+
* @since 3.0
29+
*/
30+
public class ProjectingJackson2JsonDecoder extends Jackson2JsonDecoder
31+
implements BeanClassLoaderAware, BeanFactoryAware {
32+
33+
private final SpelAwareProxyProjectionFactory projectionFactory;
34+
private final Map<Class<?>, Boolean> supportedTypesCache = new ConcurrentReferenceHashMap<>();
35+
36+
/**
37+
* Creates a new {@link ProjectingJackson2JsonDecoder} using a default {@link ObjectMapper}.
38+
*/
39+
public ProjectingJackson2JsonDecoder() {
40+
this.projectionFactory = initProjectionFactory(getObjectMapper());
41+
}
42+
43+
/**
44+
* Creates a new {@link ProjectingJackson2JsonDecoder} for the given {@link ObjectMapper}.
45+
*
46+
* @param mapper must not be {@literal null}.
47+
*/
48+
public ProjectingJackson2JsonDecoder(ObjectMapper mapper) {
49+
50+
super(mapper);
51+
52+
this.projectionFactory = initProjectionFactory(mapper);
53+
}
54+
55+
/**
56+
* Creates a new {@link SpelAwareProxyProjectionFactory} with the {@link JsonProjectingMethodInterceptorFactory}
57+
* registered for the given {@link ObjectMapper}.
58+
*
59+
* @param mapper must not be {@literal null}.
60+
* @return
61+
*/
62+
private static SpelAwareProxyProjectionFactory initProjectionFactory(ObjectMapper mapper) {
63+
64+
Assert.notNull(mapper, "ObjectMapper must not be null");
65+
66+
SpelAwareProxyProjectionFactory projectionFactory = new SpelAwareProxyProjectionFactory();
67+
projectionFactory.registerMethodInvokerFactory(
68+
new JsonProjectingMethodInterceptorFactory(new JacksonJsonProvider(mapper),
69+
new JacksonMappingProvider(mapper)));
70+
71+
return projectionFactory;
72+
}
73+
74+
@Override
75+
public void setBeanClassLoader(ClassLoader classLoader) {
76+
projectionFactory.setBeanClassLoader(classLoader);
77+
}
78+
79+
@Override
80+
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
81+
projectionFactory.setBeanFactory(beanFactory);
82+
}
83+
84+
@Override
85+
public boolean canDecode(ResolvableType elementType, @Nullable MimeType mimeType) {
86+
ObjectMapper mapper = selectObjectMapper(elementType, mimeType);
87+
if (mapper == null) {
88+
return false;
89+
}
90+
JavaType javaType = mapper.constructType(elementType.getType());
91+
// Skip String: CharSequenceDecoder + "*/*" comes after
92+
if (CharSequence.class.isAssignableFrom(elementType.toClass()) || !supportsMimeType(mimeType)) {
93+
return false;
94+
}
95+
if (!logger.isDebugEnabled()) {
96+
return mapper.canDeserialize(javaType);
97+
} else {
98+
AtomicReference<Throwable> causeRef = new AtomicReference<>();
99+
if (mapper.canDeserialize(javaType, causeRef)) {
100+
Class<?> rawType = javaType.getRawClass();
101+
Boolean result = supportedTypesCache.get(rawType);
102+
103+
if (result != null) {
104+
return result;
105+
}
106+
107+
result = rawType.isInterface() && AnnotationUtils.findAnnotation(rawType, ProjectedPayload.class) != null;
108+
supportedTypesCache.put(rawType, result);
109+
110+
return result;
111+
}
112+
logWarningIfNecessary(javaType, causeRef.get());
113+
return false;
114+
}
115+
}
116+
117+
}

0 commit comments

Comments
 (0)