blob: ce335e29a6b78b3ad1e4d58f9af8d6ae043c86ad [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.felix.webconsole.plugins.scriptconsole.internal;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import org.apache.commons.io.IOUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* It is based on org.apache.sling.scripting.core.impl.ScriptEngineManagerFactory
*/
class ScriptEngineManager implements BundleListener, ServiceTrackerCustomizer
{
private static final String ENGINE_FACTORY_SERVICE = "META-INF/services/"
+ ScriptEngineFactory.class.getName();
private final Set<Bundle> engineSpiBundles = new HashSet<Bundle>();
private final Map<ServiceReference, ScriptEngineFactoryState> engineSpiServices
= new ConcurrentHashMap<ServiceReference, ScriptEngineFactoryState>();
private final Logger log;
private EngineManagerState state = new EngineManagerState();
/**
* ServiceTracker for ScriptEngineFactory
*/
private ServiceTracker scriptFactoryTracker;
private final BundleContext context;
public ScriptEngineManager(BundleContext context, Logger logger)
{
this.log = logger;
this.context = context;
this.context.addBundleListener(this);
Bundle[] bundles = this.context.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();
this.scriptFactoryTracker = new ServiceTracker(context,
ScriptEngineFactory.class.getName(), this);
this.scriptFactoryTracker.open();
}
public List<ScriptEngineFactory> getEngineFactories()
{
return state.factories;
}
public ScriptEngine getEngineByExtension(String extension) {
ScriptEngineFactory factory = state.extensionAssociations.get(extension);
if (factory == null) return null;
ScriptEngine engine = factory.getScriptEngine();
//We do not support global scope for now
//engine.setBindings(globalScope, ScriptContext.GLOBAL_SCOPE);
return engine;
}
// ---------- BundleListener interface -------------------------------------
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();
}
}
}
// ---------- ServiceTrackerCustomizer interface -------------------------------------
public Object addingService(ServiceReference reference) {
ScriptEngineFactory service = (ScriptEngineFactory) context.getService(reference);
engineSpiServices.put(reference,new ScriptEngineFactoryState(service,getServiceProperties(reference)));
refreshScriptEngineManager();
return service;
}
public void modifiedService(ServiceReference reference, Object service)
{
ScriptEngineFactoryState state = engineSpiServices.get(reference);
state.properties = getServiceProperties(reference);
refreshScriptEngineManager();
}
public void removedService(ServiceReference reference, Object service)
{
context.ungetService(reference);
engineSpiServices.remove(reference);
refreshScriptEngineManager();
}
private void refreshScriptEngineManager()
{
EngineManagerState tmp = new EngineManagerState();
// register script engines from bundles
final SortedSet<Object> extensions = new TreeSet<Object>();
synchronized (this.engineSpiBundles)
{
for (final Bundle bundle : this.engineSpiBundles)
{
extensions.addAll(registerFactories(tmp, bundle));
}
}
// register script engines from registered services
synchronized (this.engineSpiServices)
{
for (final ScriptEngineFactoryState state : this.engineSpiServices.values())
{
extensions.addAll(registerFactory(tmp, state.scriptEngineFactory,
state.properties));
}
}
synchronized (this){
this.state = tmp;
}
}
@SuppressWarnings("unchecked")
private Collection<?> registerFactories(final EngineManagerState mgr,
final Bundle bundle)
{
URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
InputStream ins = null;
final SortedSet<String> extensions = new TreeSet<String>();
try
{
ins = url.openStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
for (String className : getClassNames(reader))
{
try
{
Class<ScriptEngineFactory> clazz = bundle.loadClass(className);
ScriptEngineFactory spi = clazz.newInstance();
registerFactory(mgr, spi, null);
extensions.addAll(spi.getExtensions());
}
catch (Throwable t)
{
log.log(LogService.LOG_ERROR,
"Cannot register ScriptEngineFactory " + className, t);
}
}
}
catch (IOException ioe)
{
// ignore
}
finally
{
IOUtils.closeQuietly(ins);
}
return extensions;
}
private Collection<?> registerFactory(final EngineManagerState mgr,
final ScriptEngineFactory factory, final Map<Object, Object> props)
{
log.log(
LogService.LOG_INFO,
String.format("Adding ScriptEngine %s, %s for language %s, %s",
factory.getEngineName(), factory.getEngineVersion(),
factory.getLanguageName(), factory.getLanguageVersion()));
mgr.factories.add(factory);
mgr.factoryProperties.put(factory, props);
for (Object ext : factory.getExtensions()) {
mgr.extensionAssociations.put((String) ext, factory);
}
return factory.getExtensions();
}
public void dispose()
{
if (scriptFactoryTracker != null)
{
scriptFactoryTracker.close();
}
}
static List<String> getClassNames(BufferedReader reader) throws IOException {
List<String> classNames = new ArrayList<String>();
String line;
while ((line = reader.readLine()) != null)
{
if (!line.startsWith("#") && line.trim().length() > 0)
{
int indexOfHash = line.indexOf('#');
if (indexOfHash >= 0)
{
line = line.substring(0, indexOfHash);
}
line = line.trim();
classNames.add(line);
}
}
return classNames;
}
private static Map<Object, Object> getServiceProperties(ServiceReference reference)
{
Map<Object, Object> props = new HashMap<Object, Object>();
for (String key : reference.getPropertyKeys())
{
props.put(key, reference.getProperty(key));
}
return props;
}
private static class ScriptEngineFactoryState
{
final ScriptEngineFactory scriptEngineFactory;
Map<Object, Object> properties;
private ScriptEngineFactoryState(ScriptEngineFactory scriptEngineFactory, Map<Object, Object> properties)
{
this.scriptEngineFactory = scriptEngineFactory;
this.properties = properties;
}
}
private static class EngineManagerState
{
private final List<ScriptEngineFactory> factories = new ArrayList<ScriptEngineFactory>();
private final Map<ScriptEngineFactory, Map<Object, Object>> factoryProperties
= new HashMap<ScriptEngineFactory, Map<Object, Object>>();
private final Map<String, ScriptEngineFactory> extensionAssociations
= new HashMap<String, ScriptEngineFactory>();
}
}