diff --git a/s-pipes-core/src/main/java/cz/cvut/spipes/function/time/AddDaysDeprecated.java b/s-pipes-core/src/main/java/cz/cvut/spipes/function/time/AddDaysDeprecated.java new file mode 100644 index 00000000..24bec3d9 --- /dev/null +++ b/s-pipes-core/src/main/java/cz/cvut/spipes/function/time/AddDaysDeprecated.java @@ -0,0 +1,75 @@ +package cz.cvut.spipes.function.time; + +import cz.cvut.spipes.constants.KBSS_TIMEF; +import cz.cvut.spipes.function.ValueFunction; +import org.apache.jena.datatypes.RDFDatatype; +import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.datatypes.xsd.impl.XSDBaseStringType; +import org.apache.jena.datatypes.xsd.impl.XSDDateType; +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.expr.NodeValue; +import org.apache.jena.sparql.function.FunctionEnv; +import org.topbraid.spin.arq.AbstractFunction2; + +import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.Optional; + +/** + * Extend specified `date` by number of `days`. Return typed literal with same datatype. + * Currently, supports only xsd:date datatype. + */ +public class AddDaysDeprecated extends AbstractFunction2 implements ValueFunction { + + + private static final String TYPE_IRI = KBSS_TIMEF.uri + "add-days-deprecated"; + + @Override + public String getTypeURI() { + return TYPE_IRI; + } + + @Override + protected NodeValue exec(Node date, Node days, FunctionEnv env) { + + Long daysToAdd = getDays(days); + RDFDatatype datatype = getDatatype(date); + + try { + if (datatype != null && daysToAdd != null) { + //TODO quite slow to parse everytime + String newDate = LocalDate.parse(date.getLiteral().getValue().toString()).plusDays(daysToAdd).toString(); + return NodeValue.makeNode(newDate, datatype); + } + } catch (DateTimeParseException e){ + } + + return null; + } + + private Long getDays(Node days) { + return Optional.of(days) + .filter(Node::isLiteral) + .filter(n -> n.getLiteralValue() instanceof Integer) + .map(n -> ((Integer) n.getLiteralValue()).longValue()) + .orElse(null); + } + + RDFDatatype getDatatype(Node date) { + + return Optional.of(date) + .filter(Node::isLiteral) + .map(n -> getNewDatatype(n.getLiteralDatatype())) + .orElse(null); + } + + RDFDatatype getNewDatatype(RDFDatatype datatype){ + if (datatype instanceof XSDDateType) { + return XSDDatatype.XSDdate; + } + if (datatype instanceof XSDBaseStringType) { + return XSDDatatype.XSDstring; + } + return null; + } +} diff --git a/s-pipes-core/src/test/java/cz/cvut/spin/SpinIntegrationTest.java b/s-pipes-core/src/test/java/cz/cvut/spin/SpinIntegrationTest.java index bed44fc8..b689ee7f 100644 --- a/s-pipes-core/src/test/java/cz/cvut/spin/SpinIntegrationTest.java +++ b/s-pipes-core/src/test/java/cz/cvut/spin/SpinIntegrationTest.java @@ -1,42 +1,74 @@ package cz.cvut.spin; -import cz.cvut.spipes.engine.PipelineFactory; -import org.apache.jena.ontology.OntModelSpec; -import org.apache.jena.query.*; -import org.apache.jena.rdf.model.*; -import org.apache.jena.util.FileUtils; -import org.junit.jupiter.api.Test; -import org.topbraid.spin.model.SPINFactory; -import org.topbraid.spin.system.SPINModuleRegistry; -import org.topbraid.spin.util.SPINExpressions; -import org.topbraid.spin.vocabulary.SP; + import org.apache.jena.ontology.OntModelSpec; + import org.apache.jena.query.*; + import org.apache.jena.rdf.model.*; + import org.apache.jena.util.FileUtils; + import org.jetbrains.annotations.NotNull; + import org.junit.jupiter.api.Test; + import org.topbraid.spin.model.SPINFactory; + import org.topbraid.spin.system.SPINModuleRegistry; + import org.topbraid.spin.util.SPINExpressions; + import org.topbraid.spin.vocabulary.SP; + + import java.io.InputStream; + import java.net.URLEncoder; + import java.nio.charset.StandardCharsets; + + import static org.junit.jupiter.api.Assertions.assertEquals; + import static org.junit.jupiter.api.Assertions.assertTrue; + + public class SpinIntegrationTest { -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; + @Test + public void executeCustomSPINRDFFunctionWithinQuery() { + // load custom function definition from RDF + Model funcDefModel = getCustomSPINRDFFunctionModel(); -import static org.junit.jupiter.api.Assertions.assertEquals; + // register custom function + //SPINModuleRegistry.get().init(); + SPINModuleRegistry.get().registerAll(funcDefModel, null); -public class SpinIntegrationTest { + String repositoryUrl = "http://repository.org"; + String graphId = "http://graphid.org"; + + String queryString = String.format(""" + PREFIX kbss-spif: + SELECT ?sparqlServiceUrl + WHERE { + BIND(kbss-spif:create-sparql-service-url( + "%s", + "%s" + ) AS ?sparqlServiceUrl) + } + """, repositoryUrl, graphId); + Model model = ModelFactory.createDefaultModel(); - @Test - public void executeSPINExpressionWithCustomSpinFunction() throws UnsupportedEncodingException { + Query query = QueryFactory.create(queryString); + + QueryExecution qexec = QueryExecutionFactory.create(query, model); + ResultSet results = qexec.execSelect(); - // load custom function definition - Model funcDefModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); - // Model funcDefModel = ModelFactory.createDefaultModel(); // TODO this does not work + assertTrue(results.hasNext(), "No results found"); - final InputStream funcDefIs = this.getClass().getResourceAsStream("/spin/spin-function.spin.ttl"); + QuerySolution soln = results.nextSolution(); + assertEquals( + soln.getResource("sparqlServiceUrl").getURI(), + constructServiceUrl(repositoryUrl, graphId) + ); - funcDefModel.read(funcDefIs, null, FileUtils.langTurtle); + } + + @Test + public void executeSPINExpressionWithCustomSPINRDFFunction() { + // load custom function definition from RDF + Model funcDefModel = getCustomSPINRDFFunctionModel(); // register custom function //SPINModuleRegistry.get().init(); SPINModuleRegistry.get().registerAll(funcDefModel, null); - - + // load custom function call Model funcCallModel = ModelFactory.createDefaultModel(); @@ -51,20 +83,17 @@ public void executeSPINExpressionWithCustomSpinFunction() throws UnsupportedEnco // evaluate SPIN expression QuerySolutionMap bindings = new QuerySolutionMap(); String repositoryUrl = "http://repository.org"; - String reportGraphId = "http://graphid.org"; - bindings.add("repositoryUrl", ResourceFactory.createPlainLiteral(repositoryUrl)); - bindings.add("reportGraphId", ResourceFactory.createPlainLiteral(reportGraphId)); - + String graphId = "http://graphid.org"; + bindings.add("sparqlEndpoint", ResourceFactory.createPlainLiteral(repositoryUrl)); + bindings.add("defaultGraphUri", ResourceFactory.createPlainLiteral(graphId)); RDFNode node = SPINExpressions.evaluate(callExpr, callExpr.getModel(), bindings); //TODO resource.getModel() should be part o context - - assertEquals(node.toString(), repositoryUrl + "?default-graph-uri=" + URLEncoder.encode(reportGraphId, StandardCharsets.UTF_8) ); + assertEquals(node.toString(), constructServiceUrl(repositoryUrl, graphId)); } @Test public void executeSPINQueryWithCustomJavaFunction() { - PipelineFactory pipelineFactory = new PipelineFactory(); String queryString = """ PREFIX kbss-timef: @@ -78,13 +107,30 @@ public void executeSPINQueryWithCustomJavaFunction() { Query query = QueryFactory.create(queryString); - QueryExecution qexec = QueryExecutionFactory.create(query, model); ResultSet results = qexec.execSelect(); - if (results.hasNext()) { - QuerySolution soln = results.nextSolution(); - assertEquals(soln.getLiteral("nextDay").getString(), "2022-01-02"); - } + assertTrue(results.hasNext(), "No results found"); + + QuerySolution soln = results.nextSolution(); + assertEquals(soln.getLiteral("nextDay").getString(), "2022-01-02"); } + + @NotNull + private Model getCustomSPINRDFFunctionModel() { + // load custom function definition + Model funcDefModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + // Model funcDefModel = ModelFactory.createDefaultModel(); // TODO this does not work + + final InputStream funcDefIs = this.getClass().getResourceAsStream("/spin/spin-function.spin.ttl"); + + funcDefModel.read(funcDefIs, null, FileUtils.langTurtle); + + return funcDefModel; + } + + @NotNull + private String constructServiceUrl(String repositoryUrl, String graphId) { + return String.format("%s?default-graph-uri=%s", repositoryUrl, URLEncoder.encode(graphId, StandardCharsets.UTF_8)); + } } diff --git a/s-pipes-core/src/test/java/cz/cvut/spipes/function/time/AddDaysDeprecatedTest.java b/s-pipes-core/src/test/java/cz/cvut/spipes/function/time/AddDaysDeprecatedTest.java new file mode 100644 index 00000000..0772a26f --- /dev/null +++ b/s-pipes-core/src/test/java/cz/cvut/spipes/function/time/AddDaysDeprecatedTest.java @@ -0,0 +1,56 @@ +package cz.cvut.spipes.function.time; + +import org.apache.jena.datatypes.RDFDatatype; +import org.apache.jena.datatypes.xsd.XSDDatatype; +import org.apache.jena.graph.Node; +import org.apache.jena.sparql.expr.NodeValue; +import org.junit.jupiter.api.Test; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddDaysDeprecatedTest { + + @Test + public void execReturnsTimeFromPast() { + + AddDaysDeprecated addDays = new AddDaysDeprecated(); + Node date = getDateNode("2022-01-01").asNode(); + Node days = NodeValue.makeNodeDecimal("-1").asNode(); + + NodeValue returnedDate = addDays.exec(date, days, null); + + NodeValue expectedDate = getDateNode("2021-12-31"); + assertEquals(expectedDate, returnedDate); + } + + @Test + public void execReturnsDatatypeOfInputLiteral() { + Node days = NodeValue.makeNodeDecimal("1").asNode(); + + Stream.of(XSDDatatype.XSDdate, XSDDatatype.XSDstring).forEach( + dt -> { + Node date = getNode("2021-12-31", dt).asNode(); + + AddDaysDeprecated addDays = new AddDaysDeprecated(); + NodeValue returnedDate = addDays.exec(date, days, null); + + NodeValue expectedDate = getNode("2022-01-01", dt); + assertEquals(expectedDate, returnedDate); + }); + } + + + private NodeValue getDateNode(String date){ + return getNode(date, XSDDatatype.XSDdate); + } + + private NodeValue getNode(String date, RDFDatatype datatype) { + return NodeValue.makeNode( + date, + null, + datatype.getURI() + ); + } +} \ No newline at end of file diff --git a/s-pipes-core/src/test/resources/spin/spin-function-call.ttl b/s-pipes-core/src/test/resources/spin/spin-function-call.ttl index bf2859b8..26407b3f 100644 --- a/s-pipes-core/src/test/resources/spin/spin-function-call.ttl +++ b/s-pipes-core/src/test/resources/spin/spin-function-call.ttl @@ -16,12 +16,12 @@ . [ - rdf:type kbss-spif:create-sparql-service-url ; + rdf:type kbss-spif:create-sparql-service-url-deprecated ; sp:arg1 [ - sp:varName "repositoryUrl" ; + sp:varName "sparqlEndpoint" ; ] ; sp:arg2 [ - sp:varName "reportGraphId" ; + sp:varName "defaultGraphUri" ; ] ; ] .