blob: 8fa315c70400edbe0c91edfbf051b7a999e2d78c [file] [log] [blame]
/*
* 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.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.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/*"
},
configurationPid = "org.apache.sling.scripting.core.impl.ScriptCacheImpl"
)
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) {
final Runnable eventTask = () -> {
for (final ResourceChange change : list) {
this.scriptCache.removeScript( change.getPath());
}
};
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();
}
}
}