diff --git a/src/main/java/org/topbraid/shacl/py/GraalPyScriptEngine.java b/src/main/java/org/topbraid/shacl/py/GraalPyScriptEngine.java index 95caf21b..f00ae51b 100644 --- a/src/main/java/org/topbraid/shacl/py/GraalPyScriptEngine.java +++ b/src/main/java/org/topbraid/shacl/py/GraalPyScriptEngine.java @@ -1,8 +1,7 @@ package org.topbraid.shacl.py; -import org.apache.jena.query.QuerySolution; -import org.apache.jena.rdf.model.Resource; import org.graalvm.polyglot.Context; +import org.topbraid.shacl.py.model.PyTermFactory; import javax.script.ScriptException; @@ -15,12 +14,19 @@ public class GraalPyScriptEngine extends PyScriptEngineImpl { public GraalPyScriptEngine() { initEngine(); - // TODO implement + context.getPolyglotBindings().putMember("PyTermFactory", new PyTermFactory()); + context.eval("python", + """ + import polyglot\s + py_tf = polyglot.import_value('PyTermFactory') + """); + context.eval("python", ARGS_FUNCTION); } @Override public void initEngine() { this.context = Context.newBuilder() + .allowAllAccess(true) .option("engine.WarnInterpreterOnly", "false") .build(); if (this.context == null) { @@ -33,33 +39,17 @@ public Object eval(String expr) throws ScriptException { return context.eval("python", expr); } - @Override - public void executeLibraries(Resource exec) throws Exception { - // TODO implement - } - - @Override - public void executeScriptFromURL(String url) throws Exception { - // TODO implement - } - @Override public Object get(String varName) { return context.getBindings("python").getMember(varName); } - @Override - public Object invokeFunction(String functionName, QuerySolution bindings) throws ScriptException, NoSuchMethodException { - return null; - // TODO implement - } - public final Context getContext() { return context; } @Override - public Object invokeFunctionOrdered(String functionName, Object[] args) throws ScriptException, NoSuchMethodException { + public Object invokeFunctionOrdered(String functionName, Object[] args) { return context.getBindings("python").getMember(functionName).execute(args); } diff --git a/src/main/java/org/topbraid/shacl/py/PyGraph.java b/src/main/java/org/topbraid/shacl/py/PyGraph.java new file mode 100644 index 00000000..62e6b4c4 --- /dev/null +++ b/src/main/java/org/topbraid/shacl/py/PyGraph.java @@ -0,0 +1,106 @@ +/* + * 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. + * + * See the NOTICE file distributed with this work for additional + * information regarding copyright ownership. + */ +package org.topbraid.shacl.py; + +import org.apache.jena.graph.Graph; +import org.apache.jena.graph.Node; +import org.apache.jena.graph.Triple; +import org.apache.jena.util.iterator.ExtendedIterator; +import org.topbraid.jenax.util.ExceptionUtil; +import org.topbraid.shacl.js.ScriptEngine; +import org.topbraid.shacl.py.model.PyFactory; +import org.topbraid.shacl.py.model.PyTriple; +import org.topbraid.shacl.util.FailureLog; + +import java.util.HashSet; +import java.util.Set; + +public class PyGraph { + + protected ScriptEngine engine; + private Graph graph; + private Set openIterators = new HashSet<>(); + + public PyGraph(Graph graph, ScriptEngine engine) { + this.engine = engine; + this.graph = graph; + } + + public void close() { + if (!openIterators.isEmpty()) { + FailureLog.get().logWarning("Python graph session ended but " + openIterators.size() + " iterators have not been closed. Make sure to close them programmatically to avoid resource leaks and locking problems."); + } + closeIterators(); + } + + public void closeIterators() { + for (PyTripleIterator stream : openIterators) { + stream.closeIterator(); + } + openIterators.clear(); + } + + public Graph getGraph() { + return graph; + } + + public PyTripleIterator find(Object subjectSOM, Object predicateSOM, Object objectSOM) { + Node subject = PyFactory.getNode(subjectSOM); + Node predicate = PyFactory.getNode(predicateSOM); + Node object = PyFactory.getNode(objectSOM); + ExtendedIterator it = getGraph().find(subject, predicate, object); + PyTripleIterator pyit = new PyTripleIterator(it); + openIterators.add(pyit); + return pyit; + } + + public Object query() { + try { + return engine.invokeFunctionOrdered("RDFQuery", new Object[]{this}); + } catch (Exception ex) { + throw ExceptionUtil.throwUnchecked(ex); + } + } + + public class PyTripleIterator { + + private ExtendedIterator it; + + PyTripleIterator(ExtendedIterator it) { + this.it = it; + } + + public void close() { + closeIterator(); + openIterators.remove(this); + } + + void closeIterator() { + it.close(); + } + + public PyTriple next() { + if (it.hasNext()) { + Triple triple = it.next(); + return PyFactory.asPyTriple(triple); + } else { + close(); + return null; + } + } + } +} diff --git a/src/main/java/org/topbraid/shacl/py/PyScriptEngineImpl.java b/src/main/java/org/topbraid/shacl/py/PyScriptEngineImpl.java index 2857abec..d8b87ac9 100644 --- a/src/main/java/org/topbraid/shacl/py/PyScriptEngineImpl.java +++ b/src/main/java/org/topbraid/shacl/py/PyScriptEngineImpl.java @@ -24,22 +24,15 @@ public abstract class PyScriptEngineImpl implements ScriptEngine { protected final static String ARGS_FUNCTION_NAME = "get_args"; protected final static String ARGS_FUNCTION = "def " + ARGS_FUNCTION_NAME + "(func_str): \n" + - "\t start_idx = func_str.find('(') + 1\n" + - "\t end_idx = func_str.find(')', start_idx)\n" + - "\t if start_idx != -1 and end_idx != -1:\n" + - "\t\t params_str = func_str[start_idx:end_idx]\n" + - "\t\t args_list = [arg.strip() for arg in params_str.split(',')]\n" + - "\t\t return args_list\n" + - "\t else:\n" + + "\t start_idx = func_str.find('(') + 1 \n" + + "\t end_idx = func_str.find(')', start_idx) \n" + + "\t if start_idx != -1 and end_idx != -1: \n" + + "\t\t params_str = func_str[start_idx:end_idx] \n" + + "\t\t args_list = [arg.strip() for arg in params_str.split(',')] \n" + + "\t\t return args_list \n" + + "\t else: \n" + "\t\t return []"; - // Copied from https://davidwalsh.name/javascript-arguments - protected final static String ARGS_FUNCTION_2 = - "def " + ARGS_FUNCTION_NAME + "(funcString): \n" + - "\t func_code = func.__code__ \n" + - "\t args_list = func_code.co_varnames[:func_code.co_argcount]\n" + - "\t return args_list\n"; - public static final String DASH_PY = "http://datashapes.org/py/dash.py"; public static final String RDFQUERY_PY = "http://datashapes.org/py/rdfquery.py"; @@ -114,6 +107,7 @@ public Object invokeFunction(String functionName, QuerySolution bindings) throws public abstract void put(String varName, Object value); protected Reader createScriptReader(String url) throws Exception { + // TODO replace dash.js and rdfquery.js with python versions if (DASH_PY.equals(url)) { return new InputStreamReader(GraalPyScriptEngine.class.getResourceAsStream("/js/dash.js")); } else if (RDFQUERY_PY.equals(url)) { @@ -133,7 +127,6 @@ private List getFunctionParameters(String functionName) throws ScriptExc throw new ScriptException("Cannot find Python function \"" + functionName + "\""); } try { - //String funcString = what.toString(); String funcString = ((Value) what).getSourceLocation().getCharacters().toString(); Object result = this.invokeFunctionOrdered(ARGS_FUNCTION_NAME, Collections.singletonList(funcString).toArray()); List results = ScriptEngineUtil.asArray(result).stream().map(Object::toString).collect(Collectors.toList()); @@ -143,4 +136,5 @@ private List getFunctionParameters(String functionName) throws ScriptExc throw new ScriptException(ex); } } + }