IDEMPIERE-3243 fixed by using classes from apache felix project. No other classes needed modifications since script-enabled classes relies on MRule

This commit is contained in:
freepath 2016-11-18 13:02:23 +01:00
parent 5f9427c39e
commit c8245e7555
5 changed files with 473 additions and 6 deletions

View File

@ -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
* <li>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;
}
}

View File

@ -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);
}
}

View File

@ -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<String> 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<String> getMimeTypes() {
return factory.getMimeTypes();
}
public List<String> 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;
}
}

View File

@ -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:
* <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>
*
*/
/**
* 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
* <li>IDEMPIERE-3243 fixed class discovery to avoid comment lines
*
*/
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) {
// IDEMPIERE-3243: removed print and small improvements on enumeration
// System.out.println(bundle.getSymbolicName());
if(bundle.getSymbolicName().equals("system.bundle")) continue;
Enumeration<URL> 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;
}
}

View File

@ -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
* <li>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;
}