/* * 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; } }