Clover icon

Magnolia REST Content Delivery 2.1

  1. Project Clover database Fri Mar 16 2018 18:21:08 CET
  2. Package info.magnolia.rest.delivery.jcr

File NodeWriterTest.java

 

Code metrics

24
301
29
4
784
538
57
0.19
10.38
7.25
1.97
1.9% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
NodeWriterTest 97 280 0.9% 47 23
0.928792692.9%
NodeWriterTest.PausableNodeWriter 702 13 0% 6 3
0.8421052784.2%
NodeWriterTest.TestObject 744 1 66.7% 1 0
1.0100%
NodeWriterTest.TestObjectMessageBodyWriter 763 7 0% 3 4
0.660%
 

Contributing tests

This file is covered by 18 tests. .

Source view

1    /**
2    * This file Copyright (c) 2017-2018 Magnolia International
3    * Ltd. (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10    * This file is distributed in the hope that it will be
11    * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12    * implied warranty of MERCHANTABILITY or FITNESS FOR A
13    * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14    * Redistribution, except as permitted by whichever of the GPL
15    * or MNA you select, is prohibited.
16    *
17    * 1. For the GPL license (GPL), you can redistribute and/or
18    * modify this file under the terms of the GNU General
19    * Public License, Version 3, as published by the Free Software
20    * Foundation. You should have received a copy of the GNU
21    * General Public License, Version 3 along with this program;
22    * if not, write to the Free Software Foundation, Inc., 51
23    * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24    *
25    * 2. For the Magnolia Network Agreement (MNA), this file
26    * and the accompanying materials are made available under the
27    * terms of the MNA which accompanies this distribution, and
28    * is available at http://www.magnolia-cms.com/mna.html
29    *
30    * Any modifications to this file must keep this entire header
31    * intact.
32    *
33    */
34    package info.magnolia.rest.delivery.jcr;
35   
36    import static info.magnolia.test.hamcrest.ExecutionMatcher.throwsAnException;
37    import static java.util.concurrent.TimeUnit.SECONDS;
38    import static org.hamcrest.Matchers.contains;
39    import static org.hamcrest.Matchers.*;
40    import static org.hamcrest.Matchers.startsWith;
41    import static org.junit.Assert.*;
42    import static org.mockito.Mockito.*;
43   
44    import info.magnolia.context.MgnlContext;
45    import info.magnolia.jcr.util.NodeTypes;
46    import info.magnolia.jcr.util.PropertiesImportExport;
47    import info.magnolia.repository.RepositoryConstants;
48    import info.magnolia.rest.reference.ConfiguredReferenceResolverDefinition;
49    import info.magnolia.rest.reference.ReferenceContext;
50    import info.magnolia.rest.reference.ReferenceResolver;
51    import info.magnolia.test.RepositoryTestCase;
52   
53    import java.io.ByteArrayOutputStream;
54    import java.io.IOException;
55    import java.io.OutputStream;
56    import java.lang.annotation.Annotation;
57    import java.lang.reflect.Type;
58    import java.time.format.DateTimeFormatter;
59    import java.time.format.DateTimeParseException;
60    import java.util.ArrayList;
61    import java.util.LinkedHashMap;
62    import java.util.List;
63    import java.util.Map;
64    import java.util.Optional;
65    import java.util.concurrent.CountDownLatch;
66    import java.util.concurrent.Executor;
67    import java.util.concurrent.Executors;
68    import java.util.function.Predicate;
69   
70    import javax.jcr.Node;
71    import javax.jcr.Property;
72    import javax.jcr.RepositoryException;
73    import javax.jcr.Session;
74    import javax.json.Json;
75    import javax.json.stream.JsonGenerator;
76    import javax.ws.rs.WebApplicationException;
77    import javax.ws.rs.core.HttpHeaders;
78    import javax.ws.rs.core.MediaType;
79    import javax.ws.rs.core.MultivaluedMap;
80    import javax.ws.rs.ext.MessageBodyWriter;
81    import javax.ws.rs.ext.Providers;
82   
83    import org.apache.jackrabbit.value.StringValue;
84    import org.junit.After;
85    import org.junit.Before;
86    import org.junit.Test;
87    import org.mockito.InjectMocks;
88    import org.mockito.Mock;
89    import org.mockito.MockitoAnnotations;
90    import org.slf4j.Logger;
91    import org.slf4j.LoggerFactory;
92   
93    import com.fasterxml.jackson.core.type.TypeReference;
94    import com.fasterxml.jackson.databind.ObjectMapper;
95    import com.google.common.collect.ImmutableMap;
96   
 
