Clover icon

Magnolia Imaging Module 3.2.3

  1. Project Clover database Fri Nov 6 2015 14:54:03 CET
  2. Package info.magnolia.imaging.caching

File CachingImageStreamerRepositoryTest.java

 

Code metrics

16
195
21
5
581
369
31
0.16
9.29
4.2
1.48
1.7% of code in this file is excluded from these metrics.

Classes

Class Line # Actions
CachingImageStreamerRepositoryTest 108 167 1% 19 75
0.6073298560.7%
CachingImageStreamerRepositoryTest.TestJob 412 6 0% 2 0
1.0100%
CachingImageStreamerRepositoryTest.DelegatingSessionStrategy 509 11 0% 4 0
1.0100%
CachingImageStreamerRepositoryTest.TestParameterProviderFactory 538 6 20% 3 1
0.87587.5%
CachingImageStreamerRepositoryTest.SingleSaveSessionWrapper 564 5 0% 3 2
0.777777877.8%
 

Contributing tests

This file is covered by 2 tests. .

Source view

1    /**
2    * This file Copyright (c) 2009-2015 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.imaging.caching;
35   
36    import static org.hamcrest.Matchers.*;
37    import static org.junit.Assert.*;
38    import static org.mockito.Matchers.isA;
39    import static org.mockito.Mockito.*;
40   
41    import info.magnolia.cms.core.Content;
42    import info.magnolia.cms.core.HierarchyManager;
43    import info.magnolia.cms.core.NodeData;
44    import info.magnolia.cms.util.ContentUtil;
45    import info.magnolia.context.JCRSessionStrategy;
46    import info.magnolia.context.MgnlContext;
47    import info.magnolia.imaging.AbstractRepositoryTestCase;
48    import info.magnolia.imaging.DefaultImageStreamer;
49    import info.magnolia.imaging.ImageGenerator;
50    import info.magnolia.imaging.ImageStreamer;
51    import info.magnolia.imaging.ImagingException;
52    import info.magnolia.imaging.OutputFormat;
53    import info.magnolia.imaging.ParameterProvider;
54    import info.magnolia.imaging.ParameterProviderFactory;
55    import info.magnolia.imaging.StringParameterProvider;
56    import info.magnolia.imaging.operations.ImageOperationChain;
57    import info.magnolia.imaging.operations.load.URLImageLoader;
58    import info.magnolia.imaging.parameters.ContentParameterProvider;
59    import info.magnolia.imaging.parameters.SimpleEqualityContentWrapper;
60    import info.magnolia.jcr.predicate.NodeTypePredicate;
61    import info.magnolia.jcr.util.NodeUtil;
62    import info.magnolia.jcr.wrapper.DelegateSessionWrapper;
63    import info.magnolia.module.ModuleManagementException;
64    import info.magnolia.module.ModuleManager;
65    import info.magnolia.module.ModuleManagerImpl;
66    import info.magnolia.module.ModuleRegistry;
67    import info.magnolia.module.model.ModuleDefinition;
68    import info.magnolia.module.model.reader.ModuleDefinitionReader;
69    import info.magnolia.test.ComponentsTestUtil;
70    import info.magnolia.test.mock.MockContext;
71   
72    import java.awt.GraphicsEnvironment;
73    import java.awt.image.BufferedImage;
74    import java.io.ByteArrayOutputStream;
75    import java.io.IOException;
76    import java.io.InputStream;
77    import java.io.OutputStream;
78    import java.io.Reader;
79    import java.util.ArrayList;
80    import java.util.Arrays;
81    import java.util.HashMap;
82    import java.util.List;
83    import java.util.Map;
84    import java.util.concurrent.Callable;
85    import java.util.concurrent.ExecutorService;
86    import java.util.concurrent.Executors;
87    import java.util.concurrent.Future;
88    import java.util.concurrent.TimeUnit;
89   
90    import javax.imageio.ImageIO;
91    import javax.jcr.Node;
92    import javax.jcr.RepositoryException;
93    import javax.jcr.Session;
94    import javax.servlet.http.HttpServletRequest;
95   
96    import org.apache.commons.io.IOUtils;
97    import org.apache.jackrabbit.commons.predicate.Predicates;
98    import org.junit.Before;
99    import org.junit.Ignore;
100    import org.junit.Test;
101   
102    /**
103    * Tests for {@link info.magnolia.imaging.caching.CachingImageStreamer}.
104    *
105    * TODO : this is sort of a load test and uses a real (in memory) repository.
106    * TODO : cleanup.
107    */
 
