1 |
|
|
2 |
|
|
3 |
|
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
11 |
|
|
12 |
|
|
13 |
|
|
14 |
|
|
15 |
|
|
16 |
|
|
17 |
|
|
18 |
|
|
19 |
|
|
20 |
|
|
21 |
|
|
22 |
|
|
23 |
|
|
24 |
|
|
25 |
|
|
26 |
|
|
27 |
|
|
28 |
|
|
29 |
|
|
30 |
|
|
31 |
|
|
32 |
|
|
33 |
|
|
34 |
|
package info.magnolia.rest.delivery.jcr; |
35 |
|
|
36 |
|
import info.magnolia.jcr.util.PropertyUtil; |
37 |
|
import info.magnolia.rest.reference.ReferenceContext; |
38 |
|
import info.magnolia.rest.reference.ReferenceResolver; |
39 |
|
|
40 |
|
import java.io.IOException; |
41 |
|
import java.io.OutputStream; |
42 |
|
import java.lang.annotation.Annotation; |
43 |
|
import java.lang.reflect.Type; |
44 |
|
import java.nio.charset.Charset; |
45 |
|
import java.util.HashSet; |
46 |
|
import java.util.Map; |
47 |
|
import java.util.Optional; |
48 |
|
import java.util.Set; |
49 |
|
|
50 |
|
import javax.jcr.Node; |
51 |
|
import javax.jcr.NodeIterator; |
52 |
|
import javax.jcr.Property; |
53 |
|
import javax.jcr.PropertyIterator; |
54 |
|
import javax.jcr.PropertyType; |
55 |
|
import javax.jcr.RepositoryException; |
56 |
|
import javax.jcr.Value; |
57 |
|
import javax.json.Json; |
58 |
|
import javax.json.JsonArrayBuilder; |
59 |
|
import javax.json.stream.JsonGenerator; |
60 |
|
import javax.ws.rs.Produces; |
61 |
|
import javax.ws.rs.WebApplicationException; |
62 |
|
import javax.ws.rs.core.Context; |
63 |
|
import javax.ws.rs.core.MediaType; |
64 |
|
import javax.ws.rs.core.MultivaluedHashMap; |
65 |
|
import javax.ws.rs.core.MultivaluedMap; |
66 |
|
import javax.ws.rs.ext.ContextResolver; |
67 |
|
import javax.ws.rs.ext.MessageBodyWriter; |
68 |
|
import javax.ws.rs.ext.Provider; |
69 |
|
import javax.ws.rs.ext.Providers; |
70 |
|
|
71 |
|
import org.apache.commons.codec.binary.Base64; |
72 |
|
import org.apache.commons.io.IOUtils; |
73 |
|
import org.apache.jackrabbit.JcrConstants; |
74 |
|
import org.slf4j.Logger; |
75 |
|
import org.slf4j.LoggerFactory; |
76 |
|
|
77 |
|
import com.google.common.collect.Sets; |
78 |
|
|
79 |
|
|
80 |
|
|
81 |
|
|
82 |
|
@Provider |
83 |
|
@Produces({ MediaType.APPLICATION_JSON }) |
|
|
| 82.5% |
Uncovered Elements: 31 (177) |
Complexity: 49 |
Complexity Density: 0.44 |
|
84 |
|
public class NodeWriter implements MessageBodyWriter<Node> { |
85 |
|
|
86 |
|
private static final Logger log = LoggerFactory.getLogger(NodeWriter.class); |
87 |
|
|
88 |
|
private static final String SAME_NAME_SIBLING_PREFIX = "_"; |
89 |
|
private static final String META_PROPERTY_PREFIX = "@"; |
90 |
|
private static final String PATH_PROPERTY = META_PROPERTY_PREFIX + "path"; |
91 |
|
private static final String NAME_PROPERTY = META_PROPERTY_PREFIX + "name"; |
92 |
|
private static final String CHILD_PROPERTY = META_PROPERTY_PREFIX + "nodes"; |
93 |
|
private static final String UUID_PROPERTY = META_PROPERTY_PREFIX + "id"; |
94 |
|
private static final String NODE_TYPE_PROPERTY = META_PROPERTY_PREFIX + "nodeType"; |
95 |
|
private static final Set<String> additionalSpecialProps = Sets.newHashSet(JcrConstants.JCR_PRIMARYTYPE, JcrConstants.JCR_UUID); |
96 |
|
|
97 |
|
@Context |
98 |
|
private Providers providers; |
99 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
100 |
1 |
@Override... |
101 |
|
public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
102 |
1 |
return Node.class.isAssignableFrom(type); |
103 |
|
} |
104 |
|
|
|
|
| 0% |
Uncovered Elements: 1 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
105 |
0 |
@Override... |
106 |
|
public long getSize(Node node, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { |
107 |
|
|
108 |
0 |
return -1; |
109 |
|
} |
110 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (10) |
Complexity: 3 |
Complexity Density: 0.5 |
|
111 |
30 |
@Override... |
112 |
|
public void writeTo(Node node, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, |
113 |
|
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { |
114 |
30 |
JsonGenerator jsonGenerator = entityStream instanceof OutputStreamWrapper ? |
115 |
|
((OutputStreamWrapper) entityStream).getJsonGenerator() : |
116 |
|
Json.createGenerator(entityStream); |
117 |
|
|
118 |
30 |
MultivaluedMap<String, String> resolvedProperties = new MultivaluedHashMap<>(); |
119 |
30 |
writeNode(jsonGenerator, node, null, true, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
120 |
|
|
121 |
30 |
if (!OutputStreamWrapper.isWrapped(entityStream)) { |
122 |
18 |
jsonGenerator.flush(); |
123 |
18 |
jsonGenerator.close(); |
124 |
|
} |
125 |
|
} |
126 |
|
|
127 |
|
|
|
|
| 96.3% |
Uncovered Elements: 1 (27) |
Complexity: 6 |
Complexity Density: 0.32 |
|
128 |
116 |
void writeNode(JsonGenerator jsonGenerator, Node node, String objectKey, boolean isResolveReference, MultivaluedMap<String, String> resolvedProperties,... |
129 |
|
Annotation[] annotations, OutputStream entityStream, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders) throws IOException { |
130 |
116 |
try { |
131 |
116 |
if (objectKey != null) { |
132 |
84 |
jsonGenerator.writeStartObject(objectKey); |
133 |
|
} else { |
134 |
32 |
jsonGenerator.writeStartObject(); |
135 |
|
} |
136 |
|
|
137 |
116 |
writeSpecialProperties(jsonGenerator, node); |
138 |
|
|
139 |
116 |
PropertyIterator properties = node.getProperties(); |
140 |
|
|
141 |
1052 |
while (properties.hasNext()) { |
142 |
936 |
Property property = properties.nextProperty(); |
143 |
936 |
if (isAllowedJcrProperty(property.getName())) { |
144 |
717 |
writeProperty(jsonGenerator, property, node.hasNode(property.getName()), isResolveReference, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
145 |
|
} |
146 |
|
} |
147 |
|
|
148 |
116 |
NodeIterator children = node.getNodes(); |
149 |
|
|
150 |
116 |
JsonArrayBuilder childNodeNames = Json.createArrayBuilder(); |
151 |
198 |
while (children.hasNext()) { |
152 |
82 |
Node child = children.nextNode(); |
153 |
82 |
writeNode(jsonGenerator, child, child.getName(), true, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
154 |
82 |
childNodeNames.add(child.getName()); |
155 |
|
} |
156 |
116 |
jsonGenerator.write(CHILD_PROPERTY, childNodeNames.build()); |
157 |
|
|
158 |
116 |
jsonGenerator.writeEnd(); |
159 |
|
} catch (RepositoryException re) { |
160 |
0 |
log.error(re.getMessage()); |
161 |
|
} |
162 |
|
} |
163 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (1) |
Complexity: 1 |
Complexity Density: 1 |
|
164 |
936 |
protected boolean isAllowedJcrProperty(String propertyName) {... |
165 |
936 |
return !additionalSpecialProps.contains(propertyName); |
166 |
|
} |
167 |
|
|
|
|
| 80.5% |
Uncovered Elements: 8 (41) |
Complexity: 11 |
Complexity Density: 0.44 |
|
168 |
717 |
private void writeProperty(JsonGenerator jsonGenerator, Property property, boolean shouldAddPrefix, boolean isResolveReference, MultivaluedMap<String, String> resolvedProperties,... |
169 |
|
Annotation[] annotations, OutputStream entityStream, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders) throws RepositoryException, IOException { |
170 |
717 |
String propertyName = (shouldAddPrefix ? SAME_NAME_SIBLING_PREFIX : "") + property.getName(); |
171 |
|
|
172 |
717 |
Optional<ReferenceResolver> referenceResolver = getReferenceResolver(property); |
173 |
|
|
174 |
717 |
if (!property.isMultiple()) { |
175 |
710 |
String valueAsString = getValueString(property.getValue()); |
176 |
|
|
177 |
710 |
if (isResolveReference && referenceResolver.isPresent()) { |
178 |
5 |
Optional<Object> reference = (Optional<Object>) referenceResolver.get().resolve(valueAsString); |
179 |
5 |
if (canWriteReference(property.getPath(), valueAsString, reference, resolvedProperties)) { |
180 |
5 |
writeResolvedReference(jsonGenerator, reference.get(), propertyName, true, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
181 |
|
} else { |
182 |
0 |
jsonGenerator.write(propertyName, valueAsString); |
183 |
|
} |
184 |
|
} else { |
185 |
705 |
jsonGenerator.write(propertyName, valueAsString); |
186 |
|
} |
187 |
|
} else { |
188 |
7 |
jsonGenerator.writeStartArray(propertyName); |
189 |
7 |
Value[] values = property.getValues(); |
190 |
|
|
191 |
7 |
Set<String> resolvedValues = new HashSet<>(); |
192 |
24 |
for (int i = 0; i < values.length; i++) { |
193 |
17 |
String valueAsString = getValueString(values[i]); |
194 |
|
|
195 |
17 |
if (isResolveReference && referenceResolver.isPresent()) { |
196 |
8 |
Optional<Object> reference = (Optional<Object>) referenceResolver.get().resolve(valueAsString); |
197 |
8 |
if (canWriteReference(property.getPath(), valueAsString, reference, resolvedProperties)) { |
198 |
8 |
writeResolvedReference(jsonGenerator, reference.get(), null, i > 0, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
199 |
8 |
resolvedValues.add(valueAsString); |
200 |
0 |
} else if (resolvedValues.contains(valueAsString)) { |
201 |
0 |
writeResolvedReference(jsonGenerator, reference.get(), null, i > 0, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
202 |
|
} else { |
203 |
0 |
jsonGenerator.write(valueAsString); |
204 |
|
} |
205 |
|
} else { |
206 |
9 |
jsonGenerator.write(valueAsString); |
207 |
|
} |
208 |
|
} |
209 |
7 |
jsonGenerator.writeEnd(); |
210 |
|
} |
211 |
|
} |
212 |
|
|
|
|
| 71.4% |
Uncovered Elements: 4 (14) |
Complexity: 5 |
Complexity Density: 0.62 |
|
213 |
13 |
private boolean canWriteReference(String propertyPath, String referenceValue, Optional<Object> resolvedReference, MultivaluedMap<String, String> resolvedProperties) {... |
214 |
13 |
if (!resolvedReference.isPresent()) { |
215 |
0 |
return false; |
216 |
|
} |
217 |
13 |
Object object = resolvedReference.get(); |
218 |
13 |
if (object instanceof Node) { |
219 |
4 |
if (resolvedProperties.containsKey(propertyPath) && resolvedProperties.get(propertyPath).contains(referenceValue)) { |
220 |
0 |
return false; |
221 |
|
} |
222 |
4 |
resolvedProperties.putSingle(propertyPath, referenceValue); |
223 |
|
} |
224 |
13 |
return true; |
225 |
|
} |
226 |
|
|
|
|
| 66.7% |
Uncovered Elements: 2 (6) |
Complexity: 2 |
Complexity Density: 0.5 |
|
227 |
717 |
private Optional<ReferenceResolver> getReferenceResolver(Property property) {... |
228 |
717 |
Optional<ReferenceContext> context = getReferenceContext(); |
229 |
|
|
230 |
717 |
if (context.isPresent()) { |
231 |
|
|
232 |
717 |
return context.get().getResolvers().entrySet().stream() |
233 |
|
.filter(entry -> entry.getKey().test(property)) |
234 |
|
.map(Map.Entry::getValue) |
235 |
|
.findFirst(); |
236 |
|
} |
237 |
|
|
238 |
0 |
return Optional.empty(); |
239 |
|
} |
240 |
|
|
|
|
| 71.4% |
Uncovered Elements: 2 (7) |
Complexity: 2 |
Complexity Density: 0.4 |
|
241 |
717 |
private Optional<ReferenceContext> getReferenceContext() {... |
242 |
717 |
if (providers == null) { |
243 |
0 |
return Optional.empty(); |
244 |
|
} |
245 |
|
|
246 |
717 |
ContextResolver<ReferenceContext> contextResolver = providers.getContextResolver(ReferenceContext.class, MediaType.WILDCARD_TYPE); |
247 |
717 |
ReferenceContext context = contextResolver.getContext(ReferenceContext.class); |
248 |
717 |
return Optional.ofNullable(context); |
249 |
|
} |
250 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (36) |
Complexity: 8 |
Complexity Density: 0.36 |
|
251 |
13 |
private void writeResolvedReference(JsonGenerator jsonGenerator, Object resolvedObject, String propertyName, boolean prependComma, MultivaluedMap<String, String> resolvedProperties,... |
252 |
|
Annotation[] annotations, OutputStream entityStream, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders) throws IOException, RepositoryException { |
253 |
13 |
if (resolvedObject instanceof Node) { |
254 |
4 |
writeNode(jsonGenerator, (Node) resolvedObject, propertyName, false, resolvedProperties, annotations, entityStream, mediaType, httpHeaders); |
255 |
4 |
return; |
256 |
|
} |
257 |
|
|
258 |
9 |
if (resolvedObject instanceof Property) { |
259 |
3 |
Property resolvedProperty = (Property) resolvedObject; |
260 |
|
|
261 |
3 |
if (propertyName == null) { |
262 |
2 |
jsonGenerator.write(resolvedProperty.getString()); |
263 |
|
} else { |
264 |
1 |
jsonGenerator.write(propertyName, resolvedProperty.getString()); |
265 |
|
} |
266 |
3 |
return; |
267 |
|
} |
268 |
|
|
269 |
6 |
if (resolvedObject instanceof String) { |
270 |
|
|
271 |
3 |
if (propertyName == null) { |
272 |
2 |
jsonGenerator.write(resolvedObject.toString()); |
273 |
|
} else { |
274 |
1 |
jsonGenerator.write(propertyName, resolvedObject.toString()); |
275 |
|
} |
276 |
3 |
return; |
277 |
|
} |
278 |
|
|
279 |
|
|
280 |
3 |
MessageBodyWriter writer = providers.getMessageBodyWriter(resolvedObject.getClass(), resolvedObject.getClass(), annotations, mediaType); |
281 |
3 |
jsonGenerator.flush(); |
282 |
|
|
283 |
|
|
284 |
3 |
if (prependComma) { |
285 |
2 |
entityStream.write(','); |
286 |
|
} |
287 |
|
|
288 |
3 |
if (propertyName != null) { |
289 |
|
|
290 |
1 |
String propertyKey = String.format("\"%s\":", propertyName); |
291 |
1 |
entityStream.write(propertyKey.getBytes(Charset.forName("UTF-8"))); |
292 |
|
} |
293 |
|
|
294 |
3 |
writer.writeTo(resolvedObject, resolvedObject.getClass(), resolvedObject.getClass(), annotations, mediaType, httpHeaders, entityStream); |
295 |
|
} |
296 |
|
|
|
|
| 100% |
Uncovered Elements: 0 (4) |
Complexity: 1 |
Complexity Density: 0.25 |
|
297 |
116 |
private void writeSpecialProperties(JsonGenerator jsonGenerator, Node node) throws RepositoryException {... |
298 |
116 |
jsonGenerator.write(NAME_PROPERTY, node.getName()); |
299 |
116 |
jsonGenerator.write(PATH_PROPERTY, node.getPath()); |
300 |
116 |
jsonGenerator.write(UUID_PROPERTY, node.getIdentifier()); |
301 |
116 |
jsonGenerator.write(NODE_TYPE_PROPERTY, node.getPrimaryNodeType().getName()); |
302 |
|
} |
303 |
|
|
|
|
| 45.5% |
Uncovered Elements: 6 (11) |
Complexity: 6 |
Complexity Density: 0.55 |
|
304 |
727 |
private String getValueString(Value value) throws RepositoryException {... |
305 |
727 |
switch (value.getType()) { |
306 |
0 |
case PropertyType.BINARY: |
307 |
0 |
return getBinaryString(value); |
308 |
0 |
case PropertyType.DECIMAL: |
309 |
0 |
return String.valueOf(value.getDecimal()); |
310 |
0 |
case PropertyType.NAME: |
311 |
0 |
case PropertyType.URI: |
312 |
309 |
case PropertyType.DATE: |
313 |
309 |
return value.getString(); |
314 |
418 |
default: |
315 |
418 |
return PropertyUtil.getValueString(value); |
316 |
|
} |
317 |
|
} |
318 |
|
|
319 |
|
|
320 |
|
@link |
321 |
|
@link |
322 |
|
|
323 |
|
@see |
324 |
|
|
|
|
| 0% |
Uncovered Elements: 5 (5) |
Complexity: 2 |
Complexity Density: 0.4 |
|
325 |
0 |
private String getBinaryString(Value propertyValue) throws RepositoryException {... |
326 |
|
|
327 |
0 |
byte[] decodedPropertyValue; |
328 |
0 |
try { |
329 |
0 |
decodedPropertyValue = Base64.encodeBase64(IOUtils.toByteArray(propertyValue.getBinary().getStream())); |
330 |
|
} catch (IOException e) { |
331 |
0 |
throw new RepositoryException(e); |
332 |
|
} |
333 |
0 |
return new String(decodedPropertyValue); |
334 |
|
} |
335 |
|
} |