97    public class NodeWriterTest extends RepositoryTestCase {
98    private static final Logger log = LoggerFactory.getLogger(NodeWriterTest.class);
99   
100    private Session session;
101    private ByteArrayOutputStream baos;
102    private ObjectMapper mapper;
103    private Node about;
104    private ReferenceContext referenceContext;
105   
106    @InjectMocks
107    private NodeWriter writer;
108   
109    @Mock
110    private Providers providers;
111   
112    @Mock
113    private HttpHeaders headers;
114   
 
115  18 toggle @Before
116    public void setUp() throws Exception {
117  18 super.setUp();
118  18 session = MgnlContext.getJCRSession(RepositoryConstants.WEBSITE);
119  18 new PropertiesImportExport().createNodes(session.getRootNode(), getClass().getClassLoader().getResourceAsStream("travel-test-data.properties"));
120  18 session.save();
121   
122  18 baos = new ByteArrayOutputStream();
123  18 mapper = new ObjectMapper();
124  18 writer = new NodeWriter();
125  18 about = session.getRootNode().getNode("travel/about");
126   
127  18 initInjectionMock();
128    }
129   
 
130  1 toggle @Test
131    public void writingNodeWithoutFiltering() throws Exception {
132    // WHEN
133  1 writer.writeTo(about, null, null, null, null, null, baos);
134  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
135   
136    // THEN
137  1 assertTrue(map.containsKey("company") && map.get("company") != null);
138  1 assertTrue(map.containsKey(NodeTypes.LastModified.NAME));
139  1 Map<String, Object> company = (Map<String, Object>) map.get("company");
140  1 assertTrue(company.containsKey("main") && company.get("main") != null);
141  1 assertTrue(company.containsKey(NodeTypes.LastModified.NAME));
142  1 Map<String, Object> main = (Map<String, Object>) company.get("main");
143  1 assertTrue(main.containsKey("00") && main.get("00") != null);
144  1 assertTrue(main.containsKey(NodeTypes.LastModified.NAME));
145  1 Map<String, String> doubleZero = (Map<String, String>) main.get("00");
146  1 assertTrue(doubleZero.containsKey("headline"));
147  1 assertTrue(map.containsKey("footer"));
148  1 assertTrue(map.containsKey("@id"));
149    }
150   
 
151    toggle @Test
152    public void isWritable() {
153    // WHEN
154    boolean isWritable = writer.isWriteable(Node.class, null, new Annotation[] {}, MediaType.APPLICATION_JSON_TYPE);
155   
156    // THEN
157    assertEquals(true, isWritable);
158    }
159   
 
160  1 toggle @Test
161    public void writingNodeReturnsJsonIncludingMetaData() throws Exception {
162    // WHEN
163  1 writer.writeTo(about, null, null, null, null, null, baos);
164  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
165   
166    // THEN
167  1 assertTrue(map.containsKey("company"));
168  1 assertTrue(map.containsKey(NodeTypes.LastModified.NAME));
169  1 assertTrue(map.containsKey("@id"));
170  1 assertTrue(map.containsKey("@nodeType"));
171    }
172   
 
173  1 toggle @Test
174    public void writingNodeHavingMultiplePropertiesReturnExpectedJson() throws Exception {
175    // GIVEN
176  1 addTagsPropertyToAboutNode();
177   
178    // WHEN
179  1 writer.writeTo(about, null, null, null, null, null, baos);
180  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
181   
182    // THEN
183  1 assertTrue(map.containsKey("tags") && map.get("tags") != null);
184  1 List<String> tags = (List<String>) map.get("tags");
185  1 assertEquals(3, tags.size());
186    }
187   
 
188  3 toggle private void addTagsPropertyToAboutNode() throws RepositoryException {
189    // Add multiple-value property "tags" to "about" node.
190  3 about.setProperty("tags", new String[] { "travel", "culture", "cuisine" });
191    }
192   
 
193  1 toggle @Test
194    public void propertyHavingTheSameNameWithSubNodeShouldBePrefixedWithUnderscore() throws Exception {
195    // GIVEN
196  1 addTagsPropertyToAboutNode();
197  1 addTagsNodeToAboutNode();
198   
199    // WHEN
200  1 writer.writeTo(about, null, null, null, null, null, baos);
201  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
202   
203    // THEN
204  1 assertTags(map);
205    }
206   
 
207  2 toggle private void addTagsNodeToAboutNode() throws RepositoryException {
208  2 Node tags = about.addNode("tags", NodeTypes.Page.NAME);
209  2 tags.setProperty("title", "Tags");
210    }
211   
 
212  2 toggle private void assertTags(Map<String, Object> map) {
213  2 assertTrue(map.containsKey("_tags") && map.get("_tags") != null);
214  2 List tags = (List) map.get("_tags");
215  2 assertEquals(3, tags.size());
216  2 assertTrue(map.containsKey("tags") && map.get("tags") != null);
217  2 Map<String, String> tagNodeMap = (Map<String, String>) map.get("tags");
218  2 assertTrue(tagNodeMap.containsKey("title"));
219    }
220   
 
221  1 toggle @Test
222    public void propertyHavingTheSameNameWithSubNodeShouldBePrefixedWithUnderscore_ChangeInsertionOrder() throws Exception {
223    // GIVEN
224  1 addTagsNodeToAboutNode();
225  1 addTagsPropertyToAboutNode();
226   
227    // WHEN
228  1 writer.writeTo(about, null, null, null, null, null, baos);
229  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
230   
231    // THEN
232  1 assertTags(map);
233    }
234   
 
235  1 toggle @Test
236    public void resultContainsMetaProperties() throws Exception {
237    // WHEN
238  1 writer.writeTo(about, null, null, null, null, null, baos);
239  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
240   
241    // THEN
242  1 assertTrue(map.containsKey("@name"));
243  1 assertTrue(map.containsKey("@path"));
244  1 assertTrue(map.containsKey("@nodes"));
245  1 assertThat((List<String>) map.get("@nodes"), contains("company", "footer"));
246    }
247   
 
248  1 toggle @Test
249    public void dateTimeShouldBeISO8601() throws Exception {
250    // WHEN
251  1 writer.writeTo(about, null, null, null, null, null, baos);
252  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
253   
254    // THEN
255  1 assertThat(() -> DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX").parse((String) map.get(NodeTypes.LastModified.NAME)),
256    not(throwsAnException(instanceOf(DateTimeParseException.class))));
257    }
258   
 
259  1 toggle @Test
260    public void resolveSingleReferencePropertyWithUuidResolverReturnNode() throws Exception {
261    // GIVEN
262  1 Node referNode = session.getRootNode().addNode("referNode");
263  1 Node node = session.getRootNode().addNode("node");
264  1 node.setProperty("propertyName", referNode.getIdentifier());
265   
266  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
267  1 try {
268  1 if (property.getName().matches("propertyName")) {
269  1 return true;
270    }
271    } catch (RepositoryException e) {
272    //
273    }
274  0 return false;
275    }, param -> {
276  1 ReferenceResolver resolver = (ReferenceResolver<Object, Node>) item -> {
277  1 try {
278  1 return Optional.of(session.getNodeByIdentifier(item.toString()));
279    } catch (RepositoryException e) {
280    //
281    }
282   
283  0 return Optional.empty();
284    };
285   
286  1 return resolver.resolve(param);
287    });
288   
289  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
290   
291    // WHEN
292  1 writer.writeTo(node, null, null, null, null, null, baos);
293   
294  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
295   
296    // THEN
297  1 assertTrue(map.containsKey("propertyName"));
298  1 assertThat(map.get("propertyName"), instanceOf(LinkedHashMap.class));
299  1 Map<String, Object> actual = (Map<String, Object>) map.get("propertyName");
300  1 assertTrue(actual.containsKey("@name"));
301    }
302   
 