108    public class CachingImageStreamerRepositoryTest extends AbstractRepositoryTestCase {
109    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CachingImageStreamerRepositoryTest.class);
110   
 
111  2 toggle @Override
112    @Before
113    public void setUp() throws Exception {
114    // this used to set autostart to false, but I'm not sure why.
115    // It seems fine as is.
116   
117  2 super.setUp();
118   
119    // Now replace JcrSessionStrategy instances (current ctx and system ctx) with a wrapper than ensures we only save once, for the purpose of these tests.
120  2 final MockContext systemContext = (MockContext) MgnlContext.getSystemContext();
121  2 systemContext.setRepositoryStrategy(new DelegatingSessionStrategy(systemContext.getRepositoryStrategy()));
122   
123  2 final MockContext regularContext = (MockContext) MgnlContext.getInstance();
124  2 regularContext.setRepositoryStrategy(new DelegatingSessionStrategy(regularContext.getRepositoryStrategy()));
125   
126  2 final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
127  2 if (!ge.isHeadless()) {
128  0 log.warn("This test should run in headless mode as the server will likely run headless too!!!!!");
129    }
130    }
131   
 
132  1 toggle @Test
133    public void testRequestForSimilarUncachedImageOnlyGeneratesItOnce() throws Exception {
134  1 final HierarchyManager srcHM = MgnlContext.getHierarchyManager("website");
135  1 final String srcPath = "/foo/bar";
136  1 ContentUtil.createPath(srcHM, srcPath);
137   
138    // ParameterProvider for tests - return a new instance of the same node everytime
139    // if we'd return the same src instance everytime, the purpose of this test would be null
140  1 final ParameterProviderFactory<Object, Content> ppf = new TestParameterProviderFactory(srcHM, srcPath);
141   
142  1 final OutputFormat png = new OutputFormat();
143  1 png.setFormatName("png");
144   
145  1 final BufferedImage dummyImg = ImageIO.read(getClass().getResourceAsStream("/funnel.gif"));
146  1 assertNotNull("Couldn't load dummy test image", dummyImg);
147   
148  1 final ImageGenerator<ParameterProvider<Content>> generator = mock(ImageGenerator.class);
149  1 when(generator.getParameterProviderFactory()).thenReturn(ppf);
150  1 when(generator.getName()).thenReturn("test");
151  1 when(generator.getOutputFormat(isA(ParameterProvider.class))).thenReturn(png);
152   
153    // aaaaand finally, here's the real reason for this test !
154  1 when(generator.generate(isA(ParameterProvider.class))).thenReturn(dummyImg);
155   
156    // yeah, we're using a "fake" workspace for the image cache, to avoid having to setup a custom one in this test
157  1 final HierarchyManager hm = MgnlContext.getHierarchyManager("config");
158  1 final ImageStreamer streamer = new CachingImageStreamer(hm, ppf.getCachingStrategy(), new DefaultImageStreamer());
159   
160    // Generator instances will always be the same (including paramProvFac)
161    // since they are instantiated with the module config and c2b.
162    // ParamProv is a new instance every time.
163    // streamer can (must) be the same - once single HM, one cache.
164   
165    // thread pool of 10, launching 8 requests, can we hit some concurrency please ?
166  1 final ExecutorService executor = Executors.newFixedThreadPool(10);
167  1 final ByteArrayOutputStream[] outs = new ByteArrayOutputStream[8];
168  1 final Future[] futures = new Future[8];
169  9 for (int i = 0; i < outs.length; i++) {
170  8 outs[i] = new ByteArrayOutputStream();
171  8 futures[i] = executor.submit(new TestJob(generator, streamer, outs[i]));
172    }
173  1 executor.shutdown();
174  1 executor.awaitTermination(30, TimeUnit.SECONDS);
175   
176  1 for (Future<?> future : futures) {
177  8 assertTrue(future.isDone());
178  8 assertFalse(future.isCancelled());
179    // ignore the results of TestJob - all we care about is if an exception was thrown
180    // and if there was any, it is kept in Future until we call Future.get()
181  8 future.get();
182    }
183   
184  1 final NodeData cachedNodeData = hm.getNodeData("/test/website/foo/bar/generated-image");
185    // update node meta data
186  1 Content cachedNode = hm.getContent("/test/website/foo/bar");
187  1 cachedNode.getMetaData().setModificationDate();
188  1 cachedNode.save();
189  1 final InputStream res = cachedNodeData.getStream();
190  1 final ByteArrayOutputStream cachedOut = new ByteArrayOutputStream();
191  1 IOUtils.copy(res, cachedOut);
192   
193    // assert all outs are the same
194  8 for (int i = 1; i < outs.length; i++) {
195    // TODO assert they're all equals byte to byte to the source? or in size? can't as-is since we convert...
196  7 final byte[] a = outs[i - 1].toByteArray();
197  7 final byte[] b = outs[i].toByteArray();
198  7 assertTrue(a.length > 0);
199  7 assertEquals("Different sizes (" + Math.abs(a.length - b.length) + " bytes diff.) with i=" + i, a.length, b.length);
200  7 assertTrue("not equals for outs/" + i, Arrays.equals(a, b));
201  7 outs[i - 1] = null; // cleanup all those byte[], or we'll soon run out of memory
202    }
203  1 assertTrue("failed comparing last thread's result with what we got from hierarchyManager",
204    Arrays.equals(outs[outs.length - 1].toByteArray(), cachedOut.toByteArray()));
205  1 outs[outs.length - 1] = null;
206   
207    // now start again another bunch of requests... they should ALL get their results from the cache
208  1 final ExecutorService executor2 = Executors.newFixedThreadPool(10);
209  1 final ByteArrayOutputStream[] outs2 = new ByteArrayOutputStream[8];
210  1 final Future[] futures2 = new Future[8];
211  9 for (int i = 0; i < outs2.length; i++) {
212  8 outs2[i] = new ByteArrayOutputStream();
213  8 futures2[i] = executor2.submit(new TestJob(generator, streamer, outs2[i]));
214    }
215  1 executor2.shutdown();
216  1 executor2.awaitTermination(30, TimeUnit.SECONDS);
217   
218  1 for (Future<?> future : futures2) {
219  8 assertTrue(future.isDone());
220  8 assertFalse(future.isCancelled());
221    // ignore the results of TestJob - all we care about is if an exception was thrown
222    // and if there was any, it is kept in Future until we call Future.get()
223  8 future.get();
224    }
225   
226  1 final NodeData cachedNodeData2 = hm.getNodeData("/test/website/foo/bar/generated-image");
227  1 final InputStream res2 = cachedNodeData2.getStream();
228  1 final ByteArrayOutputStream cachedOut2 = new ByteArrayOutputStream();
229  1 IOUtils.copy(res2, cachedOut2);
230   
231    // assert all outs are the same
232  8 for (int i = 1; i < outs2.length; i++) {
233    // TODO assert they're all equals byte to byte to the source? or in size? can't as-is since we re-save..
234  7 final byte[] a = outs2[i - 1].toByteArray();
235  7 final byte[] b = outs2[i].toByteArray();
236  7 assertTrue(a.length > 0);
237  7 assertEquals("Different sizes (" + Math.abs(a.length - b.length) + " bytes diff.) with i=" + i, a.length, b.length);
238  7 assertTrue("not equals for outs2/" + i, Arrays.equals(a, b));
239  7 outs2[i - 1] = null;
240    }
241  1 assertTrue("failed comparing last thread's result with what we got from hierarchyManager",
242    Arrays.equals(outs2[outs2.length - 1].toByteArray(), cachedOut2.toByteArray()));
243   
244  1 outs2[outs2.length - 1] = null;
245    }
246   
 
