This is an automated email from the ASF dual-hosted git repository. cziegeler pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-core.git
The following commit(s) were added to refs/heads/master by this push: new 3249d39 SLING-12194 : Cycle between scripting services 3249d39 is described below commit 3249d3989bd0ca7f7dc5552d4ce6f4853c577425 Author: Carsten Ziegeler <cziege...@apache.org> AuthorDate: Fri Dec 8 16:03:42 2023 +0100 SLING-12194 : Cycle between scripting services --- .../core/impl/ScriptCacheConsolePlugin.java | 3 +- .../sling/scripting/core/impl/ScriptCacheImpl.java | 200 +++------------------ .../core/impl/ScriptCacheInvalidator.java | 177 ++++++++++++++++++ 3 files changed, 204 insertions(+), 176 deletions(-) diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java index 8285594..e520a20 100644 --- a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java +++ b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheConsolePlugin.java @@ -20,7 +20,6 @@ package org.apache.sling.scripting.core.impl; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -81,7 +80,7 @@ public class ScriptCacheConsolePlugin extends AbstractWebConsolePlugin { throws ServletException, IOException { if (scriptCache instanceof ScriptCacheImpl) { ScriptCacheImpl scriptCacheImpl = (ScriptCacheImpl) scriptCache; - List<String> scripts = new ArrayList<>(scriptCacheImpl.getCachedScripts()); + List<String> scripts = scriptCacheImpl.getCachedScripts(); StringBuilder sb = new StringBuilder(); sb.append("<script type='text/javascript' src='").append(RESOURCES).append("/").append(SCRIPTCACHE_JS).append("'></script>"); sb.append("<div id='cached-scripts' class='ui-widget statline'>"); diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java index e1259a9..336f2a6 100644 --- a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java +++ b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java @@ -16,60 +16,32 @@ * specific language governing permissions and limitations * under the License. ******************************************************************************/ - package org.apache.sling.scripting.core.impl; import java.lang.ref.SoftReference; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Dictionary; import java.util.HashSet; -import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeSet; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import javax.script.Compilable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineFactory; - 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.ResourceChange.ChangeType; -import org.apache.sling.api.resource.observation.ResourceChangeListener; import org.apache.sling.scripting.api.CachedScript; import org.apache.sling.scripting.api.ScriptCache; import org.apache.sling.scripting.core.impl.helper.CachingMap; -import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager; import org.apache.sling.serviceusermapping.ServiceUserMapped; -import org.jetbrains.annotations.NotNull; -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.osgi.service.metatype.annotations.Designate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component( - service = {ScriptCache.class, EventHandler.class}, - property = { - Constants.SERVICE_VENDOR + "=The Apache Software Foundation", - EventConstants.EVENT_TOPIC + "=org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/*" - } + immediate = true, // cache should be immediate + service = {ScriptCache.class} ) @Designate( ocd = ScriptCacheImplConfiguration.class @@ -78,46 +50,28 @@ import org.slf4j.LoggerFactory; * The {@code ScriptCache} stores information about {@link CompiledScript} instances evaluated by various {@link ScriptEngine}s that * implement the {@link Compilable} interface. */ -public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, ExternalResourceChangeListener, EventHandler { +public class ScriptCacheImpl implements ScriptCache { private final Logger logger = LoggerFactory.getLogger(ScriptCacheImpl.class); public static final int DEFAULT_CACHE_SIZE = 65536; - private final BundleContext bundleContext; private final Map<String, SoftReference<CachedScript>> internalMap; - private final Set<String> extensions = new TreeSet<>(); - private final String[] additionalExtensions; - - private volatile ServiceRegistration<ResourceChangeListener> resourceChangeListener; // use a static policy so that we can reconfigure the watched script files if the search paths are changed @Reference private ResourceResolverFactory rrf; - private final SlingScriptEngineManager slingScriptEngineManager; - - private final ExecutorService threadPool; private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); private final Lock readLock = rwl.readLock(); private final Lock writeLock = rwl.writeLock(); - private volatile boolean active = false; @Reference private ServiceUserMapped serviceUserMapped; @Activate - public ScriptCacheImpl(@Reference final SlingScriptEngineManager slingScriptEngineManager, - final ScriptCacheImplConfiguration configuration, - final BundleContext bundleCtx) { - this.slingScriptEngineManager = slingScriptEngineManager; - this.threadPool = Executors.newSingleThreadExecutor(); - this.bundleContext = bundleCtx; - this.additionalExtensions = configuration.org_apache_sling_scripting_cache_additional__extensions(); + public ScriptCacheImpl(final ScriptCacheImplConfiguration configuration) { this.internalMap = new CachingMap<>(configuration.org_apache_sling_scripting_cache_size()); - this.initializeExtensions(); - this.active = true; - this.configureCache(); } @Override @@ -156,143 +110,41 @@ public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, Ext } @Override - public boolean removeScript(String scriptPath) { + public boolean removeScript(final String scriptPath) { writeLock.lock(); try { - SoftReference<CachedScript> reference = internalMap.remove(scriptPath); - boolean result = reference != null; - if (result) { - logger.debug("Removed script {} from script cache.", scriptPath); - } - return result; - } finally { - writeLock.unlock(); - } - } - - @Override - public void onChange(@NotNull List<ResourceChange> list) { - for (final ResourceChange change : list) { - Runnable eventTask = () -> { - String path = change.getPath(); - writeLock.lock(); - try { - final boolean removed = internalMap.remove(path) != null; - logger.debug("Detected script change for {} - removed entry from the cache.", path); - if ( !removed && change.getType() == ChangeType.REMOVED ) { - final String prefix = path + "/"; - final Set<String> removal = new HashSet<>(); - for(final Map.Entry<String, SoftReference<CachedScript>> entry : internalMap.entrySet()) { - if ( entry.getKey().startsWith(prefix) ) { - removal.add(entry.getKey()); - } - } - for(final String key : removal) { - internalMap.remove(key); - logger.debug("Detected removal for {} - removed entry {} from the cache.", path, key); - } + boolean result = false; + if (scriptPath.endsWith("/") ) { + // prefix removal + final Set<String> removal = new HashSet<>(); + for(final Map.Entry<String, SoftReference<CachedScript>> entry : internalMap.entrySet()) { + if ( entry.getKey().startsWith(scriptPath) ) { + removal.add(entry.getKey()); } - } finally { - writeLock.unlock(); } - }; - threadPool.execute(eventTask); - } - } - - protected Set<String> getCachedScripts() { - readLock.lock(); - try { - return internalMap.keySet(); - } finally { - readLock.unlock(); - } - } - - private void configureCache() { - writeLock.lock(); - try { - if (active) { - this.clear(); - if (extensions.isEmpty()) { - if (resourceChangeListener != null) { - resourceChangeListener.unregister(); - resourceChangeListener = null; - } - } else { - final List<String> globPatterns = new ArrayList<>(extensions.size()); - for (final String extension : extensions) { - globPatterns.add("glob:**/*.".concat(extension)); - } - final String[] paths = globPatterns.toArray(new String[globPatterns.size()]); - if (resourceChangeListener != null) { - final Dictionary<String, Object> resourceChangeListenerProperties = resourceChangeListener.getReference().getProperties(); - if ( !Arrays.equals(paths, (String[])resourceChangeListenerProperties.get(ResourceChangeListener.PATHS))) { - resourceChangeListenerProperties.put(ResourceChangeListener.PATHS, paths); - resourceChangeListener.setProperties(resourceChangeListenerProperties); - } - } else { - final Dictionary<String, Object> resourceChangeListenerProperties = new Hashtable<>(); - resourceChangeListenerProperties.put(ResourceChangeListener.PATHS, paths); - resourceChangeListenerProperties.put(ResourceChangeListener.CHANGES, - new String[]{ResourceChange.ChangeType.CHANGED.name(), ResourceChange.ChangeType.REMOVED.name()}); - resourceChangeListener = - bundleContext.registerService( - ResourceChangeListener.class, - this, - resourceChangeListenerProperties - ); - } + for(final String key : removal) { + internalMap.remove(key); + logger.debug("Detected removal for {} - removed entry {} from the cache.", scriptPath, key); + result = true; + } + } else { + result = internalMap.remove(scriptPath) != null; + if (result) { + logger.debug("Removed script {} from script cache.", scriptPath); } } + return result; } finally { writeLock.unlock(); } } - @Deactivate - protected void deactivate() { - this.active = false; - writeLock.lock(); - try { - internalMap.clear(); - if (resourceChangeListener != null) { - resourceChangeListener.unregister(); - resourceChangeListener = null; - } - threadPool.shutdown(); - try { - threadPool.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException e) { - logger.warn("Unable to shutdown script cache thread in time"); - } - } finally { - writeLock.unlock(); - } - } - - private void initializeExtensions() { - for (final ScriptEngineFactory factory : slingScriptEngineManager.getEngineFactories()) { - final ScriptEngine scriptEngine = factory.getScriptEngine(); - if (scriptEngine instanceof Compilable) { - extensions.addAll(factory.getExtensions()); - } - } - if (this.additionalExtensions != null) { - extensions.addAll(Arrays.asList(this.additionalExtensions)); - } - } - - @Override - public void handleEvent(Event event) { - writeLock.lock(); + protected List<String> getCachedScripts() { + readLock.lock(); try { - this.clear(); - this.extensions.clear(); - this.initializeExtensions(); - this.configureCache(); + return new ArrayList<>(internalMap.keySet()); } finally { - writeLock.unlock(); + readLock.unlock(); } } } diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheInvalidator.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheInvalidator.java new file mode 100644 index 0000000..4961fbc --- /dev/null +++ b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheInvalidator.java @@ -0,0 +1,177 @@ +/******************************************************************************* + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.scripting.core.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import javax.script.Compilable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +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.scripting.api.ScriptCache; +import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager; +import org.jetbrains.annotations.NotNull; +import org.osgi.framework.BundleContext; +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.osgi.service.metatype.annotations.Designate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component( + immediate = true, // event handler should be immediate + service = {EventHandler.class}, + property = { + EventConstants.EVENT_TOPIC + "=org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/*" + } +) +@Designate( + ocd = ScriptCacheImplConfiguration.class +) +public class ScriptCacheInvalidator implements ResourceChangeListener, ExternalResourceChangeListener, EventHandler { + + private final Logger logger = LoggerFactory.getLogger(ScriptCacheInvalidator.class); + + private final BundleContext bundleContext; + private final Set<String> extensions = new TreeSet<>(); + private final String[] additionalExtensions; + + private volatile ServiceRegistration<ResourceChangeListener> resourceChangeListener; + + private final SlingScriptEngineManager slingScriptEngineManager; + + private final ExecutorService threadPool; + + private final ScriptCache scriptCache; + + @Activate + public ScriptCacheInvalidator(@Reference final SlingScriptEngineManager slingScriptEngineManager, + @Reference final ScriptCache scriptCache, + final ScriptCacheImplConfiguration configuration, + final BundleContext bundleCtx) { + + this.slingScriptEngineManager = slingScriptEngineManager; + this.scriptCache = scriptCache; + this.threadPool = Executors.newSingleThreadExecutor(); + this.bundleContext = bundleCtx; + this.additionalExtensions = configuration.org_apache_sling_scripting_cache_additional__extensions(); + this.handleEvent(null); + } + + @Deactivate + protected void deactivate() { + if (resourceChangeListener != null) { + resourceChangeListener.unregister(); + resourceChangeListener = null; + } + threadPool.shutdown(); + try { + threadPool.awaitTermination(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.warn("Unable to shutdown script cache thread in time"); + } + } + + @Override + public void onChange(@NotNull List<ResourceChange> list) { + for (final ResourceChange change : list) { + Runnable eventTask = () -> { + final String path = change.getPath(); + if (!this.scriptCache.removeScript(path)) { + this.scriptCache.removeScript(path.concat("/")); + } + }; + threadPool.execute(eventTask); + } + } + + private void configureListener() { + this.scriptCache.clear(); + if (extensions.isEmpty()) { + if (resourceChangeListener != null) { + resourceChangeListener.unregister(); + resourceChangeListener = null; + } + } else { + final List<String> globPatterns = new ArrayList<>(extensions.size()); + for (final String extension : extensions) { + globPatterns.add("glob:**/*.".concat(extension)); + } + final String[] paths = globPatterns.toArray(new String[globPatterns.size()]); + if (resourceChangeListener != null) { + final Dictionary<String, Object> resourceChangeListenerProperties = resourceChangeListener.getReference().getProperties(); + if ( !Arrays.equals(paths, (String[])resourceChangeListenerProperties.get(ResourceChangeListener.PATHS))) { + resourceChangeListenerProperties.put(ResourceChangeListener.PATHS, paths); + resourceChangeListener.setProperties(resourceChangeListenerProperties); + } + } else { + final Dictionary<String, Object> resourceChangeListenerProperties = new Hashtable<>(); + resourceChangeListenerProperties.put(ResourceChangeListener.PATHS, paths); + resourceChangeListenerProperties.put(ResourceChangeListener.CHANGES, + new String[]{ResourceChange.ChangeType.CHANGED.name(), ResourceChange.ChangeType.REMOVED.name()}); + resourceChangeListener = + bundleContext.registerService( + ResourceChangeListener.class, + this, + resourceChangeListenerProperties + ); + } + } + } + + private void initializeExtensions() { + this.extensions.clear(); + for (final ScriptEngineFactory factory : this.slingScriptEngineManager.getEngineFactories()) { + final ScriptEngine scriptEngine = factory.getScriptEngine(); + if (scriptEngine instanceof Compilable) { + extensions.addAll(factory.getExtensions()); + } + } + if (this.additionalExtensions != null) { + extensions.addAll(Arrays.asList(this.additionalExtensions)); + } + } + + @Override + public void handleEvent(final Event event) { + synchronized ( this.extensions ) { + this.initializeExtensions(); + this.configureListener(); + } + } +}