303  1 toggle @Test
304    public void resolveMultipleValuesReferencePropertyWithUuidResolverReturnNodes() throws Exception {
305    // GIVEN
306  1 Node referNode1 = session.getRootNode().addNode("referNode1");
307  1 Node referNode2 = session.getRootNode().addNode("referNode2");
308  1 Node node = session.getRootNode().addNode("node");
309  1 node.setProperty("propertyName", new StringValue[] { new StringValue(referNode1.getIdentifier()), new StringValue(referNode2.getIdentifier()) });
310   
311  1 ConfiguredReferenceResolverDefinition referenceResolverDefinition = new ConfiguredReferenceResolverDefinition();
312  1 referenceResolverDefinition.setTargetWorkspace(session.getWorkspace().getName());
313   
314  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
315  1 try {
316  1 if (property.getName().matches("propertyName")) {
317  1 return true;
318    }
319    } catch (RepositoryException e) {
320    //
321    }
322  0 return false;
323    }, param -> {
324  2 ReferenceResolver resolver = (ReferenceResolver<Object, Node>) item -> {
325  2 try {
326  2 return Optional.of(session.getNodeByIdentifier(item.toString()));
327    } catch (RepositoryException e) {
328    //
329    }
330   
331  0 return Optional.empty();
332    };
333   
334  2 return resolver.resolve(param);
335    });
336   
337  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
338   
339    // WHEN
340  1 writer.writeTo(node, null, null, null, null, null, baos);
341   
342  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
343   
344    // THEN
345  1 assertTrue(map.containsKey("propertyName"));
346  1 assertThat(map.get("propertyName"), instanceOf(List.class));
347  1 List list = (List) map.get("propertyName");
348  1 assertEquals(list.size(), 2);
349  1 assertThat(list.get(0), instanceOf(LinkedHashMap.class));
350  1 assertThat(list.get(1), instanceOf(LinkedHashMap.class));
351    }
352   
 