247  1 toggle @Test
248    public void testGenerateAndStoreIsDoneUnderSystemContext() throws RepositoryException, IOException, ImagingException {
249    // GIVEN
250  1 final HierarchyManager srcHM = MgnlContext.getHierarchyManager("website");
251  1 final String srcPath = "/foo/bar";
252  1 ContentUtil.createPath(srcHM, srcPath);
253   
254    // ParameterProvider for tests - return a new instance of the same node everytime
255    // if we'd return the same src instance everytime, the purpose of this test would be null
256  1 final ParameterProviderFactory<Object, Content> ppf = new TestParameterProviderFactory(srcHM, srcPath);
257   
258  1 final OutputFormat png = new OutputFormat();
259  1 png.setFormatName("png");
260   
261  1 final BufferedImage dummyImg = ImageIO.read(getClass().getResourceAsStream("/funnel.gif"));
262  1 assertNotNull("Couldn't load dummy test image", dummyImg);
263   
264  1 final ImageGenerator<ParameterProvider<Content>> generator = mock(ImageGenerator.class);
265  1 when(generator.getParameterProviderFactory()).thenReturn(ppf);
266  1 when(generator.getName()).thenReturn("test");
267  1 when(generator.getOutputFormat(isA(ParameterProvider.class))).thenReturn(png);
268   
269  1 when(generator.generate(isA(ParameterProvider.class))).thenReturn(dummyImg);
270   
271    // yeah, we're using a "fake" workspace for the image cache, to avoid having to setup a custom one in this test
272  1 final HierarchyManager hm = MgnlContext.getHierarchyManager("config");
273   
274  1 final CachingImageStreamer streamer = new CachingImageStreamer(hm, ppf.getCachingStrategy(), new DefaultImageStreamer());
275   
276    // WHEN
277  1 streamer.generateAndStore(generator, generator.getParameterProviderFactory().newParameterProviderFor(null));
278   
279    // THEN
280  1 final Session systemSession = MgnlContext.getSystemContext().getJCRSession("config");
281  1 final Session session = MgnlContext.getJCRSession("config");
282  1 assertTrue(systemSession instanceof SingleSaveSessionWrapper);
283  1 assertTrue(session instanceof SingleSaveSessionWrapper);
284  1 assertTrue("should have saved in system session", ((SingleSaveSessionWrapper) systemSession).saved);
285  1 assertFalse("should not have saved in regular session", ((SingleSaveSessionWrapper) session).saved);
286    }
287   
 
