Author: radu Date: Wed Oct 26 12:38:50 2016 New Revision: 1766676 URL: http://svn.apache.org/viewvc?rev=1766676&view=rev Log: SLING-6165 - Expose a service for Sling Scripting that provides request-scoped Resource Resolvers for scripting dependencies
* made resolver initialisation lazy * extended tests for concurrent access scenarios Modified: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImpl.java sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImplTest.java Modified: sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImpl.java?rev=1766676&r1=1766675&r2=1766676&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImpl.java (original) +++ sling/trunk/bundles/scripting/core/src/main/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImpl.java Wed Oct 26 12:38:50 2016 @@ -43,7 +43,7 @@ public class ScriptingResourceResolverFa private static final Logger LOGGER = LoggerFactory.getLogger(ScriptingResourceResolverFactoryImpl.class); - private ThreadLocal<ScriptingResourceResolver> perThreadResourceResolver = new ThreadLocal<>(); + private static final ThreadLocal<ScriptingResourceResolver> perThreadResourceResolver = new ThreadLocal<>(); private boolean logStackTraceOnResolverClose; @Reference @@ -68,7 +68,23 @@ public class ScriptingResourceResolverFa @Override public ResourceResolver getRequestScopedResourceResolver() { - return perThreadResourceResolver.get(); + ScriptingResourceResolver threadResolver = perThreadResourceResolver.get(); + if (threadResolver == null) { + // no per thread; need to synchronize access + synchronized (perThreadResourceResolver) { + try { + ResourceResolver delegate = rrf.getServiceResourceResolver(null); + threadResolver = new ScriptingResourceResolver(logStackTraceOnResolverClose, delegate); + perThreadResourceResolver.set(threadResolver); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Setting per thread resource resolver for thread {}.", Thread.currentThread().getName()); + } + } catch (LoginException e) { + throw new IllegalStateException("Cannot create per thread resource resolver.", e); + } + } + } + return threadResolver; } @Override @@ -76,24 +92,13 @@ public class ScriptingResourceResolverFa try { return rrf.getServiceResourceResolver(null); } catch (LoginException e) { - LOGGER.error("Unable to retrieve a scripting resource resolver."); - throw new IllegalStateException(e); + throw new IllegalStateException("Unable to retrieve a scripting resource resolver.", e); } } @Override public void onEvent(SlingRequestEvent sre) { - if (sre.getType().equals(SlingRequestEvent.EventType.EVENT_INIT)) { - try { - ResourceResolver delegate = rrf.getServiceResourceResolver(null); - this.perThreadResourceResolver.set(new ScriptingResourceResolver(logStackTraceOnResolverClose, delegate)); - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("Setting per thread resource resolver for thread {}.", Thread.currentThread().getName()); - } - } catch (LoginException e) { - LOGGER.error("Cannot create per thread resource resolver.", e); - } - } else if (sre.getType().equals(SlingRequestEvent.EventType.EVENT_DESTROY)) { + if (sre.getType().equals(SlingRequestEvent.EventType.EVENT_DESTROY)) { ScriptingResourceResolver scriptingResourceResolver = perThreadResourceResolver.get(); if (scriptingResourceResolver != null) { scriptingResourceResolver._close(); Modified: sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImplTest.java URL: http://svn.apache.org/viewvc/sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImplTest.java?rev=1766676&r1=1766675&r2=1766676&view=diff ============================================================================== --- sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImplTest.java (original) +++ sling/trunk/bundles/scripting/core/src/test/java/org/apache/sling/scripting/core/impl/ScriptingResourceResolverFactoryImplTest.java Wed Oct 26 12:38:50 2016 @@ -29,56 +29,63 @@ import javax.servlet.ServletContext; import javax.servlet.ServletRequest; import org.apache.sling.api.request.SlingRequestEvent; +import org.apache.sling.api.resource.LoginException; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ResourceResolverFactory; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.mockito.internal.util.reflection.Whitebox; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; import static org.mockito.Mockito.*; public class ScriptingResourceResolverFactoryImplTest { - private static final int MAX_CONCURRENT_RESOLVERS = 100; + private static final int MAX_CONCURRENT_RESOLVERS = 200; + private static final int RESOLVER_REUSE_FOR_SAME_THREAD = 100; + private ScriptingResourceResolverFactoryImpl scriptingResourceResolverFactory; + private Set<ResourceResolver> delegates; + + @Before + public void setUp() throws LoginException { + delegates = new HashSet<>(); + ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); + when(rrf.getServiceResourceResolver(null)).thenAnswer(new Answer<ResourceResolver>() { + @Override + public ResourceResolver answer(InvocationOnMock invocation) throws Throwable { + ResourceResolver delegate = getMockedRR(); + delegates.add(delegate); + return delegate; + } + }); + scriptingResourceResolverFactory = new ScriptingResourceResolverFactoryImpl(); + Whitebox.setInternalState(scriptingResourceResolverFactory, "rrf", rrf); + } + + @After + public void tearDown() { + scriptingResourceResolverFactory = null; + delegates = null; + } @Test public void testGetRequestScopedResourceResolver() throws Exception { - ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); - ResourceResolver delegate = mock(ResourceResolver.class); - when(delegate.getUserID()).thenReturn("sling-scripting"); - when(rrf.getServiceResourceResolver(null)).thenReturn(delegate); - ScriptingResourceResolverFactoryImpl scriptingResourceResolverFactory = new ScriptingResourceResolverFactoryImpl(); - Whitebox.setInternalState(scriptingResourceResolverFactory, "rrf", rrf); - assertNull(scriptingResourceResolverFactory.getRequestScopedResourceResolver()); - SlingRequestEvent sre1 = new SlingRequestEvent(mock(ServletContext.class), mock(ServletRequest.class), SlingRequestEvent.EventType - .EVENT_INIT); - scriptingResourceResolverFactory.onEvent(sre1); ResourceResolver resourceResolver = scriptingResourceResolverFactory.getRequestScopedResourceResolver(); assertEquals("sling-scripting", resourceResolver.getUserID()); SlingRequestEvent sre2 = new SlingRequestEvent(mock(ServletContext.class), mock(ServletRequest.class), SlingRequestEvent.EventType .EVENT_DESTROY); scriptingResourceResolverFactory.onEvent(sre2); - assertNull(scriptingResourceResolverFactory.getRequestScopedResourceResolver()); - verify(delegate).close(); + assertEquals(1, delegates.size()); + for (ResourceResolver delegate : delegates) { + verify(delegate).close(); + } } @Test public void testGetRequestScopedResourceResolverWithThreads() throws Exception { - ResourceResolverFactory rrf = mock(ResourceResolverFactory.class); - final Set<ResourceResolver> delegates = new HashSet<>(); - when(rrf.getServiceResourceResolver(null)).thenAnswer(new Answer<ResourceResolver>() { - @Override - public ResourceResolver answer(InvocationOnMock invocation) throws Throwable { - ResourceResolver delegate = getMockedRR(); - delegates.add(delegate); - return delegate; - } - }); - final ScriptingResourceResolverFactoryImpl scriptingResourceResolverFactory = new ScriptingResourceResolverFactoryImpl(); - Whitebox.setInternalState(scriptingResourceResolverFactory, "rrf", rrf); Collection<Callable<ResourceResolver>> callables = new ArrayList<>(MAX_CONCURRENT_RESOLVERS); for (int i = 0; i < MAX_CONCURRENT_RESOLVERS; i++) { callables.add(createCallable(scriptingResourceResolverFactory)); @@ -89,8 +96,8 @@ public class ScriptingResourceResolverFa for (Future<ResourceResolver> future : futures) { resolvers.add(future.get()); } - assertEquals(MAX_CONCURRENT_RESOLVERS, resolvers.size()); - assertEquals(MAX_CONCURRENT_RESOLVERS, delegates.size()); + assertEquals("The number of ScriptingResourceResolvers is not what we expected.", MAX_CONCURRENT_RESOLVERS, resolvers.size()); + assertEquals("The number of delegate resource resolvers is not what we expected.", MAX_CONCURRENT_RESOLVERS, delegates.size()); for (ResourceResolver delegate : delegates) { verify(delegate).close(); } @@ -100,16 +107,16 @@ public class ScriptingResourceResolverFa private Callable<ResourceResolver> createCallable(final ScriptingResourceResolverFactoryImpl scriptingResourceResolverFactory) { return new Callable<ResourceResolver>() { @Override - public ResourceResolver call() throws Exception { - try { - scriptingResourceResolverFactory.onEvent(new SlingRequestEvent(mock(ServletContext.class), mock(ServletRequest.class), - SlingRequestEvent.EventType.EVENT_INIT)); - return scriptingResourceResolverFactory.getRequestScopedResourceResolver(); - } finally { - scriptingResourceResolverFactory.onEvent(new SlingRequestEvent(mock(ServletContext.class), mock(ServletRequest.class), - SlingRequestEvent.EventType.EVENT_DESTROY)); - assertNull(scriptingResourceResolverFactory.getRequestScopedResourceResolver()); + public ResourceResolver call() { + ResourceResolver resourceResolver = scriptingResourceResolverFactory.getRequestScopedResourceResolver(); + for (int i = 0; i < RESOLVER_REUSE_FOR_SAME_THREAD; i++) { + ResourceResolver subsequentResolver = scriptingResourceResolverFactory.getRequestScopedResourceResolver(); + assertEquals("Expected that subsequent calls to ScriptingResourceResolverFactory#getRequestScopedResourceResolver() " + + "from the same thread will not create additional resolvers.", resourceResolver, subsequentResolver); } + scriptingResourceResolverFactory.onEvent(new SlingRequestEvent(mock(ServletContext.class), mock(ServletRequest.class), + SlingRequestEvent.EventType.EVENT_DESTROY)); + return resourceResolver; } }; }