353  1 toggle @Test
354    public void resolveSingleReferencePropertyWithUuidResolverReturnProperty() throws Exception {
355    // GIVEN
356  1 Node referNode = session.getRootNode().addNode("referNode");
357  1 Property testProperty = referNode.setProperty("referenceProperty", "this is reference property");
358  1 Node node = session.getRootNode().addNode("node");
359  1 node.setProperty("propertyName", referNode.getIdentifier());
360   
361  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
362  1 try {
363  1 if (property.getName().matches("propertyName")) {
364  1 return true;
365    }
366    } catch (RepositoryException e) {
367    //
368    }
369  0 return false;
370    }, param -> {
371  1 ReferenceResolver resolver = (ReferenceResolver<Object, Property>) item -> Optional.of(testProperty);
372   
373  1 return resolver.resolve(param);
374    });
375   
376  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
377   
378    // WHEN
379  1 writer.writeTo(node, null, null, null, null, null, baos);
380   
381  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
382   
383    // THEN
384  1 assertTrue(map.containsKey("propertyName"));
385  1 assertThat(map.get("propertyName"), instanceOf(String.class));
386  1 assertThat(map.get("propertyName").toString(), equalToIgnoringCase("this is reference property"));
387    }
388   
 
389  1 toggle @Test
390    public void resolveMultipleValuesReferencePropertyWithUuidResolverReturnProperties() throws Exception {
391    // GIVEN
392  1 Node referNode1 = session.getRootNode().addNode("referNode1");
393  1 Property refProperty1 = referNode1.setProperty("refProperty1", "refProperty1");
394  1 Property refProperty2 = referNode1.setProperty("refProperty2", "refProperty2");
395  1 session.getRootNode().addNode("referNode2");
396  1 Node node = session.getRootNode().addNode("node");
397  1 node.setProperty("propertyName", new StringValue[] { new StringValue("refProperty1"), new StringValue("refProperty2") });
398   
399  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
400  1 try {
401  1 if (property.getName().matches("propertyName")) {
402  1 return true;
403    }
404    } catch (RepositoryException e) {
405    //
406    }
407  0 return false;
408    }, param -> {
409  2 ReferenceResolver resolver = (ReferenceResolver<String, Property>) item -> {
410  2 if (item.matches("refProperty1")) {
411  1 return Optional.of(refProperty1);
412    }
413   
414  1 return Optional.of(refProperty2);
415    };
416   
417  2 return resolver.resolve(param);
418    });
419   
420  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
421   
422    // WHEN
423  1 writer.writeTo(node, null, null, null, null, null, baos);
424   
425  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
426   
427    // THEN
428  1 assertTrue(map.containsKey("propertyName"));
429  1 assertThat(map.get("propertyName"), instanceOf(List.class));
430  1 List<String> list = (List) map.get("propertyName");
431  1 assertEquals(list.size(), 2);
432  1 assertThat(list, containsInAnyOrder("refProperty1", "refProperty2"));
433    }
434   
435   
 