288  0 toggle @Test
289    @Ignore
290    public void shouldStoreNothingWhenUsingNullCachingStrategy() throws IOException, ImagingException, RepositoryException {
291    // GIVEN
292  0 final HttpServletRequest req = mock(HttpServletRequest.class);
293  0 when(req.getRequestURI()).thenReturn("dummyUri");
294   
295  0 final ParameterProviderFactory<HttpServletRequest, String> ppf = new ParameterProviderFactory<HttpServletRequest, String>() {
 
296  0 toggle @Override
297    public ParameterProvider<String> newParameterProviderFor(HttpServletRequest request) {
298  0 return new StringParameterProvider(request.getRequestURI());
299    }
300   
 
301    toggle @Override
302    public CachingStrategy<String> getCachingStrategy() {
303    return new NullCachingStrategy<String>();
304    }
305    };
306   
307  0 final OutputFormat png = new OutputFormat();
308  0 png.setFormatName("png");
309   
310  0 final BufferedImage dummyImg = ImageIO.read(getClass().getResourceAsStream("/funnel.gif"));
311  0 assertNotNull("Couldn't load dummy test image", dummyImg);
312   
313  0 final ImageGenerator<ParameterProvider<String>> generator = mock(ImageGenerator.class);
314  0 when(generator.getParameterProviderFactory()).thenReturn(ppf);
315  0 when(generator.getName()).thenReturn("test");
316  0 when(generator.getOutputFormat(isA(ParameterProvider.class))).thenReturn(png);
317  0 when(generator.generate(isA(ParameterProvider.class))).thenReturn(dummyImg);
318   
319    // yeah, we're using a "fake" workspace for the image cache, to avoid having to setup a custom one in this test
320  0 final HierarchyManager hm = MgnlContext.getHierarchyManager("config");
321  0 final CachingImageStreamer streamer = new CachingImageStreamer(hm, ppf.getCachingStrategy(), new DefaultImageStreamer());
322   
323    // WHEN
324  0 final ByteArrayOutputStream out = new ByteArrayOutputStream();
325  0 streamer.serveImage(generator, ppf.newParameterProviderFor(req), out);
326   
327    // THEN
328    // Ensure we're still using the NCS
329  0 assertThat(ppf.getCachingStrategy(), instanceOf(NullCachingStrategy.class));
330   
331    // The image was served:
332  0 assertThat(out.size(), greaterThan(0));
333   
334    // Check nothing was stored
335  0 final Session session = MgnlContext.getJCRSession("config");
336  0 final Node root = session.getRootNode();
337    // Collect all nodes except /jcr:system node
338  0 final Iterable<Node> nodes = NodeUtil.collectAllChildren(root, Predicates.not(new NodeTypePredicate("rep:system")));
339  0 assertThat(nodes, emptyIterable());
340    }
341   
342    /**
343    * This test is not executed by default - too long !
344    * Used to reproduce the "session already closed issue", see MGNLIMG-59.
345    * Set the "expiration" property of the jobs map in CachingImageStreamer to a longer value
346    * to have more chances of reproducing the problem.
347    */
 
