SPARQL builder and CQL-to-SPARQL converter SUP-652
authorJakub Skoczen <jakub@indexdata.dk>
Wed, 15 Oct 2014 14:52:34 +0000 (16:52 +0200)
committerJakub Skoczen <jakub@indexdata.dk>
Wed, 15 Oct 2014 14:52:34 +0000 (16:52 +0200)
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.

16 files changed:
src/main/java/org/z3950/zing/cql/sparql/Filter.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Form.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/GraphPattern.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/GraphPatternSet.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Optional.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Prefix.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Query.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/SPARQLNodeVisitor.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Select.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/SelectQuery.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Sparqler.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/TriplePattern.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/sparql/Where.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/utils/PrettyPrinter.java [new file with mode: 0644]
src/main/java/org/z3950/zing/cql/utils/Printable.java [new file with mode: 0644]
src/test/java/org/z3950/zing/cql/sparql/QueryTest.java [new file with mode: 0644]

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 (file)
index 0000000..29f6e89
--- /dev/null
@@ -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 (file)
index 0000000..8b3a777
--- /dev/null
@@ -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 (file)
index 0000000..2acded2
--- /dev/null
@@ -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 (file)
index 0000000..859d91c
--- /dev/null
@@ -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<GraphPattern> patterns = new LinkedList<GraphPattern>();
+
+  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 (file)
index 0000000..88d51cc
--- /dev/null
@@ -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 (file)
index 0000000..5218969
--- /dev/null
@@ -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 (file)
index 0000000..0f3a213
--- /dev/null
@@ -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<String, Prefix> prefixes = new TreeMap<String, Prefix>();
+  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 (file)
index 0000000..2fe37d6
--- /dev/null
@@ -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 (file)
index 0000000..31a4e2e
--- /dev/null
@@ -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<String> variables = new LinkedList<String>();
+  
+  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 (file)
index 0000000..3b13fe2
--- /dev/null
@@ -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 (file)
index 0000000..6b06a57
--- /dev/null
@@ -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 (file)
index 0000000..88430b2
--- /dev/null
@@ -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 (file)
index 0000000..fa5479c
--- /dev/null
@@ -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 (file)
index 0000000..8ed9bfc
--- /dev/null
@@ -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 (file)
index 0000000..26fc05b
--- /dev/null
@@ -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 (file)
index 0000000..08b5e84
--- /dev/null
@@ -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: <http://bibframe.org/vocab/>\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: <http://bibframe.org/vocab/>\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: <http://bibframe.org/vocab/>\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: <http://bibframe.org/vocab/>\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());
+  }
+  
+}