From c8245e755505628ae16f032b79a0c79f4f1ba55b Mon Sep 17 00:00:00 2001 From: freepath Date: Fri, 18 Nov 2016 13:02:23 +0100 Subject: [PATCH] IDEMPIERE-3243 fixed by using classes from apache felix project. No other classes needed modifications since script-enabled classes relies on MRule --- .../src/org/adempiere/base/Core.java | 27 ++ .../adempiere/base/osgi/OSGiScriptEngine.java | 85 ++++++ .../base/osgi/OSGiScriptEngineFactory.java | 89 ++++++ .../base/osgi/OSGiScriptEngineManager.java | 263 ++++++++++++++++++ .../src/org/compiere/model/MRule.java | 15 +- 5 files changed, 473 insertions(+), 6 deletions(-) create mode 100644 org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngine.java create mode 100644 org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineFactory.java create mode 100644 org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineManager.java diff --git a/org.adempiere.base/src/org/adempiere/base/Core.java b/org.adempiere.base/src/org/adempiere/base/Core.java index 91a36a2fb9..426ae8fe6a 100644 --- a/org.adempiere.base/src/org/adempiere/base/Core.java +++ b/org.adempiere.base/src/org/adempiere/base/Core.java @@ -25,6 +25,10 @@ import java.util.ArrayList; import java.util.List; import java.util.logging.Level; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; + +import org.adempiere.base.osgi.OSGiScriptEngineManager; import org.adempiere.model.IAddressValidation; import org.adempiere.model.IShipmentProcessor; import org.adempiere.model.ITaxProvider; @@ -43,6 +47,7 @@ import org.compiere.model.StandardTaxProvider; import org.compiere.process.ProcessCall; import org.compiere.util.CLogger; import org.compiere.util.ReplenishInterface; +import org.osgi.framework.FrameworkUtil; /** * This is a facade class for the Service Locator. @@ -50,6 +55,8 @@ import org.compiere.util.ReplenishInterface; * * @author viola * @author hengsin + * @author Silvano Trinchero, www.freepath.it + *
  • IDEMPIERE-3243 added getScriptEngine to manage both registered engines and engines provided by osgi bundles */ public class Core { @@ -395,4 +402,24 @@ public class Core { return myReplenishInstance; } + + + /** Get script engine, checking classpath first, and then osgi plugins + * + * @param engineName + * @return ScriptEngine found, or null + */ + public static ScriptEngine getScriptEngine(String engineName) + { + ScriptEngineManager factory = new ScriptEngineManager(Core.class.getClassLoader()); + ScriptEngine engine = factory.getEngineByName(engineName); + + if(engine == null) + { + OSGiScriptEngineManager osgiFactory = new OSGiScriptEngineManager( FrameworkUtil.getBundle(Core.class).getBundleContext()); + engine = osgiFactory.getEngineByName(engineName); + } + + return engine; + } } diff --git a/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngine.java b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngine.java new file mode 100644 index 0000000000..31619a0428 --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngine.java @@ -0,0 +1,85 @@ +/* + * 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.adempiere.base.osgi; + +import java.io.Reader; + +import javax.script.Bindings; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; + +/** + * Imported from Apache Felix project. + * Original file: + * http://svn.apache.org/repos/asf/felix/trunk/mishell/src/main/java/org/apache/felix/mishell/OSGiScriptEngine.java + * + */ + + +public class OSGiScriptEngine implements ScriptEngine{ + private ScriptEngine engine; + private OSGiScriptEngineFactory factory; + public OSGiScriptEngine(ScriptEngine engine, OSGiScriptEngineFactory factory){ + this.engine=engine; + this.factory=factory; + } + public Bindings createBindings() { + return engine.createBindings(); + } + public Object eval(Reader reader, Bindings n) throws ScriptException { + return engine.eval(reader, n); + } + public Object eval(Reader reader, ScriptContext context) throws ScriptException { + return engine.eval(reader, context); + } + public Object eval(Reader reader) throws ScriptException { + return engine.eval(reader); + } + public Object eval(String script, Bindings n) throws ScriptException { + return engine.eval(script, n); + } + public Object eval(String script, ScriptContext context) throws ScriptException { + return engine.eval(script, context); + } + public Object eval(String script) throws ScriptException { + return engine.eval(script); + } + public Object get(String key) { + return engine.get(key); + } + public Bindings getBindings(int scope) { + return engine.getBindings(scope); + } + public ScriptContext getContext() { + return engine.getContext(); + } + public ScriptEngineFactory getFactory() { + return factory; + } + public void put(String key, Object value) { + engine.put(key, value); + } + public void setBindings(Bindings bindings, int scope) { + engine.setBindings(bindings, scope); + } + public void setContext(ScriptContext context) { + engine.setContext(context); + } + +} diff --git a/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineFactory.java b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineFactory.java new file mode 100644 index 0000000000..15f45a32ea --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineFactory.java @@ -0,0 +1,89 @@ +/* + * 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.adempiere.base.osgi; + +import java.util.List; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +/** + * This is a wrapper class for the ScriptEngineFactory class that deals with context class loader issues + * It is necessary because engines (at least ruby) use the context classloader to find their resources (i.e., their "native" classes) + * + */ + +/** + * Imported from Apache Felix project. + * Original file: + * http://svn.apache.org/repos/asf/felix/trunk/mishell/src/main/java/org/apache/felix/mishell/OSGiScriptEngineFactory.java + * + */ +public class OSGiScriptEngineFactory implements ScriptEngineFactory{ + private ScriptEngineFactory factory; + private ClassLoader contextClassLoader; + public OSGiScriptEngineFactory (ScriptEngineFactory factory, ClassLoader contextClassLoader){ + this.factory=factory; + this.contextClassLoader=contextClassLoader; + } + public String getEngineName() { + return factory.getEngineName(); + } + public String getEngineVersion() { + return factory.getEngineVersion(); + } + public List getExtensions() { + return factory.getExtensions(); + } + public String getLanguageName() { + return factory.getLanguageName(); + } + public String getLanguageVersion() { + return factory.getLanguageVersion(); + } + public String getMethodCallSyntax(String obj, String m, String... args) { + return factory.getMethodCallSyntax(obj, m, args); + } + public List getMimeTypes() { + return factory.getMimeTypes(); + } + public List getNames() { + return factory.getNames(); + } + public String getOutputStatement(String toDisplay) { + return factory.getOutputStatement(toDisplay); + } + public Object getParameter(String key) { + return factory.getParameter(key); + } + public String getProgram(String... statements) { + return factory.getProgram(statements); + } + public ScriptEngine getScriptEngine() { + ScriptEngine engine=null; + if(contextClassLoader!=null){ + ClassLoader old=Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(contextClassLoader); + engine=factory.getScriptEngine(); + Thread.currentThread().setContextClassLoader(old); + } + else engine=factory.getScriptEngine(); + return engine; + } + + +} diff --git a/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineManager.java b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineManager.java new file mode 100644 index 0000000000..e19832314f --- /dev/null +++ b/org.adempiere.base/src/org/adempiere/base/osgi/OSGiScriptEngineManager.java @@ -0,0 +1,263 @@ +/* + * 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.adempiere.base.osgi; + +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.compiere.util.Util; +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: + *
    • + * All factories are wrapped with an {@link OSGiScriptEngineFactory}. As Engines are not wrapped, + * calls like + * + * ScriptEngineManager osgiManager=new OSGiScriptEngineManager(context);
      + * 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 + *
      + * 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. + *
    • + * + */ + +/** + * Imported from Apache Felix project. + * Original file: + * http://svn.apache.org/repos/asf/felix/trunk/mishell/src/main/java/org/apache/felix/mishell/OSGiScriptEngineManager.java + * + * @author Silvano Trinchero, www.freepath.it + *
    • IDEMPIERE-3243 fixed class discovery to avoid comment lines + * + */ + +public class OSGiScriptEngineManager extends ScriptEngineManager{ + private Bindings bindings; + private Map 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: + * + * 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 + * + * + * + */ + 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 getEngineFactories() { + List osgiFactories=new ArrayList(); + 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 findManagers(BundleContext context) { + Map managers=new HashMap(); + 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 findFactoryCandidates(BundleContext context) throws IOException{ + Bundle[] bundles = context.getBundles(); + List factoryCandidates = new ArrayList(); + for (Bundle bundle : bundles) { + // IDEMPIERE-3243: removed print and small improvements on enumeration + // 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 = urls.nextElement(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(u.openStream())); + String line; + while ((line = reader.readLine()) != null) + { + // IDEMPIERE-3243: removed comment lines + + int commentIdx = line.indexOf('#'); + + if(commentIdx >= 0) + line = line.substring(0, commentIdx); + + line = line.trim(); + + if(Util.isEmpty(line) == false) + { + factoryCandidates.add(line); + } + } + } + } + return factoryCandidates; + } +} diff --git a/org.adempiere.base/src/org/compiere/model/MRule.java b/org.adempiere.base/src/org/compiere/model/MRule.java index 457cd50ffc..bb87a37f07 100644 --- a/org.adempiere.base/src/org/compiere/model/MRule.java +++ b/org.adempiere.base/src/org/compiere/model/MRule.java @@ -24,8 +24,8 @@ import java.util.List; import java.util.Properties; import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; +import org.adempiere.base.Core; import org.compiere.util.CCache; import org.compiere.util.CLogger; import org.compiere.util.Msg; @@ -34,7 +34,10 @@ import org.compiere.util.Util; /** * Persistent Rule Model * @author Carlos Ruiz + * @author Silvano Trinchero, www.freepath.it + *
    • IDEMPIERE-3243 refactored getScriptEngine to use Core.getScriptEngine * @version $Id: MRule.java + * */ public class MRule extends X_AD_Rule { @@ -129,8 +132,6 @@ public class MRule extends X_AD_Rule @SuppressWarnings("unused") private static CLogger s_log = CLogger.getCLogger (MRule.class); - /* Engine Manager */ - private ScriptEngineManager factory = null; /* The Engine */ ScriptEngine engine = null; @@ -195,10 +196,12 @@ public class MRule extends X_AD_Rule * @return ScriptEngine */ public ScriptEngine getScriptEngine() { - factory = new ScriptEngineManager(getClass().getClassLoader()); + String engineName = getEngineName(); - if (engineName != null) - engine = factory.getEngineByName(getEngineName()); + + if(engineName != null) + engine = Core.getScriptEngine(engineName); + return engine; }