436  1 toggle @Test
437    public void resolveSingleReferencePropertyWithUuidResolverReturnString() throws Exception {
438    // GIVEN
439  1 Node referNode = session.getRootNode().addNode("referNode");
440  1 Node node = session.getRootNode().addNode("node");
441  1 node.setProperty("propertyName", referNode.getIdentifier());
442   
443  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
444  1 try {
445  1 if (property.getName().matches("propertyName")) {
446  1 return true;
447    }
448    } catch (RepositoryException e) {
449    //
450    }
451  0 return false;
452    }, param -> {
453  1 ReferenceResolver resolver = (ReferenceResolver<String, String>) Optional::of;
454   
455  1 return resolver.resolve(param);
456    });
457   
458  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
459   
460    // WHEN
461  1 writer.writeTo(node, null, null, null, null, null, baos);
462   
463  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
464   
465    // THEN
466  1 assertTrue(map.containsKey("propertyName"));
467  1 assertThat(map.get("propertyName"), instanceOf(String.class));
468  1 assertThat(map.get("propertyName").toString(), equalToIgnoringCase(referNode.getIdentifier()));
469    }
470   
 
471  1 toggle @Test
472    public void resolveMultipleValuesReferencePropertyWithUuidResolverReturnStrings() throws Exception {
473    // GIVEN
474  1 session.getRootNode().addNode("referNode2");
475  1 Node node = session.getRootNode().addNode("node");
476  1 node.setProperty("propertyName", new StringValue[] { new StringValue("refProperty1"), new StringValue("refProperty2") });
477   
478  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
479  1 try {
480  1 if (property.getName().matches("propertyName")) {
481  1 return true;
482    }
483    } catch (RepositoryException e) {
484    //
485    }
486  0 return false;
487    }, param -> {
488  2 ReferenceResolver resolver = (ReferenceResolver<String, String>) Optional::of;
489   
490  2 return resolver.resolve(param);
491    });
492   
493  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
494   
495    // WHEN
496  1 writer.writeTo(node, null, null, null, null, null, baos);
497   
498  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
499   
500    // THEN
501  1 assertTrue(map.containsKey("propertyName"));
502  1 assertThat(map.get("propertyName"), instanceOf(List.class));
503  1 List<String> list = (List<String>) map.get("propertyName");
504  1 assertEquals(list.size(), 2);
505  1 assertThat(list, containsInAnyOrder("refProperty1", "refProperty2"));
506    }
507   
508   
 
509  1 toggle @Test
510    public void resolveSingleReferencePropertyWithUuidResolverReturnObject() throws Exception {
511    // GIVEN
512  1 Node referNode = session.getRootNode().addNode("referNode");
513  1 Node node = session.getRootNode().addNode("node");
514  1 node.setProperty("propertyName", referNode.getIdentifier());
515   
516  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
517  1 try {
518  1 if (property.getName().matches("propertyName")) {
519  1 return true;
520    }
521    } catch (RepositoryException e) {
522    //
523    }
524  0 return false;
525    }, param -> {
526  1 ReferenceResolver resolver = (ReferenceResolver<String, TestObject>) item -> Optional.of(new TestObject(item));
527   
528  1 return resolver.resolve(param);
529    });
530   
531  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
532   
533    // WHEN
534  1 writer.writeTo(node, null, null, null, MediaType.APPLICATION_JSON_TYPE, null, baos);
535   
536  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
537   
538    // THEN
539  1 assertTrue(map.containsKey("propertyName"));
540  1 assertThat(map.get("propertyName"), instanceOf(LinkedHashMap.class));
541  1 Map<String, Object> actual = (Map<String, Object>) map.get("propertyName");
542  1 assertTrue(actual.containsKey("name"));
543    }
544   
 
545  1 toggle @Test
546    public void resolveMultipleValuesReferencePropertyWithUuidResolverReturnObjects() throws Exception {
547    // GIVEN
548  1 session.getRootNode().addNode("referNode2");
549  1 Node node = session.getRootNode().addNode("node");
550  1 node.setProperty("propertyName", new StringValue[] { new StringValue("refProperty1"), new StringValue("refProperty2") });
551   
552  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
553  1 try {
554  1 if (property.getName().matches("propertyName")) {
555  1 return true;
556    }
557    } catch (RepositoryException e) {
558    //
559    }
560  0 return false;
561    }, param -> {
562  2 ReferenceResolver resolver = (ReferenceResolver<String, TestObject>) item -> Optional.of(new TestObject(item));
563   
564  2 return resolver.resolve(param);
565    });
566   
567  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
568   
569    // WHEN
570  1 writer.writeTo(node, null, null, null, MediaType.APPLICATION_JSON_TYPE, null, baos);
571   
572  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
573   
574    // THEN
575  1 assertTrue(map.containsKey("propertyName"));
576  1 assertThat(map.get("propertyName"), instanceOf(List.class));
577  1 List list = (List) map.get("propertyName");
578  1 assertEquals(list.size(), 2);
579  1 assertThat(list.get(0), instanceOf(LinkedHashMap.class));
580  1 assertThat(list.get(1), instanceOf(LinkedHashMap.class));
581    }
582   
 
