From baa3804b48f9e203026408b8ea8b8fe5ffd09ea0 Mon Sep 17 00:00:00 2001 From: Jakub Skoczen Date: Wed, 15 Oct 2014 16:52:34 +0200 Subject: [PATCH] SPARQL builder and CQL-to-SPARQL converter SUP-652 A set of classes to allow simple creation and modification of SPARQL queries and a trivial, sample CQL-to-SPARQL converter (ignores acual booleans and assumes AND, uses index name for graph paths). Tests. --- .../java/org/z3950/zing/cql/sparql/Filter.java | 27 +++++ src/main/java/org/z3950/zing/cql/sparql/Form.java | 17 +++ .../org/z3950/zing/cql/sparql/GraphPattern.java | 18 ++++ .../org/z3950/zing/cql/sparql/GraphPatternSet.java | 38 +++++++ .../java/org/z3950/zing/cql/sparql/Optional.java | 27 +++++ .../java/org/z3950/zing/cql/sparql/Prefix.java | 30 ++++++ src/main/java/org/z3950/zing/cql/sparql/Query.java | 74 +++++++++++++ .../z3950/zing/cql/sparql/SPARQLNodeVisitor.java | 50 +++++++++ .../java/org/z3950/zing/cql/sparql/Select.java | 35 ++++++ .../org/z3950/zing/cql/sparql/SelectQuery.java | 30 ++++++ .../java/org/z3950/zing/cql/sparql/Sparqler.java | 21 ++++ .../org/z3950/zing/cql/sparql/TriplePattern.java | 33 ++++++ src/main/java/org/z3950/zing/cql/sparql/Where.java | 29 +++++ .../org/z3950/zing/cql/utils/PrettyPrinter.java | 64 +++++++++++ .../java/org/z3950/zing/cql/utils/Printable.java | 16 +++ .../java/org/z3950/zing/cql/sparql/QueryTest.java | 112 ++++++++++++++++++++ 16 files changed, 621 insertions(+) create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Filter.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Form.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/GraphPattern.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/GraphPatternSet.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Optional.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Prefix.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Query.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/SPARQLNodeVisitor.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Select.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/SelectQuery.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Sparqler.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/TriplePattern.java create mode 100644 src/main/java/org/z3950/zing/cql/sparql/Where.java create mode 100644 src/main/java/org/z3950/zing/cql/utils/PrettyPrinter.java create mode 100644 src/main/java/org/z3950/zing/cql/utils/Printable.java create mode 100644 src/test/java/org/z3950/zing/cql/sparql/QueryTest.java diff --git a/src/main/java/org/z3950/zing/cql/sparql/Filter.java b/src/main/java/org/z3950/zing/cql/sparql/Filter.java new file mode 100644 index 0000000..29f6e89 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Filter.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; + +/** + * + * @author jakub + */ +public class Filter implements GraphPattern { + private final String filter; + + public Filter(String filter) { + this.filter = filter; + } + + @Override + public void print(PrettyPrinter pr) { + pr.startl("FILTER ").put(filter).put(".").endl(); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Form.java b/src/main/java/org/z3950/zing/cql/sparql/Form.java new file mode 100644 index 0000000..8b3a777 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Form.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import org.z3950.zing.cql.utils.Printable; + +/** + * + * @author jakub + */ +public interface Form extends Printable { +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/GraphPattern.java b/src/main/java/org/z3950/zing/cql/sparql/GraphPattern.java new file mode 100644 index 0000000..2acded2 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/GraphPattern.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import org.z3950.zing.cql.utils.Printable; + +/** + * + * @author jakub + */ +public interface GraphPattern extends Printable { + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/GraphPatternSet.java b/src/main/java/org/z3950/zing/cql/sparql/GraphPatternSet.java new file mode 100644 index 0000000..859d91c --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/GraphPatternSet.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author jakub + */ +public class GraphPatternSet implements GraphPattern { + private final List patterns = new LinkedList(); + + public GraphPatternSet(GraphPattern... patterns) { + for (GraphPattern pattern : patterns) + this.patterns.add(pattern); + } + + public GraphPatternSet pattern(GraphPattern pattern) { + patterns.add(pattern); + return this; + } + + @Override + public void print(PrettyPrinter sw) { + sw.put("{").endl(); + for (GraphPattern tp : patterns) { + tp.print(sw.levelUp()); + } + sw.putl("}"); + } +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Optional.java b/src/main/java/org/z3950/zing/cql/sparql/Optional.java new file mode 100644 index 0000000..88d51cc --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Optional.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; + +/** + * + * @author jakub + */ +public class Optional extends GraphPatternSet { + + public Optional(GraphPattern pattern) { + super(pattern); + } + + @Override + public void print(PrettyPrinter sw) { + sw.startl("OPTIONAL "); + super.print(sw); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Prefix.java b/src/main/java/org/z3950/zing/cql/sparql/Prefix.java new file mode 100644 index 0000000..5218969 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Prefix.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +/** + * + * @author jakub + */ +public class Prefix { + private final String name; + private final String url; + + public Prefix(String name, String url) { + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Query.java b/src/main/java/org/z3950/zing/cql/sparql/Query.java new file mode 100644 index 0000000..0f3a213 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Query.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import java.util.Map; +import java.util.TreeMap; +import org.z3950.zing.cql.utils.Printable; + +/** + * + * @author jakub + */ +public class Query implements Printable { + private final Map prefixes = new TreeMap(); + private Form form; + private Where where; + + public Query() { + } + + public Query(Form form, Where where) { + this.form = form; + this.where = where; + } + + + public Prefix prefix(String name) { + return prefixes.get(name); + } + + public Query prefix(String name, String url) { + prefixes.put(name, new Prefix(name, url)); + return this; + } + + public Query prefix(Prefix prefix) { + prefixes.put(prefix.getName(), prefix); + return this; + } + + public Where where() { + return where; + } + + public Query where(Where where) { + this.where = where; + return this; + } + + public Form form() { + return form; + } + + public Query form(Form form) { + this.form = form; + return this; + } + + @Override + public void print(PrettyPrinter sw) { + for (Prefix prefix : prefixes.values()) { + sw.startl("PREFIX ") + .put(prefix.getName()).put(": ") + .put("<").put(prefix.getUrl()).put(">").endl(); + } + form.print(sw); + where.print(sw); + } +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/SPARQLNodeVisitor.java b/src/main/java/org/z3950/zing/cql/sparql/SPARQLNodeVisitor.java new file mode 100644 index 0000000..2fe37d6 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/SPARQLNodeVisitor.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.CQLDefaultNodeVisitor; +import org.z3950.zing.cql.CQLRelation; +import org.z3950.zing.cql.CQLTermNode; + +/** + * + * @author jakub + */ +public class SPARQLNodeVisitor extends CQLDefaultNodeVisitor { + private SelectQuery query = new SelectQuery(new Select(), new Where()); + private int objectCounter = 1; + + //some initial configuration + { + query.prefix("bf", "http://bibframe.org/vocab/"); + query.select().var("*"); + query.where().pattern(new TriplePattern("?work", "a", "bf:Work")); + } + + @Override + public void onRelation(CQLRelation relation) { + if (!relation.getBase().equals("=")) + throw new IllegalArgumentException("Can only handle '=' relations"); + } + + @Override + public void onTermNode(CQLTermNode node) { + //map index to predicate + String obj = getNextObject(); + query.where().pattern(new TriplePattern("?work", node.getIndex(), obj)); + query.where().pattern(new Filter("contains(lcase("+obj+"), \""+node.getTerm().toLowerCase()+"\")")); + } + + private String getNextObject() { + return "?o" + objectCounter++; + } + + public Query getQuery() { + return query; + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Select.java b/src/main/java/org/z3950/zing/cql/sparql/Select.java new file mode 100644 index 0000000..31a4e2e --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Select.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author jakub + */ +public class Select implements Form { + private final List variables = new LinkedList(); + + public Select var(String name) { + variables.add(name); + return this; + } + + @Override + public void print(PrettyPrinter sw) { + sw.startl("SELECT"); + for (String var : variables) { + sw.put(" "); + sw.put(var); + } + sw.endl(); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/SelectQuery.java b/src/main/java/org/z3950/zing/cql/sparql/SelectQuery.java new file mode 100644 index 0000000..3b13fe2 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/SelectQuery.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +/** + * API helper for select queries + * @author jakub + */ +public class SelectQuery extends Query { + + public SelectQuery(Select select, Where where) { + form(select); + where(where); + } + + + public Select select() { + return (Select) form(); + } + + public SelectQuery select(Select select) { + form(select); + return this; + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Sparqler.java b/src/main/java/org/z3950/zing/cql/sparql/Sparqler.java new file mode 100644 index 0000000..6b06a57 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Sparqler.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.CQLNode; + +/** + * + * @author jakub + */ +public class Sparqler { + + public static String toSPARQL(CQLNode query) { + return null; + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/TriplePattern.java b/src/main/java/org/z3950/zing/cql/sparql/TriplePattern.java new file mode 100644 index 0000000..88430b2 --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/TriplePattern.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; + +/** + * + * @author jakub + */ +public class TriplePattern implements GraphPattern { + private final String subject; + private final String predicate; + private final String object; + + public TriplePattern(String subjet, String predicate, String object) { + this.subject = subjet; + this.predicate = predicate; + this.object = object; + } + + @Override + public void print(PrettyPrinter wr) { + wr.startl(subject).put(" ") + .put(predicate).put(" ") + .put(object).put(" .").endl(); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/sparql/Where.java b/src/main/java/org/z3950/zing/cql/sparql/Where.java new file mode 100644 index 0000000..fa5479c --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/sparql/Where.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import org.z3950.zing.cql.utils.PrettyPrinter; +import java.util.LinkedList; +import java.util.List; + +/** + * + * @author jakub + */ +public class Where extends GraphPatternSet { + + public Where(GraphPattern... patterns) { + super(patterns); + } + + @Override + public void print(PrettyPrinter sw) { + sw.startl("WHERE "); + super.print(sw); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/utils/PrettyPrinter.java b/src/main/java/org/z3950/zing/cql/utils/PrettyPrinter.java new file mode 100644 index 0000000..8ed9bfc --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/utils/PrettyPrinter.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.utils; + +/** + * + * @author jakub + */ +public class PrettyPrinter { + private final static String INDENT = " "; + private final StringBuilder sb; + private final int level; + + public PrettyPrinter() { + sb = new StringBuilder(); + level = 0; + } + + private PrettyPrinter(StringBuilder sb, int level) { + this.sb = sb; + this.level = level; + } + + public PrettyPrinter put(String append) { + sb.append(append); + return this; + } + + public PrettyPrinter endl() { + sb.append('\n'); + return this; + } + + public PrettyPrinter startl(String append) { + indent(level); + return put(append); + } + + public PrettyPrinter putl(String line) { + return startl(line).endl(); + } + + public PrettyPrinter levelUp() { + //create new instance with a higher levelUp, backed by the same builder + return new PrettyPrinter(sb, level+1); + } + + public PrettyPrinter indent(int level) { + while (level-- > 0) { + sb.append(INDENT); + } + return this; + } + + @Override + public String toString() { + return sb.toString(); + } + +} diff --git a/src/main/java/org/z3950/zing/cql/utils/Printable.java b/src/main/java/org/z3950/zing/cql/utils/Printable.java new file mode 100644 index 0000000..26fc05b --- /dev/null +++ b/src/main/java/org/z3950/zing/cql/utils/Printable.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.utils; + +/** + * + * @author jakub + */ +public interface Printable { + void print(PrettyPrinter pr); + +} diff --git a/src/test/java/org/z3950/zing/cql/sparql/QueryTest.java b/src/test/java/org/z3950/zing/cql/sparql/QueryTest.java new file mode 100644 index 0000000..08b5e84 --- /dev/null +++ b/src/test/java/org/z3950/zing/cql/sparql/QueryTest.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 1995-2014, Index Data + * All rights reserved. + * See the file LICENSE for details. + */ + +package org.z3950.zing.cql.sparql; + +import java.io.IOException; +import static java.lang.System.out; +import static org.junit.Assert.*; + +import org.junit.Test; +import org.z3950.zing.cql.CQLNode; +import org.z3950.zing.cql.CQLParseException; +import org.z3950.zing.cql.CQLParser; +import org.z3950.zing.cql.utils.PrettyPrinter; + +/** + * + * @author jakub + */ +public class QueryTest { + + @Test + public void testSimpleQuery() { + Query query = new Query().prefix("bf", "http://bibframe.org/vocab/") + .form(new Select().var("?title")).where( + new Where(new TriplePattern("?work", "bf:title", "?title"))); + String expected = + "PREFIX bf: \n" + + "SELECT ?title\n" + + "WHERE {\n" + + " ?work bf:title ?title .\n" + + "}\n"; + PrettyPrinter writer = new PrettyPrinter(); + query.print(writer); + String sparql = writer.toString(); + out.println(sparql); + assertEquals(expected, sparql); + } + + @Test + public void testComplexQuery() { + Query query = new Query().prefix("bf", "http://bibframe.org/vocab/") + .form(new Select().var("?wtitle").var("?ititle")) + .where( + new Where( + new TriplePattern("?work", "bf:title", "?title"), + new Optional(new TriplePattern("?ins", "?bf:instanceOf", "?work"))) + ); + String expected = + "PREFIX bf: \n" + + "SELECT ?wtitle ?ititle\n" + + "WHERE {\n" + + " ?work bf:title ?title .\n" + + " OPTIONAL {\n" + + " ?ins ?bf:instanceOf ?work .\n" + + " }\n" + + "}\n"; + PrettyPrinter pp = new PrettyPrinter(); + query.print(pp); + String sparql = pp.toString(); + out.println(sparql); + assertEquals(expected, sparql); + } + + @Test + public void testCQLConversionTitle() throws CQLParseException, IOException { + CQLParser p = new CQLParser(); + CQLNode query = p.parse("bf:title = \"al gore\""); + SPARQLNodeVisitor visitor = new SPARQLNodeVisitor(); + query.traverse(visitor); + Query sparql = visitor.getQuery(); + PrettyPrinter pp = new PrettyPrinter(); + sparql.print(pp); + String expected = + "PREFIX bf: \n" + + "SELECT *\n" + + "WHERE {\n" + + " ?work a bf:Work .\n" + + " ?work bf:title ?o1 .\n" + + " FILTER contains(lcase(?o1), \"al gore\").\n" + + "}\n"; + out.println(pp); + assertEquals(expected, pp.toString()); + } + + @Test + public void testCQLConversionTitleAuthor() throws CQLParseException, IOException { + CQLParser p = new CQLParser(); + CQLNode query = p.parse("bf:title = \"al gore\" and \"bf:creator/bf:label\" = \"Stefoff, Rebecca\""); + SPARQLNodeVisitor visitor = new SPARQLNodeVisitor(); + query.traverse(visitor); + Query sparql = visitor.getQuery(); + PrettyPrinter pp = new PrettyPrinter(); + sparql.print(pp); + String expected = + "PREFIX bf: \n" + + "SELECT *\n" + + "WHERE {\n" + + " ?work a bf:Work .\n" + + " ?work bf:title ?o1 .\n" + + " FILTER contains(lcase(?o1), \"al gore\").\n" + + " ?work bf:creator/bf:label ?o2 .\n" + + " FILTER contains(lcase(?o2), \"stefoff, rebecca\").\n" + + "}\n"; + out.println(pp); + assertEquals(expected, pp.toString()); + } + +} -- 1.7.10.4