/*
* 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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import org.apache.sling.api.scripting.SlingScriptConstants;
import org.apache.sling.scripting.core.impl.helper.ProxyScriptEngineManager;
import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Component;
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.component.annotations.ReferencePolicyOption;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Component which exposes a ScriptEngineManager service.
*
*/
@Component(service = {},
reference = @Reference(
name = "ScriptEngineFactory",
service = ScriptEngineFactory.class,
policy = ReferencePolicy.DYNAMIC,
cardinality = ReferenceCardinality.MULTIPLE
)
)
public class ScriptEngineManagerFactory implements BundleListener {
private final Logger log = LoggerFactory.getLogger(ScriptEngineManagerFactory.class);
private static final String ENGINE_FACTORY_SERVICE = "META-INF/services/" + ScriptEngineFactory.class.getName();
private BundleContext bundleContext;
/**
* Event admin is optional
*/
@Reference(policy = ReferencePolicy.DYNAMIC,
policyOption = ReferencePolicyOption.GREEDY,
cardinality=ReferenceCardinality.OPTIONAL)
private volatile EventAdmin eventAdmin;
/**
* The proxy to the actual ScriptEngineManager. This proxy is actually
* registered as the ScriptEngineManager service for the lifetime of
* this factory.
*/
private final ProxyScriptEngineManager scriptEngineManager = new ProxyScriptEngineManager();
private final Set<Bundle> engineSpiBundles = new HashSet<>();
private final Map<ScriptEngineFactory, Map<Object, Object>> engineSpiServices = new HashMap<>();
private ServiceRegistration scriptEngineManagerRegistration;
/**
* Refresh the script engine manager.
*/
private void refreshScriptEngineManager() {
// create (empty) script engine manager
final ClassLoader loader = getClass().getClassLoader();
final SlingScriptEngineManager tmp = new SlingScriptEngineManager(loader);
// register script engines from bundles
synchronized (this.engineSpiBundles) {
for (final Bundle bundle : this.engineSpiBundles) {
registerFactories(tmp, bundle);
}
}
// register script engines from registered services
synchronized (this.engineSpiServices) {
for (final Map.Entry<ScriptEngineFactory, Map<Object, Object>> factory : this.engineSpiServices.entrySet()) {
registerFactory(tmp, factory.getKey(), factory.getValue());
}
}
scriptEngineManager.setDelegatee(tmp);
final List<ScriptEngineFactory> factories = tmp.getEngineFactories();
for (final ScriptEngineFactory factory : factories) {
log.info("ScriptEngine {}/{} is now handling {}, {}, {}.", new Object[]{factory.getEngineName(), factory.getEngineVersion(), factory.getExtensions(), factory.getMimeTypes(), factory.getNames()});
}
}
@SuppressWarnings("unchecked")
private void registerFactories(final SlingScriptEngineManager mgr, final Bundle bundle) {
URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
InputStream ins = null;
try {
ins = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
String line;
while ((line = reader.readLine()) != null) {
if (!line.startsWith("#") && line.trim().length() > 0) {
try {
Class<ScriptEngineFactory> clazz = (Class<ScriptEngineFactory>) bundle.loadClass(line);
ScriptEngineFactory spi = clazz.newInstance();
registerFactory(mgr, spi, null);
} catch (Throwable t) {
log.error("Cannot register ScriptEngineFactory " + line, t);
}
}
}
} catch (IOException ioe) {
// ignore
} finally {
if (ins != null) {
try {
ins.close();
} catch (IOException ioe) {
}
}
}
}
private void registerFactory(final SlingScriptEngineManager mgr, final ScriptEngineFactory factory, final Map<Object, Object> props) {
log.info("Adding ScriptEngine {}/{} for language {}/{}.", new Object[]{factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), factory.getLanguageVersion()});
mgr.registerScriptEngineFactory(factory, props);
}
// ---------- BundleListener interface -------------------------------------
@Override
public void bundleChanged(BundleEvent event) {
if (event.getType() == BundleEvent.STARTED
&& event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
synchronized (this.engineSpiBundles) {
this.engineSpiBundles.add(event.getBundle());
}
this.refreshScriptEngineManager();
} else if (event.getType() == BundleEvent.STOPPED) {
boolean refresh;
synchronized (this.engineSpiBundles) {
refresh = this.engineSpiBundles.remove(event.getBundle());
}
if (refresh) {
this.refreshScriptEngineManager();
}
}
}
// ---------- SCR integration ----------------------------------------------
protected void activate(ComponentContext context) {
this.bundleContext = context.getBundleContext();
this.bundleContext.addBundleListener(this);
Bundle[] bundles = this.bundleContext.getBundles();
synchronized (this.engineSpiBundles) {
for (Bundle bundle : bundles) {
if (bundle.getState() == Bundle.ACTIVE
&& bundle.getEntry(ENGINE_FACTORY_SERVICE) != null) {
this.engineSpiBundles.add(bundle);
}
}
}
// create a script engine manager
this.refreshScriptEngineManager();
scriptEngineManagerRegistration = this.bundleContext.registerService(
new String[] { ScriptEngineManager.class.getName(),
SlingScriptEngineManager.class.getName() },
scriptEngineManager, new Hashtable<String, Object>());
org.apache.sling.scripting.core.impl.ScriptEngineConsolePlugin.initPlugin(context.getBundleContext(), this);
}
protected void deactivate(ComponentContext context) {
org.apache.sling.scripting.core.impl.ScriptEngineConsolePlugin.destroyPlugin();
context.getBundleContext().removeBundleListener(this);
if (scriptEngineManagerRegistration != null) {
scriptEngineManagerRegistration.unregister();
scriptEngineManagerRegistration = null;
}
synchronized ( this ) {
this.engineSpiBundles.clear();
this.engineSpiServices.clear();
}
scriptEngineManager.setDelegatee(null);
this.bundleContext = null;
}
protected void bindScriptEngineFactory(final ScriptEngineFactory scriptEngineFactory, final Map<Object, Object> props) {
if (scriptEngineFactory != null) {
synchronized ( this.engineSpiServices) {
this.engineSpiServices.put(scriptEngineFactory, props);
}
this.refreshScriptEngineManager();
// send event
postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_ADDED, scriptEngineFactory);
}
}
protected void unbindScriptEngineFactory(final ScriptEngineFactory scriptEngineFactory) {
boolean refresh;
synchronized (this.engineSpiServices) {
refresh = this.engineSpiServices.remove(scriptEngineFactory) != null;
}
if (refresh) {
this.refreshScriptEngineManager();
}
// send event
postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_REMOVED, scriptEngineFactory);
}
private String[] toArray(final List<String> list) {
return list.toArray(new String[list.size()]);
}
/**
* Post a notification with the EventAdmin
*/
private void postEvent(final String topic, final ScriptEngineFactory scriptEngineFactory) {
final EventAdmin localEA = this.eventAdmin;
if (localEA != null) {
final Dictionary<String, Object> props = new Hashtable<>();
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_NAME, scriptEngineFactory.getEngineName());
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION, scriptEngineFactory.getEngineVersion());
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS, toArray(scriptEngineFactory.getExtensions()));
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME, scriptEngineFactory.getLanguageName());
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION, scriptEngineFactory.getLanguageVersion());
props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES, toArray(scriptEngineFactory.getMimeTypes()));
localEA.postEvent(new Event(topic, props));
}
}
/**
* Get the script engine manager.
* Refresh the manager if changes occurred.
*/
ScriptEngineManager getScriptEngineManager() {
return this.scriptEngineManager;
}
}