348  0 toggle @Ignore
349    @Test
350    public void testConcurrencyAndJCRSessions() throws Exception {
351  0 final HierarchyManager srcHM = MgnlContext.getHierarchyManager("website");
352  0 final String srcPath = "/foo/bar";
353  0 ContentUtil.createPath(srcHM, srcPath);
354   
355    // ParameterProvider for tests - return a new instance of the same node everytime
356    // if we'd return the same src instance everytime, the purpose of this test would be null
357  0 final ParameterProviderFactory<Object, Content> ppf = new TestParameterProviderFactory(srcHM, srcPath);
358   
359  0 final OutputFormat png = new OutputFormat();
360  0 png.setFormatName("png");
361   
362  0 final ImageOperationChain<ParameterProvider<Content>> generator = new ImageOperationChain<ParameterProvider<Content>>();
363  0 final URLImageLoader<ParameterProvider<Content>> load = new URLImageLoader<ParameterProvider<Content>>();
364  0 load.setUrl(getClass().getResource("/funnel.gif").toExternalForm());
365  0 generator.addOperation(load);
366  0 generator.setOutputFormat(png);
367  0 generator.setName("foo blob bar");
368  0 generator.setParameterProviderFactory(ppf);
369   
370    // yeah, we're using a "fake" workspace for the image cache, to avoid having to setup a custom one in this test
371  0 final HierarchyManager hm = MgnlContext.getHierarchyManager("config");
372   
373  0 final ImageStreamer streamer = new CachingImageStreamer(hm, ppf.getCachingStrategy(), new DefaultImageStreamer());
374   
375    // thread pool of 10, launching 8 requests, can we hit some concurrency please ?
376  0 final ExecutorService executor = Executors.newFixedThreadPool(10);
377  0 final ByteArrayOutputStream[] outs = new ByteArrayOutputStream[8];
378  0 final Future[] futures = new Future[8];
379  0 for (int i = 0; i < outs.length; i++) {
380  0 final int ii = i;
381  0 outs[i] = new ByteArrayOutputStream();
382  0 futures[i] = executor.submit(new Runnable() {
 
383  0 toggle @Override
384    public void run() {
385  0 final ParameterProvider p = generator.getParameterProviderFactory().newParameterProviderFor(null);
386  0 try {
387  0 streamer.serveImage(generator, p, outs[ii]);
388    } catch (Exception e) {
389  0 throw new RuntimeException(e); // TODO
390    }
391    }
392    });
393    }
394  0 executor.shutdown();
395  0 executor.awaitTermination(30, TimeUnit.SECONDS);
396   
397  0 for (Future<?> future : futures) {
398  0 assertTrue(future.isDone());
399  0 assertFalse(future.isCancelled());
400    // ignore the results of TestJob - but if there was an exception thrown by TestJob.call(),
401    // it is only thrown back at us when we call get() below. (so the test will fail badly if the job threw an exception)
402  0 Object ignored = future.get();
403    }
404   
405  0 shutdownRepository(true);
406   
407    // sleep for a while so that the jobs map's expiration thread can kick in !
408  0 Thread.sleep(10000);
409    }
410   
411    // just a generation job for tests
 
