/* | |
* Copyright 2005 The Apache Software Foundation | |
* | |
* Licensed 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.mishell; | |
import java.io.BufferedReader; | |
import java.io.IOException; | |
import java.io.InputStreamReader; | |
import java.net.URL; | |
import java.util.ArrayList; | |
import java.util.Enumeration; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import javax.script.Bindings; | |
import javax.script.ScriptEngine; | |
import javax.script.ScriptEngineFactory; | |
import javax.script.ScriptEngineManager; | |
import javax.script.SimpleBindings; | |
import org.osgi.framework.Bundle; | |
import org.osgi.framework.BundleContext; | |
/** | |
* This class acts as a delegate for all the available ScriptEngineManagers. Unluckily, the standard did not | |
* define it as an interface, so we need to extend it to allow polymorphism. However, no calls to super are used. | |
* It wraps all available ScriptEngineManagers in the OSGi ServicePlatform into a merged ScriptEngineManager. | |
* | |
* Internally, what this class does is creating ScriptEngineManagers for each bundle | |
* that contains a ScriptEngineFactory and includes a META-INF/services/javax.script.ScriptEngineFactory file. | |
* It assumes that the file contains a list of @link ScriptEngineFactory classes. For each bundle, it creates a | |
* ScriptEngineManager, then merges them. @link ScriptEngineFactory objects are wrapped | |
* into @link OSGiScriptEngineFactory objects to deal with problems of context class loader: | |
* Those scripting engines that rely on the ContextClassloader for finding resources need to use this wrapper | |
* and the @link OSGiScriptFactory. Mainly, jruby does. | |
* | |
* Note that even if no context classloader issues arose, it would still be needed to search manually for the | |
* factories and either use them directly (losing the mimeType/extension/shortName mechanisms for finding engines | |
* or manually registering them) or still use this class, which would be smarter. In the latter case, | |
* it would only be needed to remove the hack that temporarily sets the context classloader to the appropriate, | |
* bundle-related, class loader. | |
* | |
* Caveats: | |
* <ul><li> | |
* All factories are wrapped with an {@link OSGiScriptEngineFactory}. As Engines are not wrapped, | |
* calls like | |
* <code> | |
* ScriptEngineManager osgiManager=new OSGiScriptEngineManager(context);<br> | |
* ScriptEngine engine=osgiManager.getEngineByName("ruby"); | |
* ScriptEngineFactory factory=engine.getFactory() //this does not return the OSGiFactory wrapper | |
* factory.getScriptEngine(); //this might fail, as it does not use OSGiScriptEngineFactory wrapper | |
* </code> | |
* might result in unexpected errors. Future versions may wrap the ScriptEngine with a OSGiScriptEngine to solve this | |
* issue, but for the moment it is not needed. | |
* </li> | |
* | |
*/ | |
public class OSGiScriptEngineManager extends ScriptEngineManager{ | |
private Bindings bindings; | |
private Map <ScriptEngineManager, ClassLoader> classLoaders; | |
private BundleContext context; | |
public OSGiScriptEngineManager(BundleContext context){ | |
this.context=context; | |
bindings=new SimpleBindings(); | |
this.classLoaders=findManagers(context); | |
} | |
/** | |
* This method is the only one that is visible and not part of the ScriptEngineManager class. | |
* Its purpose is to find new managers that weren't available before, but keeping the globalScope bindings | |
* set. | |
* If you want to clean the bindings you can either get a fresh instance of OSGiScriptManager or | |
* setting up a new bindings object. | |
* This can be done with: | |
* <code> | |
* ScriptEngineManager manager=new OSGiScriptEngineManager(context); | |
* (...)//do stuff | |
* osgiManager=(OSGiScriptEngineManager)manager;//cast to ease reading | |
* osgiManager.reloadManagers(); | |
* | |
* manager.setBindings(new OSGiBindings());//or you can use your own bindings implementation | |
* | |
* </code> | |
* | |
*/ | |
public void reloadManagers(){ | |
this.classLoaders=findManagers(context); | |
} | |
public Object get(String key) { | |
return bindings.get(key); | |
} | |
public Bindings getBindings() { | |
return bindings; | |
} | |
public ScriptEngine getEngineByExtension(String extension) { | |
//TODO this is a hack to deal with context class loader issues | |
ScriptEngine engine=null; | |
for(ScriptEngineManager manager: classLoaders.keySet()){ | |
ClassLoader old=Thread.currentThread().getContextClassLoader(); | |
Thread.currentThread().setContextClassLoader(classLoaders.get(manager)); | |
engine=manager.getEngineByExtension(extension); | |
Thread.currentThread().setContextClassLoader(old); | |
if (engine!=null) break; | |
} | |
return engine; | |
} | |
public ScriptEngine getEngineByMimeType(String mimeType) { | |
//TODO this is a hack to deal with context class loader issues | |
ScriptEngine engine=null; | |
for(ScriptEngineManager manager: classLoaders.keySet()){ | |
ClassLoader old=Thread.currentThread().getContextClassLoader(); | |
Thread.currentThread().setContextClassLoader(classLoaders.get(manager)); | |
engine=manager.getEngineByMimeType(mimeType); | |
Thread.currentThread().setContextClassLoader(old); | |
if (engine!=null) break; | |
} | |
return engine; | |
} | |
public ScriptEngine getEngineByName(String shortName) { | |
//TODO this is a hack to deal with context class loader issues | |
for(ScriptEngineManager manager: classLoaders.keySet()){ | |
ClassLoader old=Thread.currentThread().getContextClassLoader(); | |
Thread.currentThread().setContextClassLoader(classLoaders.get(manager)); | |
ScriptEngine engine=manager.getEngineByName(shortName); | |
Thread.currentThread().setContextClassLoader(old); | |
if (engine!=null){ | |
return new OSGiScriptEngine(engine, new OSGiScriptEngineFactory(engine.getFactory(), classLoaders.get(manager))); | |
} | |
} | |
return null; | |
} | |
public List<ScriptEngineFactory> getEngineFactories() { | |
List<ScriptEngineFactory> osgiFactories=new ArrayList<ScriptEngineFactory>(); | |
for(ScriptEngineManager engineManager: classLoaders.keySet()){ | |
for (ScriptEngineFactory factory : engineManager.getEngineFactories()){ | |
osgiFactories.add(new OSGiScriptEngineFactory(factory, classLoaders.get(engineManager))); | |
} | |
} | |
return osgiFactories; | |
} | |
public void put(String key, Object value) { | |
bindings.put(key, value); | |
} | |
public void registerEngineExtension(String extension, ScriptEngineFactory factory) { | |
for(ScriptEngineManager engineManager: classLoaders.keySet()) | |
engineManager.registerEngineExtension(extension, factory); | |
} | |
public void registerEngineMimeType(String type, ScriptEngineFactory factory) { | |
for(ScriptEngineManager engineManager: classLoaders.keySet()) | |
engineManager.registerEngineMimeType(type, factory); | |
} | |
public void registerEngineName(String name, ScriptEngineFactory factory) { | |
for(ScriptEngineManager engineManager: classLoaders.keySet()) | |
engineManager.registerEngineName(name, factory); | |
} | |
/** | |
* Follows the same behavior of @link javax.script.ScriptEngineManager#setBindings(Bindings) | |
* This means that the same bindings are applied to all the underlying managers. | |
* @param bindings | |
*/ | |
public void setBindings(Bindings bindings) { | |
this.bindings=bindings; | |
for(ScriptEngineManager manager: classLoaders.keySet()){ | |
manager.setBindings(bindings); | |
} | |
} | |
private Map<ScriptEngineManager, ClassLoader> findManagers(BundleContext context) { | |
Map<ScriptEngineManager, ClassLoader> managers=new HashMap<ScriptEngineManager, ClassLoader>(); | |
try { | |
for(String factoryName: findFactoryCandidates(context)){ | |
//We do not really need the class, but we need the classloader | |
ClassLoader factoryLoader=Class.forName(factoryName).getClassLoader(); | |
ScriptEngineManager manager=new ScriptEngineManager(factoryLoader); | |
manager.setBindings(bindings); | |
managers.put(manager, factoryLoader); | |
} | |
return managers; | |
} catch (IOException ioe) { | |
throw new RuntimeException(ioe); | |
} catch (ClassNotFoundException cnfe) { | |
throw new RuntimeException(cnfe); | |
} | |
} | |
/** | |
* Iterates through all bundles to get the available @link ScriptEngineFactory classes | |
* @return the names of the available ScriptEngineFactory classes | |
* @throws IOException | |
*/ | |
private List<String> findFactoryCandidates(BundleContext context) throws IOException{ | |
Bundle[] bundles = context.getBundles(); | |
List<String> factoryCandidates = new ArrayList<String>(); | |
for (Bundle bundle : bundles) { | |
System.out.println(bundle.getSymbolicName()); | |
if(bundle.getSymbolicName().equals("system.bundle")) continue; | |
Enumeration urls = bundle.findEntries("META-INF/services", | |
"javax.script.ScriptEngineFactory", false); | |
if (urls == null) | |
continue; | |
while (urls.hasMoreElements()) { | |
URL u = (URL) urls.nextElement(); | |
BufferedReader reader = new BufferedReader( | |
new InputStreamReader(u.openStream())); | |
String line; | |
while ((line = reader.readLine()) != null) { | |
factoryCandidates.add(line.trim()); | |
} | |
} | |
} | |
return factoryCandidates; | |
} | |
} |