SLING-7536 : Refactor implementation
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
index e78ce68..842d144 100644
--- a/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/SlingServletResolver.java
@@ -41,12 +41,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.management.NotCompliantMBeanException;
-import javax.management.StandardMBean;
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
@@ -70,9 +65,6 @@
import org.apache.sling.api.resource.ResourceResolverFactory;
import org.apache.sling.api.resource.ResourceUtil;
import org.apache.sling.api.resource.SyntheticResource;
-import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
-import org.apache.sling.api.resource.observation.ResourceChange;
-import org.apache.sling.api.resource.observation.ResourceChangeListener;
import org.apache.sling.api.scripting.SlingScript;
import org.apache.sling.api.servlets.OptingServlet;
import org.apache.sling.api.servlets.ServletResolver;
@@ -85,9 +77,9 @@
import org.apache.sling.servlets.resolver.internal.helper.NamedScriptResourceCollector;
import org.apache.sling.servlets.resolver.internal.helper.ResourceCollector;
import org.apache.sling.servlets.resolver.internal.helper.SlingServletConfig;
+import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
import org.apache.sling.servlets.resolver.internal.resource.ServletResourceProvider;
import org.apache.sling.servlets.resolver.internal.resource.ServletResourceProviderFactory;
-import org.apache.sling.servlets.resolver.jmx.SlingServletResolverCacheMBean;
import org.apache.sling.spi.resource.provider.ResourceProvider;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
@@ -100,8 +92,6 @@
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventHandler;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
@@ -116,7 +106,7 @@
*
*/
@Component(name = SlingServletResolver.Config.PID,
- service = { ServletResolver.class,ErrorHandler.class, SlingRequestListener.class },
+ service = { ServletResolver.class, ErrorHandler.class, SlingRequestListener.class },
property = {
Constants.SERVICE_DESCRIPTION + "=Apache Sling Servlet Resolver and Error Handler",
Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
@@ -125,9 +115,7 @@
public class SlingServletResolver
implements ServletResolver,
SlingRequestListener,
- ErrorHandler,
- EventHandler,
- ResourceChangeListener,ExternalResourceChangeListener {
+ ErrorHandler {
@ObjectClassDefinition(name = "Apache Sling Servlet/Script Resolver and Error Handler",
description= "The Sling Servlet and Script Resolver has "+
@@ -190,7 +178,7 @@
private ServiceUserMapped consoleServiceUserMapped;
@Reference
- private ScriptEngineManager scriptEngineManager;
+ private ResolutionCache resolutionCache;
private List<String> scriptEnginesExtensions = new ArrayList<>();
@@ -213,18 +201,6 @@
// a request. This field is set on demand by getDefaultErrorServlet()
private Servlet fallbackErrorServlet;
- /** The script resolution cache. */
- private volatile Map<AbstractResourceCollector, Servlet> cache;
-
- /** The cache size. */
- private int cacheSize;
-
- /** Flag to log warning if cache size exceed only once. */
- private volatile boolean logCacheSizeWarning;
-
- /** Registration as event handler. */
- private ServiceRegistration<?> eventHandlerReg;
-
/**
* The allowed execution paths.
*/
@@ -500,8 +476,6 @@
private final ThreadLocal<ResourceResolver> perThreadScriptResolver = new ThreadLocal<>();
- private ServiceRegistration<SlingServletResolverCacheMBean> mbeanRegistration;
-
/**
* @see org.apache.sling.api.request.SlingRequestListener#onEvent(org.apache.sling.api.request.SlingRequestEvent)
*/
@@ -608,8 +582,8 @@
final SlingHttpServletRequest request,
final ResourceResolver resolver) {
// use local variable to avoid race condition with activate
- final Map<AbstractResourceCollector, Servlet> localCache = this.cache;
- final Servlet scriptServlet = (localCache != null ? localCache.get(locationUtil) : null);
+ final ResolutionCache localCache = this.resolutionCache;
+ final Servlet scriptServlet = localCache.get(locationUtil);
if (scriptServlet != null) {
if ( LOGGER.isDebugEnabled() ) {
LOGGER.debug("Using cached servlet {}", RequestUtil.getServletName(scriptServlet));
@@ -639,14 +613,8 @@
final boolean isOptingServlet = candidate instanceof OptingServlet;
boolean servletAcceptsRequest = !isOptingServlet || (request != null && ((OptingServlet) candidate).accepts(request));
if (servletAcceptsRequest) {
- if (!hasOptingServlet && !isOptingServlet && localCache != null) {
- if ( localCache.size() < this.cacheSize ) {
- localCache.put(locationUtil, candidate);
- } else if ( this.logCacheSizeWarning ) {
- this.logCacheSizeWarning = false;
- LOGGER.warn("Script cache has reached its limit of {}. You might want to increase the cache size for the servlet resolver.",
- this.cacheSize);
- }
+ if (!hasOptingServlet && !isOptingServlet ) {
+ localCache.put(locationUtil, candidate);
}
LOGGER.debug("Using servlet provided by candidate resource {}", candidateResource.getPath());
return candidate;
@@ -785,57 +753,10 @@
this.executionPaths = AbstractResourceCollector.getExecutionPaths(config.servletresolver_paths());
this.defaultExtensions = config.servletresolver_defaultExtensions();
- // create cache - if a cache size is configured
- this.cacheSize = config.servletresolver_cacheSize();
- if (this.cacheSize > 5) {
- this.cache = new ConcurrentHashMap<>(cacheSize);
- this.logCacheSizeWarning = true;
- } else {
- this.cacheSize = 0;
- }
-
// setup default servlet
this.getDefaultServlet();
- // and finally register as event listener if we need to flush the cache
- if ( this.cache != null ) {
-
- final Dictionary<String, Object> props = new Hashtable<>();
- props.put("event.topics", new String[] {"javax/script/ScriptEngineFactory/*",
- "org/apache/sling/api/adapter/AdapterFactory/*","org/apache/sling/scripting/core/BindingsValuesProvider/*" });
- props.put(ResourceChangeListener.PATHS, "/");
- props.put("service.description", "Apache Sling Servlet Resolver and Error Handler");
- props.put("service.vendor","The Apache Software Foundation");
-
- this.eventHandlerReg = context
- .registerService(new String[] {ResourceChangeListener.class.getName(), EventHandler.class.getName()}, this, props);
- }
-
this.plugin = new ServletResolverWebConsolePlugin(context);
- if ( this.cache != null ) {
- try {
- Dictionary<String, String> mbeanProps = new Hashtable<>();
- mbeanProps.put("jmx.objectname", "org.apache.sling:type=servletResolver,service=SlingServletResolverCache");
-
- ServletResolverCacheMBeanImpl mbean = new ServletResolverCacheMBeanImpl();
- mbeanRegistration = context.registerService(SlingServletResolverCacheMBean.class, mbean, mbeanProps);
- } catch (Throwable t) {
- LOGGER.debug("Unable to register mbean");
- }
- }
- updateScriptEngineExtensions();
- }
-
- private void updateScriptEngineExtensions() {
- final ScriptEngineManager localScriptEngineManager = scriptEngineManager;
- // use local variable to avoid racing with deactivate
- if ( localScriptEngineManager != null ) {
- final List<String> scriptEnginesExtensions = new ArrayList<>();
- for (ScriptEngineFactory factory : localScriptEngineManager.getEngineFactories()) {
- scriptEnginesExtensions.addAll(factory.getExtensions());
- }
- this.scriptEnginesExtensions = Collections.unmodifiableList(scriptEnginesExtensions);
- }
}
/**
@@ -850,12 +771,6 @@
this.plugin.dispose();
}
- // unregister event handler
- if (this.eventHandlerReg != null) {
- this.eventHandlerReg.unregister();
- this.eventHandlerReg = null;
- }
-
// Copy the list of servlets first, to minimize the need for
// synchronization
final Collection<ServiceReference<Servlet>> refs;
@@ -886,13 +801,7 @@
this.sharedScriptResolver = null;
}
- this.cache = null;
this.servletResourceProviderFactory = null;
-
- if (this.mbeanRegistration != null) {
- this.mbeanRegistration.unregister();
- this.mbeanRegistration = null;
- }
}
// TODO
@@ -1050,28 +959,6 @@
}
}
- /**
- * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
- */
- @Override
- public void handleEvent(final Event event) {
- // return immediately if already deactivated
- if ( this.context == null ) {
- return;
- }
- flushCache();
- updateScriptEngineExtensions();
- }
-
- private void flushCache() {
- // use local variable to avoid racing with deactivate
- final Map<AbstractResourceCollector, Servlet> localCache = this.cache;
- if ( localCache != null ) {
- localCache.clear();
- this.logCacheSizeWarning = true;
- }
- }
-
/** The list of property names checked by {@link #getName(ServiceReference)} */
private static final String[] NAME_PROPERTIES = { SLING_SERVLET_NAME,
COMPONENT_NAME, SERVICE_PID, SERVICE_ID };
@@ -1371,54 +1258,4 @@
}
}
-
- class ServletResolverCacheMBeanImpl extends StandardMBean implements SlingServletResolverCacheMBean {
-
- ServletResolverCacheMBeanImpl() throws NotCompliantMBeanException {
- super(SlingServletResolverCacheMBean.class);
- }
-
- @Override
- public int getCacheSize() {
- // use local variable to avoid racing with deactivate
- final Map<AbstractResourceCollector, Servlet> localCache = cache;
- return localCache != null ? localCache.size() : 0;
- }
-
- @Override
- public void flushCache() {
- SlingServletResolver.this.flushCache();
- }
-
- @Override
- public int getMaximumCacheSize() {
- return cacheSize;
- }
-
- }
-
- @Override
- public void onChange(final List<ResourceChange> changes) {
- // return immediately if already deactivated
- if ( context == null ) {
- return;
- }
- boolean flushCache = false;
- for(final ResourceChange change : changes){
- // if the path of the event is a sub path of a search path
- // we flush the whole cache
- final String path = change.getPath();
- int index = 0;
- while (!flushCache && index < searchPaths.length) {
- if (path.startsWith(this.searchPaths[index])) {
- flushCache = true;
- }
- index++;
- }
- if ( flushCache ) {
- flushCache();
- break; // we can stop looping
- }
- }
- }
}
diff --git a/src/main/java/org/apache/sling/servlets/resolver/internal/resolution/ResolutionCache.java b/src/main/java/org/apache/sling/servlets/resolver/internal/resolution/ResolutionCache.java
new file mode 100644
index 0000000..2cf4034
--- /dev/null
+++ b/src/main/java/org/apache/sling/servlets/resolver/internal/resolution/ResolutionCache.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.servlets.resolver.internal.resolution;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.management.NotCompliantMBeanException;
+import javax.management.StandardMBean;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+import javax.servlet.Servlet;
+
+import org.apache.sling.api.resource.LoginException;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.resource.ResourceResolverFactory;
+import org.apache.sling.api.resource.observation.ExternalResourceChangeListener;
+import org.apache.sling.api.resource.observation.ResourceChange;
+import org.apache.sling.api.resource.observation.ResourceChangeListener;
+import org.apache.sling.serviceusermapping.ServiceUserMapped;
+import org.apache.sling.servlets.resolver.internal.SlingServletResolver;
+import org.apache.sling.servlets.resolver.internal.helper.AbstractResourceCollector;
+import org.apache.sling.servlets.resolver.jmx.SlingServletResolverCacheMBean;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Cache for script resolution
+ *
+ */
+@Component(configurationPid = SlingServletResolver.Config.PID,
+ service = {ResolutionCache.class})
+public class ResolutionCache
+ implements EventHandler, ResourceChangeListener, ExternalResourceChangeListener {
+
+ private final Logger logger = LoggerFactory.getLogger(this.getClass());
+
+ @Reference
+ private ResourceResolverFactory resourceResolverFactory;
+
+ @Reference(target="("+ServiceUserMapped.SUBSERVICENAME+"=scripts)")
+ private ServiceUserMapped scriptServiceUserMapped;
+
+ @Reference
+ private ScriptEngineManager scriptEngineManager;
+
+ private volatile List<String> scriptEnginesExtensions = Collections.emptyList();
+
+ /** The script resolution cache. */
+ private volatile Map<AbstractResourceCollector, Servlet> cache;
+
+ /** The cache size. */
+ private int cacheSize;
+
+ /** Flag to log warning if cache size exceed only once. */
+ private volatile boolean logCacheSizeWarning;
+
+ /**
+ * The search paths
+ */
+ private String[] searchPaths;
+
+ /** Registration as event handler. */
+ private volatile ServiceRegistration<EventHandler> eventHandlerRegistration;
+
+ private volatile ServiceRegistration<ResourceChangeListener> resourceListenerRegistration;
+
+ private volatile ServiceRegistration<SlingServletResolverCacheMBean> mbeanRegistration;
+
+ /**
+ * Activate this component.
+ */
+ @Activate
+ protected void activate(final BundleContext context,
+ final SlingServletResolver.Config config) throws LoginException {
+ try ( final ResourceResolver scriptRR = resourceResolverFactory.getServiceResourceResolver(Collections.singletonMap(ResourceResolverFactory.SUBSERVICE, (Object)"scripts"))) {
+ this.searchPaths = scriptRR.getSearchPath();
+ }
+
+ // create cache - if a cache size is configured
+ this.cacheSize = config.servletresolver_cacheSize();
+ if (this.cacheSize > 5) {
+ this.cache = new ConcurrentHashMap<>(cacheSize);
+ this.logCacheSizeWarning = true;
+
+ // register MBean
+ try {
+ Dictionary<String, String> mbeanProps = new Hashtable<>();
+ mbeanProps.put("jmx.objectname", "org.apache.sling:type=servletResolver,service=SlingServletResolverCache");
+
+ ServletResolverCacheMBeanImpl mbean = new ServletResolverCacheMBeanImpl();
+ mbeanRegistration = context.registerService(SlingServletResolverCacheMBean.class, mbean, mbeanProps);
+ } catch (final Throwable t) {
+ logger.debug("Unable to register mbean");
+ }
+ } else {
+ this.cacheSize = 0;
+ }
+
+ // and finally register as event listener
+ // to invalidate cache and script extensions
+ final Dictionary<String, Object> props = new Hashtable<>();
+ props.put(Constants.SERVICE_DESCRIPTION, "Apache Sling Servlet Resolver Event Handler");
+ props.put(Constants.SERVICE_VENDOR,"The Apache Software Foundation");
+
+ props.put(EventConstants.EVENT_TOPIC, new String[] {"javax/script/ScriptEngineFactory/*",
+ "org/apache/sling/api/adapter/AdapterFactory/*","org/apache/sling/scripting/core/BindingsValuesProvider/*" });
+
+ this.eventHandlerRegistration = context.registerService(EventHandler.class, this, props);
+
+ // we need a resource change listener to invalidate the cache
+ if ( this.cache != null ) {
+ props.put(ResourceChangeListener.PATHS, "/");
+ this.resourceListenerRegistration = context.registerService(ResourceChangeListener.class, this, props);
+ }
+
+ updateScriptEngineExtensions();
+ }
+
+ /**
+ * Deactivate this component.
+ */
+ @Deactivate
+ protected void deactivate() {
+ // unregister mbean
+ if ( this.mbeanRegistration != null ) {
+ this.mbeanRegistration.unregister();
+ this.mbeanRegistration = null;
+ }
+
+ // unregister event handler
+ if (this.eventHandlerRegistration != null) {
+ this.eventHandlerRegistration.unregister();
+ this.eventHandlerRegistration = null;
+ }
+
+ // unregister event handler
+ if (this.resourceListenerRegistration != null) {
+ this.resourceListenerRegistration.unregister();
+ this.resourceListenerRegistration = null;
+ }
+
+ this.cache = null;
+ }
+
+
+ public List<String> getScriptEngineExtensions() {
+ return this.scriptEnginesExtensions;
+ }
+
+ private void updateScriptEngineExtensions() {
+ final ScriptEngineManager localScriptEngineManager = scriptEngineManager;
+ // use local variable to avoid racing with deactivate
+ if ( localScriptEngineManager != null ) {
+ final List<String> scriptEnginesExtensions = new ArrayList<>();
+ for (ScriptEngineFactory factory : localScriptEngineManager.getEngineFactories()) {
+ scriptEnginesExtensions.addAll(factory.getExtensions());
+ }
+ this.scriptEnginesExtensions = Collections.unmodifiableList(scriptEnginesExtensions);
+ }
+ }
+
+ /**
+ * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
+ */
+ @Override
+ public void handleEvent(final Event event) {
+ // return immediately if already deactivated
+ if ( this.eventHandlerRegistration == null ) {
+ return;
+ }
+ flushCache();
+ updateScriptEngineExtensions();
+ }
+
+ private void flushCache() {
+ // use local variable to avoid racing with deactivate
+ final Map<AbstractResourceCollector, Servlet> localCache = this.cache;
+ if ( localCache != null ) {
+ localCache.clear();
+ this.logCacheSizeWarning = true;
+ }
+ }
+
+ @Override
+ public void onChange(final List<ResourceChange> changes) {
+ // return immediately if already deactivated
+ if ( resourceListenerRegistration == null ) {
+ return;
+ }
+ boolean flushCache = false;
+ for(final ResourceChange change : changes){
+ // if the path of the event is a sub path of a search path
+ // we flush the whole cache
+ final String path = change.getPath();
+ int index = 0;
+ while (!flushCache && index < searchPaths.length) {
+ if (path.startsWith(this.searchPaths[index])) {
+ flushCache = true;
+ break;
+ }
+ index++;
+ }
+ if ( flushCache ) {
+ flushCache();
+ break; // we can stop looping
+ }
+ }
+ }
+
+ class ServletResolverCacheMBeanImpl extends StandardMBean implements SlingServletResolverCacheMBean {
+
+ ServletResolverCacheMBeanImpl() throws NotCompliantMBeanException {
+ super(SlingServletResolverCacheMBean.class);
+ }
+
+ @Override
+ public int getCacheSize() {
+ // use local variable to avoid racing with deactivate
+ final Map<AbstractResourceCollector, Servlet> localCache = cache;
+ return localCache != null ? localCache.size() : 0;
+ }
+
+ @Override
+ public void flushCache() {
+ ResolutionCache.this.flushCache();
+ }
+
+ @Override
+ public int getMaximumCacheSize() {
+ return cacheSize;
+ }
+
+ }
+
+ public Servlet get(final AbstractResourceCollector context) {
+ final Map<AbstractResourceCollector, Servlet> localCache = this.cache;
+ if ( localCache != null ) {
+ return localCache.get(context);
+ }
+ return null;
+ }
+
+ public void put(final AbstractResourceCollector context, final Servlet candidate) {
+ final Map<AbstractResourceCollector, Servlet> localCache = this.cache;
+ if ( localCache != null ) {
+ if ( localCache.size() < this.cacheSize ) {
+ localCache.put(context, candidate);
+ } else if ( this.logCacheSizeWarning ) {
+ this.logCacheSizeWarning = false;
+ logger.warn("Script cache has reached its limit of {}. You might want to increase the cache size for the servlet resolver.",
+ this.cacheSize);
+ }
+ }
+ }
+}
diff --git a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
index 2bf82d4..48b3732 100644
--- a/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
+++ b/src/test/java/org/apache/sling/servlets/resolver/internal/SlingServletResolverTest.java
@@ -30,7 +30,6 @@
import java.util.List;
import java.util.Map;
-import javax.script.ScriptEngineManager;
import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
@@ -46,6 +45,7 @@
import org.apache.sling.commons.testing.sling.MockResource;
import org.apache.sling.commons.testing.sling.MockResourceResolver;
import org.apache.sling.commons.testing.sling.MockSlingHttpServletRequest;
+import org.apache.sling.servlets.resolver.internal.resolution.ResolutionCache;
import org.apache.sling.servlets.resolver.internal.resource.MockServletResource;
import org.apache.sling.servlets.resolver.internal.resource.ServletResourceProvider;
import org.apache.sling.servlets.resolver.internal.resource.ServletResourceProviderFactory;
@@ -135,10 +135,10 @@
resolverField.setAccessible(true);
resolverField.set(servletResolver, factory);
- // set script engine manager
- final Field scriptEngineManagerField = resolverClass.getDeclaredField("scriptEngineManager");
- scriptEngineManagerField.setAccessible(true);
- scriptEngineManagerField.set(servletResolver, Mockito.mock(ScriptEngineManager.class));
+ // set cache
+ final Field cacheField = resolverClass.getDeclaredField("resolutionCache");
+ cacheField.setAccessible(true);
+ cacheField.set(servletResolver, new ResolutionCache());
final Bundle bundle = Mockito.mock(Bundle.class);
Mockito.when(bundle.getBundleId()).thenReturn(1L);