583  1 toggle @Test
584    public void wontSupportRecursionReferenceResolver() throws Exception {
585    // GIVEN
586  1 Node referNode1 = session.getRootNode().addNode("referNode1");
587  1 Node referNode2 = session.getRootNode().addNode("referNode2");
588  1 referNode1.setProperty("propertyName", referNode2.getIdentifier());
589  1 referNode2.setProperty("propertyName", referNode1.getIdentifier());
590   
591  1 ConfiguredReferenceResolverDefinition referenceResolverDefinition = new ConfiguredReferenceResolverDefinition();
592  1 referenceResolverDefinition.setTargetWorkspace(session.getWorkspace().getName());
593   
594  1 Map<Predicate, ReferenceResolver> resolvers = ImmutableMap.of((Predicate<Property>) property -> {
595  2 try {
596  2 if (property.getName().matches("propertyName")) {
597  2 return true;
598    }
599    } catch (RepositoryException e) {
600    //
601    }
602  0 return false;
603    }, param -> {
604  1 ReferenceResolver resolver = (ReferenceResolver<String, Node>) item -> {
605  1 try {
606  1 return Optional.of(session.getNodeByIdentifier(item));
607    } catch (RepositoryException e) {
608    //
609    }
610   
611  0 return Optional.empty();
612    };
613   
614  1 return resolver.resolve(param);
615    });
616   
617  1 when(referenceContext.getResolvers()).thenReturn(resolvers);
618   
619    // WHEN
620  1 writer.writeTo(referNode2, null, null, null, null, null, baos);
621   
622  1 Map<String, Object> map = mapper.readValue(baos.toString(), new TypeReference<Map<String, Object>>() {});
623   
624    // THEN
625  1 assertTrue(map.containsKey("propertyName"));
626  1 assertThat(map.get("@name").toString(), equalToIgnoringCase("referNode2"));
627  1 assertThat(map.get("propertyName"), instanceOf(LinkedHashMap.class));
628  1 LinkedHashMap resolvedMap = (LinkedHashMap) map.get("propertyName");
629  1 assertTrue(resolvedMap.containsKey("propertyName"));
630  1 assertThat(resolvedMap.get("@name").toString(), equalToIgnoringCase("referNode1"));
631  1 assertThat(resolvedMap.get("propertyName"), instanceOf(String.class));
632  1 assertThat(resolvedMap.get("propertyName").toString(), equalToIgnoringCase(referNode2.getIdentifier()));
633    }
634   
 
635  1 toggle @Test
636    public void nodeWriterIsThreadSafe() throws Exception {
637    // GIVEN
638  1 long testExecThreadId = Thread.currentThread().getId();
639  1 CountDownLatch latch = new CountDownLatch(1);
640  1 writer = new PausableNodeWriter(testExecThreadId, latch);
641  1 log.debug("{} > starting test", testExecThreadId);
642   
643  1 List<String> results = new ArrayList<>();
644  1 Runnable command = () -> {
645  2 try {
646  2 initInjectionMock();
647   
648  2 long tid = Thread.currentThread().getId();
649  2 ByteArrayOutputStream baos = new ByteArrayOutputStream();
650  2 writer.writeTo(about, null, null, null, null, null, baos);
651  2 String result = new String(baos.toByteArray());
652  2 results.add(result);
653  2 log.debug("{} > done", tid);
654    } catch (Exception e) {
655  0 e.printStackTrace();
656  0 fail();
657    } finally {
658  2 try {
659  2 NodeWriterTest.this.end();
660    } catch (IOException e) {
661    //
662    }
663    }
664    };
665   
666    // start the async exec until the waiting point (give it space to start before the 2nd run)
667  1 Executor service = Executors.newSingleThreadExecutor();
668  1 service.execute(command);
669  1 Thread.sleep(200);
670   
671    // second run is synchronous within the test exec thread
672  1 command.run();
673   
674    // resume the first execution and leave time to complete
675  1 latch.countDown();
676  1 Thread.sleep(1000);
677   
678    // THEN
679  1 assertThat(results, hasSize(2));
680  1 assertThat(results.get(0), startsWith("{\"@name\":\"about\",\"@path\":\"/travel/about\""));
681  1 assertEquals(results.get(0), results.get(1));
682    }
683   
 