412    private class TestJob implements Callable<Object> {
413    private final ImageGenerator generator;
414    private final ImageStreamer streamer;
415    private final OutputStream out;
416   
 
417  16 toggle public TestJob(ImageGenerator generator, ImageStreamer streamer, final OutputStream out) {
418  16 this.generator = generator;
419  16 this.streamer = streamer;
420  16 this.out = out;
421    }
422   
 
423  16 toggle @Override
424    public Object call() throws Exception {
425  16 final ParameterProvider p = generator.getParameterProviderFactory().newParameterProviderFor(null);
426  16 streamer.serveImage(generator, p, out);
427  14 return null;
428    }
429    }
430   
431    // TODO - this is an ugly hack to workaround MAGNOLIA-2593 - we should review RepositoryTestCase
 
432  2 toggle @Override
433    protected void initDefaultImplementations() throws IOException, ModuleManagementException {
434    //MgnlTestCase clears factory before running this method, so we have to instrument factory here rather then in setUp() before calling super.setUp()
435  2 ModuleRegistry registry = mock(ModuleRegistry.class);
436  2 ComponentsTestUtil.setInstance(ModuleRegistry.class, registry);
437   
438  2 final ModuleDefinitionReader fakeReader = new ModuleDefinitionReader() {
 
439  0 toggle @Override
440    public ModuleDefinition read(Reader in) throws ModuleManagementException {
441  0 return null;
442    }
443   
 
444  0 toggle @Override
445    public Map readAll() throws ModuleManagementException {
446  0 Map m = new HashMap();
447  0 m.put("moduleDef", "dummy");
448  0 return m;
449    }
450   
 
451  0 toggle @Override
452    public ModuleDefinition readFromResource(String resourcePath) throws ModuleManagementException {
453  0 return null;
454    }
455    };
456  2 final ModuleManagerImpl fakeModuleManager = new ModuleManagerImpl(null, fakeReader) {
 
457  0 toggle @Override
458    public List loadDefinitions() throws ModuleManagementException {
459    // TODO Auto-generated method stub
460  0 return new ArrayList();
461    }
462    };
463  2 ComponentsTestUtil.setInstance(ModuleManager.class, fakeModuleManager);
464  2 super.initDefaultImplementations();
465    }
466   
467    /*
468    final IMocksControl iMocksControl = createStrictControl();
469    // can't be strict on method call order on hm, because we can't guarantee which of the 8 threads will create the node
470    iMocksControl.checkOrder(false);
471    // not exactly sure what this entails; hopefully it doesn't render the test useless...
472    iMocksControl.makeThreadSafe(true);
473    final HierarchyManager hm = iMocksControl.createMock(HierarchyManager.class);
474    final Content root = createStrictMock(Content.class);
475    final Content t = createStrictMock(Content.class);
476    final Content m = createStrictMock(Content.class);
477    final Content p = createStrictMock(Content.class);
478    final Content y = createStrictMock(Content.class);
479   
480    // first 8 request: node doesn't exist.
481    for (int i = 0; i < 8; i++) {
482    // generator name + path provided by ParameterProviderFactory
483    expect(hm.isExist("/test/my/param/yo")).andReturn(false);
484    }
485   
486    // one of these 8 threads creates the node
487    expect(hm.getRoot()).andReturn(root);
488    expect(root.hasContent("test")).andReturn(false);
489    expect(root.createContent("test", ItemType.CONTENT)).andReturn(t);
490    expect(t.hasContent("my")).andReturn(false);
491    expect(t.createContent("my", ItemType.CONTENT)).andReturn(m);
492    expect(m.hasContent("param")).andReturn(false);
493    expect(m.createContent("param", ItemType.CONTENT)).andReturn(p);
494    expect(p.hasContent("yo")).andReturn(false);
495    expect(p.createContent("yo", ItemType.CONTENT)).andReturn(y);
496    expect(y.hasNodeData("generated-image")).andReturn(false);
497   
498    // 8 more requests, the node exists in the hm
499    for (int i = 0; i < 8; i++) {
500    expect(hm.isExist("/test/my/param/yo")).andReturn(true);
501    }
502   
503    replay(hm, root, t, m, p, y);
504    */
505   
506    /**
507    * A JCRSessionStrategy which allows returning a given session instead of the regular one.
508    */
 
