diff --git a/pom.xml b/pom.xml index 100b16589429bd146d1770ca425b4cb735aea8eb..bc0158879cc86ca568cd7b6d25d78c2a65c657c8 100755 --- a/pom.xml +++ b/pom.xml @@ -58,6 +58,12 @@ <type>jar</type> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.python</groupId> + <artifactId>jython-standalone</artifactId> + <version>2.7.0</version> + <type>jar</type> + </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> diff --git a/src/main/java/no/nibio/vips/model/JythonModel.java b/src/main/java/no/nibio/vips/model/JythonModel.java new file mode 100644 index 0000000000000000000000000000000000000000..a14860538656b097474eea9ebe720098900caa9a --- /dev/null +++ b/src/main/java/no/nibio/vips/model/JythonModel.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2017 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSCommon. + * VIPSCommon is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSCommon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSCommon. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.model; + +import org.python.util.PythonInterpreter; + +/** + * When implementing a model using Jython (Python on the Java Virtual Machine), + * you must extend this class. + * + * @copyright 2017 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +public abstract class JythonModel{ + + public JythonModel() + { + // Adding possible paths to the python module + PythonInterpreter interpreter = new PythonInterpreter(); + interpreter.exec("import sys"); + // Adding path to the containing JAR (if any) or code source + String jarPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(); + // For some reason weird prefixes and suffixes are added to the path. We try to + // remove them. This removal might ned to be revised when new deployment situations occur + jarPath = jarPath.replaceFirst("file:", "").replace("!", ""); + interpreter.exec("sys.path.append('" + jarPath + "python')"); + //interpreter.exec("print sys.path"); + } + + /** + * Assumes the python modules are found under the [jarPath]/python folder + * @param moduleName + * @param className + * @return + */ + protected Model getPythonModel(String moduleName, String className){ + JythonObjectFactory f = new JythonObjectFactory( + Model.class, moduleName, className + ); + return (Model) f.createObject(); + } +} diff --git a/src/main/java/no/nibio/vips/model/JythonObjectFactory.java b/src/main/java/no/nibio/vips/model/JythonObjectFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..88dad9c58d07e565273fe021c67ad9cbfa351afa --- /dev/null +++ b/src/main/java/no/nibio/vips/model/JythonObjectFactory.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2017 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSCommon. + * VIPSCommon is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSCommon is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSCommon. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.model; + +/** + * @copyright 2017 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +import java.util.Properties; +import org.python.core.Py; +import org.python.core.PyObject; +import org.python.core.PySystemState; + +/** + * Jython Object Factory using PySystemState + * Found here: http://www.jython.org/jythonbook/en/1.0/JythonAndJavaIntegration.html#more-efficient-version-of-loosely-coupled-object-factory + */ +public class JythonObjectFactory { + + private final Class interfaceType; + private final PyObject klass; + + // Constructor obtains a reference to the importer, module, and the class name + public JythonObjectFactory(PySystemState state, Class interfaceType, String moduleName, String className) { + this.interfaceType = interfaceType; + PyObject importer = state.getBuiltins().__getitem__(Py.newString("__import__")); + PyObject module = importer.__call__(Py.newString(moduleName)); + klass = module.__getattr__(className); + //System.err.println("module=" + module + ",class=" + klass); + } + + // This constructor passes through to the other constructor + public JythonObjectFactory(Class interfaceType, String moduleName, String className) { + this(new PySystemState(), interfaceType, moduleName, className); + } + + // All of the followng methods return + // a coerced Jython object based upon the pieces of information + // that were passed into the factory. The differences are + // between them are the number of arguments that can be passed + // in as arguents to the object. + + public Object createObject() { + return klass.__call__().__tojava__(interfaceType); + } + + + public Object createObject(Object arg1) { + return klass.__call__(Py.java2py(arg1)).__tojava__(interfaceType); + } + + public Object createObject(Object arg1, Object arg2) { + return klass.__call__(Py.java2py(arg1), Py.java2py(arg2)).__tojava__(interfaceType); + } + + public Object createObject(Object arg1, Object arg2, Object arg3) + { + return klass.__call__(Py.java2py(arg1), Py.java2py(arg2), + Py.java2py(arg3)).__tojava__(interfaceType); + } + + public Object createObject(Object args[], String keywords[]) { + PyObject convertedArgs[] = new PyObject[args.length]; + for (int i = 0; i < args.length; i++) { + convertedArgs[i] = Py.java2py(args[i]); + } + + return klass.__call__(convertedArgs, keywords).__tojava__(interfaceType); + } + + public Object createObject(Object... args) { + return createObject(args, Py.NoKeywords); + } + + /** + * Adds user.dir into python.path to make Jython look for python modules in working directory in all cases + * (both standalone and not standalone modes) + * @param props + * @return props + */ +private Properties setDefaultPythonPath(Properties props) { + String pythonPathProp = props.getProperty("python.path"); + String new_value; + if (pythonPathProp==null) + { + new_value = System.getProperty("user.dir"); + } else { + new_value = pythonPathProp +java.io.File.pathSeparator + System.getProperty("user.dir") + java.io.File.pathSeparator; + } + props.setProperty("python.path",new_value); + return props; +} + +}