684  20 toggle private void initInjectionMock() {
685  20 MockitoAnnotations.initMocks(NodeWriterTest.this);
686  20 TestObjectMessageBodyWriter messageBodyWriter = new TestObjectMessageBodyWriter();
687  20 when(headers.getMediaType()).thenReturn(MediaType.APPLICATION_JSON_TYPE);
688  20 when(providers.getMessageBodyWriter(TestObject.class, TestObject.class, null, MediaType.APPLICATION_JSON_TYPE)).thenReturn(messageBodyWriter);
689   
690  20 referenceContext = mock(ReferenceContext.class);
691  20 when(providers.getContextResolver(ReferenceContext.class, MediaType.WILDCARD_TYPE)).thenReturn(type -> referenceContext);
692    }
693   
 
694  20 toggle @After
695    public void end() throws IOException {
696  20 baos.close();
697    }
698   
699    /**
700    * Instruments the NodeWriter with a waiting latch, if it runs async to the test execution thread.
701    */
 
702    private class PausableNodeWriter extends NodeWriter {
703    private final long testExecThreadId;
704    private final CountDownLatch latch;
705   
706    /**
707    * @param testExecThreadId the thread ID of the test execution thread, in order to block only the async thread.
708    * @param latch the {@link CountDownLatch} to wait upon; to be controlled by the test case.
709    */
 
710  1 toggle PausableNodeWriter(long testExecThreadId, CountDownLatch latch) {
711  1 this.testExecThreadId = testExecThreadId;
712  1 this.latch = latch;
713    }
714   
 
715  10 toggle @Override
716    void writeNode(JsonGenerator jsonGenerator, Node node, String objectKey, boolean isResolveReference, MultivaluedMap<String, String> resolvedProperties,
717    Annotation[] annotations, OutputStream entityStream, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders) throws IOException {
718  10 try {
719  10 long tid = Thread.currentThread().getId();
720  10 boolean isAsync = tid != testExecThreadId;
721   
722    // writeNode is a recursive method;
723    // pause only after the JSON object construction has started
724  10 if (isAsync && "/travel/about/company".equals(node.getPath())) {
725  1 log.debug("{} > latch waiting at {}", tid, node.getPath());
726  1 boolean received = latch.await(2, SECONDS);
727  1 log.debug("{} > latch {}", tid, received ? "passing" : "timed out");
728    }
729   
730  10 log.debug("{} > write {}", tid, node.getPath());
731   
732  10 super.writeNode(jsonGenerator, node, objectKey, isResolveReference, resolvedProperties, annotations, entityStream, mediaType, httpHeaders);
733   
734    } catch (InterruptedException | RepositoryException e) {
735  0 log.error(e.getMessage(), e);
736  0 fail();
737    }
738    }
739    }
740   
741    /**
742    * A test object for testing nodeWriter can write an object as reference.
743    */
 
744    private class TestObject {
745    private String name;
746   
 
747  3 toggle TestObject(String name) {
748  3 this.name = name;
749    }
750   
 
751    toggle public String getName() {
752    return name;
753    }
754   
 
755    toggle public void setName(String name) {
756    this.name = name;
757    }
758    }
759   
760    /**
761    * Message body writer for {@link TestObject}.
762    */
 
763    private class TestObjectMessageBodyWriter implements MessageBodyWriter<TestObject> {
764   
 
765  0 toggle @Override
766    public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
767  0 return type.isAssignableFrom(TestObject.class);
768    }
769   
 
770  0 toggle @Override
771    public long getSize(TestObject testObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
772  0 return 0;
773    }
774   
 
775  3 toggle @Override
776    public void writeTo(TestObject testObject, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException {
777  3 JsonGenerator jsonGenerator = Json.createGenerator(entityStream);
778  3 jsonGenerator.writeStartObject();
779  3 jsonGenerator.write("name", testObject.getName());
780  3 jsonGenerator.writeEnd();
781  3 jsonGenerator.flush();
782    }
783    }
784    }