509    private static class DelegatingSessionStrategy implements JCRSessionStrategy {
510    private final Map<String, Session> jcrSessions = new HashMap<String, Session>();
511    private final JCRSessionStrategy delegate;
512   
 
513  4 toggle DelegatingSessionStrategy(JCRSessionStrategy delegate) {
514  4 this.delegate = delegate;
515    }
516   
 
517  22 toggle @Override
518    public Session getSession(String workspaceName) throws RepositoryException {
519  22 if (jcrSessions.containsKey(workspaceName)) {
520  16 final Session session = jcrSessions.get(workspaceName);
521  16 assertThat(session, is(instanceOf(SingleSaveSessionWrapper.class)));
522  16 return session;
523    } else {
524  6 final Session session = delegate.getSession(workspaceName);
525  6 assertThat(session, is(not(instanceOf(SingleSaveSessionWrapper.class))));
526  6 final SingleSaveSessionWrapper sessionWrapper = new SingleSaveSessionWrapper(session);
527  6 jcrSessions.put(workspaceName, sessionWrapper);
528  6 return sessionWrapper;
529    }
530    }
531   
 
532  2 toggle @Override
533    public void release() {
534  2 delegate.release();
535    }
536    }
537   
 
538    private static class TestParameterProviderFactory implements ParameterProviderFactory {
539    private final HierarchyManager srcHM;
540    private final String srcPath;
541   
 
542  2 toggle public TestParameterProviderFactory(HierarchyManager srcHM, String srcPath) {
543  2 this.srcHM = srcHM;
544  2 this.srcPath = srcPath;
545    }
546   
 
547  17 toggle @Override
548    public ParameterProvider<Content> newParameterProviderFor(Object environment) {
549  17 try {
550  17 final Content src = srcHM.getContent(srcPath);
551    // copied from ContentParameterProviderFactory
552  17 return new ContentParameterProvider(new SimpleEqualityContentWrapper(src));
553    } catch (RepositoryException e) {
554  0 throw new RuntimeException(e);
555    }
556    }
557   
 
558    toggle @Override
559    public CachingStrategy getCachingStrategy() {
560    return new ContentBasedCachingStrategy();
561    }
562    }
563   
 
564    private static class SingleSaveSessionWrapper extends DelegateSessionWrapper {
565    boolean saved = false;
566   
 
567  6 toggle public SingleSaveSessionWrapper(Session session) throws RepositoryException {
568  6 super(session);
569    }
570   
 
571  2 toggle @Override
572    public synchronized void save() throws RepositoryException {
573  2 if (saved) {
574  0 fail("save() was called more than once");
575    } else {
576  2 saved = true;
577    }
578  2 super.save();
579    }
580    }
581    }