This is an automated email from the ASF dual-hosted git repository.

zjffdu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 660c476  [ZEPPELIN-3644] SPARQL-Interpreter support via Apache Jena ARQ
660c476 is described below

commit 660c476ca3fe622e820218eb4ddd114789bf277d
Author: hsteinmueller <harald.steinmuel...@gmail.com>
AuthorDate: Tue Nov 5 11:12:29 2019 +0000

    [ZEPPELIN-3644] SPARQL-Interpreter support via Apache Jena ARQ
    
    ### What is this PR for?
    Interpreter for SPARQL-queries on any SPARQL-endpoint via [Apache Jena 
ARQ](https://jena.apache.org/documentation/query/).
    
    [SPARQL](https://www.w3.org/TR/sparql11-query/) is an RDF query language 
able to retrieve and manipulate data stored in Resource Description Framework 
(RDF) format.
    
    ### What type of PR is it?
    [Feature]
    
    ### Todos
    * [ ] - Task
    
    ### What is the Jira issue?
    * https://issues.apache.org/jira/browse/ZEPPELIN-3644
    
    ### How should this be tested?
    * Unit tests are provided
    * https://travis-ci.org/hsteinmueller/zeppelin
    
    ### Screenshots (if appropriate)
    
![sparql-example](https://user-images.githubusercontent.com/4417940/66488249-ed1f3d00-eaad-11e9-84bd-b2d0f39438e4.PNG)
    
    ### Questions:
    * Does the licenses files need update? Yes, added in PR
    * Is there breaking changes for older versions? No
    * Does this needs documentation? Yes, added in PR
    
    Author: hsteinmueller <harald.steinmuel...@gmail.com>
    Author: Harald Steinmueller <harald.steinmuel...@gmail.com>
    
    Closes #3480 from hsteinmueller/sparql-interpreter and squashes the 
following commits:
    
    44f14bc50 [hsteinmueller] added support for different implementations
    720f7144f [Harald Steinmueller] Merge branch 'master' into 
sparql-interpreter
    475a94163 [hsteinmueller] renamed package from fuseki to sparql
    594a033ae [hsteinmueller] added support for cancel
    3e2120f48 [hsteinmueller] moved variable initialization to open()-method
    94dffd93e [hsteinmueller] Merge branch 'sparql-interpreter' of 
https://github.com/hsteinmueller/zeppelin into sparql-interpreter
    c5ea82750 [hsteinmueller] added removeDatatypes option
    5dbd27325 [Harald Steinmueller] Merge branch 'master' into 
sparql-interpreter
    85f48e92a [hsteinmueller] SPARQL-Interpreter for Apache Zeppelin
---
 .travis.yml                                        |   2 +-
 conf/interpreter-list                              |   1 +
 docs/_includes/themes/zeppelin/_navigation.html    |   1 +
 .../zeppelin/img/docs-img/sparql-example.png       | Bin 0 -> 68474 bytes
 docs/index.md                                      |   1 +
 docs/interpreter/sparql.md                         |  55 ++++++
 docs/usage/interpreter/installation.md             |   5 +
 pom.xml                                            |   1 +
 sparql/pom.xml                                     |  80 +++++++++
 .../apache/zeppelin/sparql/JenaInterpreter.java    | 143 +++++++++++++++
 .../org/apache/zeppelin/sparql/SparqlEngine.java   |  32 ++++
 .../apache/zeppelin/sparql/SparqlInterpreter.java  | 107 +++++++++++
 sparql/src/main/resources/interpreter-setting.json |  42 +++++
 .../zeppelin/sparql/SparqlJenaEngineTest.java      | 198 +++++++++++++++++++++
 sparql/src/test/resources/data.ttl                 |  18 ++
 zeppelin-distribution/src/bin_license/LICENSE      |   1 +
 zeppelin-web/bower.json                            |   1 +
 zeppelin-web/karma.conf.js                         |   1 +
 zeppelin-web/src/index.html                        |   1 +
 19 files changed, 689 insertions(+), 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 95fe8dd..8c48d3c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -51,7 +51,7 @@ services:
 env:
   global:
     # Interpreters does not required by zeppelin-server integration tests
-    - 
INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine'
+    - 
INTERPRETERS='!beam,!hbase,!pig,!jdbc,!file,!flink,!ignite,!kylin,!lens,!cassandra,!elasticsearch,!bigquery,!alluxio,!scio,!livy,!groovy,!sap,!java,!geode,!neo4j,!hazelcastjet,!submarine,!sparql'
 
 matrix:
   include:
diff --git a/conf/interpreter-list b/conf/interpreter-list
index 2e19ad6..05cb9ff 100644
--- a/conf/interpreter-list
+++ b/conf/interpreter-list
@@ -43,4 +43,5 @@ sap             org.apache.zeppelin:zeppelin-sap:0.9.0        
          SAP Supp
 scalding        org.apache.zeppelin:zeppelin-scalding_2.0.10:0.9.0      
Scalding interpreter
 scio            org.apache.zeppelin:zeppelin-scio:0.9.0                 Scio 
interpreter
 shell           org.apache.zeppelin:zeppelin-shell:0.9.0                Shell 
command
+sparql          org.apache.zeppelin:zeppelin-sparql:0.9.0               Sparql 
interpreter
 submarine       org.apache.zeppelin:zeppelin-submarine:0.9.0            
Submarine interpreter
diff --git a/docs/_includes/themes/zeppelin/_navigation.html 
b/docs/_includes/themes/zeppelin/_navigation.html
index 4858691..9b83369 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -153,6 +153,7 @@
                 <li><a 
href="{{BASE_PATH}}/interpreter/scalding.html">Scalding</a></li>
                 <li><a href="{{BASE_PATH}}/interpreter/scio.html">Scio</a></li>
                 <li><a 
href="{{BASE_PATH}}/interpreter/shell.html">Shell</a></li>
+                <li><a 
href="{{BASE_PATH}}/interpreter/sparql.html">Sparql</a></li>
                 <li><a 
href="{{BASE_PATH}}/interpreter/submarine.html">Submarine</a></li>
               </ul>
             </li>
diff --git a/docs/assets/themes/zeppelin/img/docs-img/sparql-example.png 
b/docs/assets/themes/zeppelin/img/docs-img/sparql-example.png
new file mode 100644
index 0000000..de39570
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/docs-img/sparql-example.png differ
diff --git a/docs/index.md b/docs/index.md
index 5481fb4..ad53721 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -158,6 +158,7 @@ limitations under the License.
   * [Scio](./interpreter/scio.html)
   * [Shell](./interpreter/shell.html)
   * [Spark](./interpreter/spark.html)
+  * [Sparql](./interpreter/sparql.html)
   * [Submarine](./interpreter/submarine.html)
 
 #### External Resources
diff --git a/docs/interpreter/sparql.md b/docs/interpreter/sparql.md
new file mode 100644
index 0000000..542853c
--- /dev/null
+++ b/docs/interpreter/sparql.md
@@ -0,0 +1,55 @@
+---
+layout: page
+title: "SPARQL Interpreter for Apache Zeppelin"
+description: "SPARQL is an RDF query language able to retrieve and manipulate 
data stored in Resource Description Framework (RDF) format. Apache Zeppelin 
uses Apache Jena"
+group: interpreter
+---
+<!--
+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.
+-->
+{% include JB/setup %}
+
+# SPARQL Interpreter for Apache Zeppelin
+
+<div id="toc"></div>
+
+## Overview
+[SPARQL](https://www.w3.org/TR/sparql11-query/) is an RDF query language able 
to retrieve and manipulate data stored in Resource Description Framework (RDF) 
format.
+Apache Zeppelin uses [Apache Jena](https://jena.apache.org/) to query 
SPARQL-Endpoints.
+
+To query your endpoint configure it in the Interpreter-Settings and use the 
**%sparql** interpreter.
+Then write your query in the paragraph.
+If you want the prefixes to replace the URI's, set the replaceURIs setting.
+
+## Configuration
+<table class="table-configuration">
+  <tr>
+    <th>Name</th>
+    <th>Default Value</th>
+    <th>Description</th>
+  </tr>
+  <tr>
+    <td>sparql.endpoint</td>
+    <td>http://dbpedia.org/sparql</td>
+    <td>Complete URL of the endpoint</td>
+  </tr>
+  <tr>
+    <td>sparql.replaceURIs</td>
+    <td>true</td>
+    <td>Replace the URIs in the result with the prefixes</td>
+  </tr>
+</table>
+
+## Example
+
+<img 
src="{{BASE_PATH}}/assets/themes/zeppelin/img/docs-img/sparql-example.png" 
width="100%"/>
\ No newline at end of file
diff --git a/docs/usage/interpreter/installation.md 
b/docs/usage/interpreter/installation.md
index 1ee708d..83c2094 100644
--- a/docs/usage/interpreter/installation.md
+++ b/docs/usage/interpreter/installation.md
@@ -244,6 +244,11 @@ You can also find the below community managed interpreter 
list in `conf/interpre
     <td>Shell command</td>
   </tr>
   <tr>
+    <td>sparql</td>
+    <td>org.apache.zeppelin:zeppelin-sparql:0.9.0</td>
+    <td>Sparql interpreter</td>
+  </tr>
+  <tr>
     <td>submarine</td>
     <td>org.apache.zeppelin:zeppelin-submarine:0.9.0</td>
     <td>Submarine interpreter</td>
diff --git a/pom.xml b/pom.xml
index ffe1802..2ec7755 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,7 @@
     <module>hazelcastjet</module>
     <module>geode</module>
     <module>ksql</module>
+    <module>sparql</module>
     <module>zeppelin-web</module>
     <module>zeppelin-server</module>
     <module>zeppelin-jupyter</module>
diff --git a/sparql/pom.xml b/sparql/pom.xml
new file mode 100644
index 0000000..abf6bc6
--- /dev/null
+++ b/sparql/pom.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  -->
+  
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"; 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/maven-v4_0_0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>zeppelin-interpreter-parent</artifactId>
+        <groupId>org.apache.zeppelin</groupId>
+        <version>0.9.0-SNAPSHOT</version>
+        <relativePath>../zeppelin-interpreter-parent/pom.xml</relativePath>
+    </parent>
+    
+    <groupId>org.apache.zeppelin</groupId>
+    <artifactId>zeppelin-sparql</artifactId>
+    <packaging>jar</packaging>
+    <version>0.9.0-SNAPSHOT</version>
+    <name>Zeppelin: Sparql interpreter</name>
+    <description>Zeppelin sparql support</description>
+
+    <properties>
+        <interpreter.name>sparql</interpreter.name>
+        <jena.version>3.12.0</jena.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.jena</groupId>
+            <artifactId>jena-arq</artifactId>
+            <version>${jena.version}</version>
+        </dependency>
+
+        <!-- test libraries -->
+        <dependency>
+            <groupId>org.apache.jena</groupId>
+            <artifactId>jena-fuseki-main</artifactId>
+            <version>${jena.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <artifactId>maven-enforcer-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-dependency-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-resources-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <artifactId>maven-shade-plugin</artifactId>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <skip>false</skip>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
\ No newline at end of file
diff --git 
a/sparql/src/main/java/org/apache/zeppelin/sparql/JenaInterpreter.java 
b/sparql/src/main/java/org/apache/zeppelin/sparql/JenaInterpreter.java
new file mode 100644
index 0000000..9faf869
--- /dev/null
+++ b/sparql/src/main/java/org/apache/zeppelin/sparql/JenaInterpreter.java
@@ -0,0 +1,143 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.sparql;
+
+import org.apache.http.HttpStatus;
+
+import org.apache.jena.query.QueryExecution;
+import org.apache.jena.query.QueryExecutionFactory;
+import org.apache.jena.query.QueryParseException;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.query.ResultSetFormatter;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.sparql.ARQException;
+import org.apache.jena.sparql.engine.http.QueryExceptionHTTP;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.ByteArrayOutputStream;
+import java.util.Map;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+
+
+/**
+ * Interpreter for SPARQL-Query via Apache Jena ARQ.
+ */
+public class JenaInterpreter implements SparqlEngine {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(JenaInterpreter.class);
+
+  private String serviceEndpoint;
+  private boolean replaceURIs;
+  private boolean removeDatatypes;
+
+  private QueryExecution queryExecution;
+
+  public JenaInterpreter(String serviceEndpoint, boolean replaceURIs, boolean 
removeDatatypes) {
+    this.serviceEndpoint = serviceEndpoint;
+    this.replaceURIs = replaceURIs;
+    this.removeDatatypes = removeDatatypes;
+  }
+
+  @Override
+  public InterpreterResult query(String query) {
+    LOGGER.info("SPARQL: Run Query '" + query + "' against " + 
serviceEndpoint);
+
+    try {
+      queryExecution = QueryExecutionFactory.sparqlService(serviceEndpoint, 
query);
+      PrefixMapping prefixMapping = 
queryExecution.getQuery().getPrefixMapping();
+
+      // execute query and get Results
+      ResultSet results = queryExecution.execSelect();
+
+      // transform ResultSet to TSV-String
+      ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+      ResultSetFormatter.outputAsTSV(outputStream, results);
+      String tsv = new String(outputStream.toByteArray());
+
+      if (replaceURIs) {
+        LOGGER.info("SPARQL: Replacing URIs");
+        tsv = replaceURIs(tsv, prefixMapping);
+      }
+
+      if (removeDatatypes) {
+        LOGGER.info("SPARQL: Removing datatypes");
+        tsv = removeDatatypes(tsv);
+      }
+
+      return new InterpreterResult(
+              InterpreterResult.Code.SUCCESS,
+              InterpreterResult.Type.TABLE,
+              tsv);
+    } catch (QueryParseException e) {
+      LOGGER.error(e.toString());
+      return new InterpreterResult(
+        InterpreterResult.Code.ERROR,
+        "Error: " + e.getMessage());
+    } catch (QueryExceptionHTTP e) {
+      LOGGER.error(e.toString());
+      int responseCode = e.getResponseCode();
+
+      if (responseCode == HttpStatus.SC_UNAUTHORIZED) {
+        return new InterpreterResult(
+          InterpreterResult.Code.ERROR,
+            "Unauthorized.");
+      } else if (responseCode == HttpStatus.SC_NOT_FOUND) {
+        return new InterpreterResult(
+          InterpreterResult.Code.ERROR,
+            "Endpoint not found, please check endpoint in the configuration.");
+      } else {
+        return new InterpreterResult(
+          InterpreterResult.Code.ERROR,
+            "Error: " + e.getMessage());
+      }
+    } catch (ARQException e) {
+      return new InterpreterResult(
+        InterpreterResult.Code.INCOMPLETE,
+          "Query cancelled.");
+    }
+  }
+
+  @Override
+  public void cancel() {
+    if (queryExecution != null) {
+      queryExecution.abort();
+    }
+  }
+
+  @Override
+  public void close() {
+    if (queryExecution != null) {
+      queryExecution.close();
+    }
+  }
+
+  private String replaceURIs(String tsv, PrefixMapping prefixMapping) {
+    Map<String, String> pmap = prefixMapping.getNsPrefixMap();
+    for (Map.Entry<String, String> entry : pmap.entrySet()) {
+      tsv = tsv.replaceAll(entry.getValue(), entry.getKey() + ":");
+    }
+    return tsv;
+  }
+
+  private String removeDatatypes(String tsv) {
+    // capture group: "($1)"^^<.+?>
+    return tsv.replaceAll("\"(.+?)\"\\^\\^\\<.+?\\>", "$1");
+  }
+}
diff --git a/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlEngine.java 
b/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlEngine.java
new file mode 100644
index 0000000..af1863e
--- /dev/null
+++ b/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlEngine.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.sparql;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+
+
+/**
+ * Interpreter for SPARQL-Query via Apache Jena ARQ.
+ */
+public interface SparqlEngine {
+  InterpreterResult query(String query);
+
+  void cancel();
+
+  void close();
+}
diff --git 
a/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlInterpreter.java 
b/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlInterpreter.java
new file mode 100644
index 0000000..eeb9955
--- /dev/null
+++ b/sparql/src/main/java/org/apache/zeppelin/sparql/SparqlInterpreter.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.sparql;
+
+import org.apache.commons.lang3.StringUtils;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Properties;
+
+import org.apache.zeppelin.interpreter.Interpreter;
+import org.apache.zeppelin.interpreter.InterpreterContext;
+import org.apache.zeppelin.interpreter.InterpreterResult;
+
+
+/**
+ * Interpreter for SPARQL-Query via Apache Jena ARQ.
+ */
+public class SparqlInterpreter extends Interpreter {
+  private static final Logger LOGGER = 
LoggerFactory.getLogger(SparqlInterpreter.class);
+
+  public static final String SPARQL_ENGINE_TYPE = "sparql.engine";
+  public static final String SPARQL_SERVICE_ENDPOINT = "sparql.endpoint";
+  public static final String SPARQL_REPLACE_URIS = "sparql.replaceURIs";
+  public static final String SPARQL_REMOVE_DATATYPES = 
"sparql.removeDatatypes";
+
+  public static final String ENGINE_TYPE_JENA = "jena";
+
+  public SparqlEngine engine;
+  
+  /**
+   * Sparql Engine Type.
+   */
+  public enum SparqlEngineType {
+    JENA {
+      @Override
+      public String toString() {
+        return ENGINE_TYPE_JENA;
+      }
+    }
+  }
+
+  public SparqlInterpreter(Properties properties) {
+    super(properties);
+  }
+
+  @Override
+  public void open() {
+    LOGGER.info("Properties: {}", getProperties());
+
+    String serviceEndpoint = getProperty(SPARQL_SERVICE_ENDPOINT);
+    boolean replaceURIs = getProperty(SPARQL_REPLACE_URIS) != null
+        && getProperty(SPARQL_REPLACE_URIS).equals("true");
+    boolean removeDatatypes = getProperty(SPARQL_REMOVE_DATATYPES) != null
+        && getProperty(SPARQL_REMOVE_DATATYPES).equals("true");
+    String engineType = getProperty(SPARQL_ENGINE_TYPE);
+
+    if (SparqlEngineType.JENA.toString().equals(engineType)) {
+      engine = new JenaInterpreter(serviceEndpoint, replaceURIs, 
removeDatatypes);
+    } 
+  }
+
+  @Override
+  public void close() {
+    engine.close();
+  }
+
+  @Override
+  public InterpreterResult interpret(String query, InterpreterContext context) 
{
+    if (StringUtils.isEmpty(query) || StringUtils.isEmpty(query.trim())) {
+      return new InterpreterResult(InterpreterResult.Code.SUCCESS);
+    }
+
+    return engine.query(query);
+  }
+
+  @Override
+  public void cancel(InterpreterContext context) {
+    engine.cancel();
+  }
+
+  @Override
+  public FormType getFormType() {
+    return FormType.SIMPLE;
+  }
+
+  @Override
+  public int getProgress(InterpreterContext context) {
+    return 0;
+  }
+}
diff --git a/sparql/src/main/resources/interpreter-setting.json 
b/sparql/src/main/resources/interpreter-setting.json
new file mode 100644
index 0000000..77bdbfb
--- /dev/null
+++ b/sparql/src/main/resources/interpreter-setting.json
@@ -0,0 +1,42 @@
+[
+  {
+    "group": "sparql",
+    "name": "sparql",
+    "className": "org.apache.zeppelin.sparql.SparqlInterpreter",
+    "properties": {
+      "sparql.engine": {
+        "envName": "SPARQL_ENGINE",
+        "propertyName": "sparql.engine",
+        "defaultValue": "jena",
+        "description": "The sparql engine to use for the queries. Default: 
jena",
+        "type": "string"
+      },
+      "sparql.endpoint": {
+        "envName": "SPARQL_SERVICE_ENDPOINT",
+        "propertyName": "sparql.endpoint",
+        "defaultValue": "http://dbpedia.org/sparql";,
+        "description": "Complete URL of the endpoint. Default: 
http://dbpedia.org/sparql";,
+        "type": "string"
+      },
+      "sparql.replaceURIs": {
+        "envName": "SPARQL_REPLACE_URI",
+        "propertyName": "sparql.replaceURIs",
+        "defaultValue": true,
+        "description": "Replace the URIs in the result with the prefixes. 
Default: true",
+        "type": "checkbox"
+      },
+      "sparql.removeDatatypes": {
+        "envName": "SPARQL_REMOVE_DATATYPES",
+        "propertyName": "sparql.removeDatatypes",
+        "defaultValue": true,
+        "description": "Remove the datatypes from Literals so Zeppelin can use 
the values. Default: true",
+        "type": "checkbox"
+      }
+    },
+    "editor": {
+      "language": "sparql",
+      "editOnDblClick": false,
+      "completionKey": "TAB"
+    }
+  }
+]
diff --git 
a/sparql/src/test/java/org/apache/zeppelin/sparql/SparqlJenaEngineTest.java 
b/sparql/src/test/java/org/apache/zeppelin/sparql/SparqlJenaEngineTest.java
new file mode 100644
index 0000000..15c8880
--- /dev/null
+++ b/sparql/src/test/java/org/apache/zeppelin/sparql/SparqlJenaEngineTest.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+package org.apache.zeppelin.sparql;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.jena.fuseki.Fuseki;
+import org.apache.jena.fuseki.main.FusekiServer;
+import org.apache.jena.fuseki.server.DataAccessPointRegistry;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.query.DatasetFactory;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.ModelFactory;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.Properties;
+
+import org.apache.zeppelin.interpreter.InterpreterResult;
+import org.apache.zeppelin.interpreter.InterpreterResult.Code;
+
+
+public class SparqlJenaEngineTest {
+  private static int port;
+
+  private static FusekiServer server;
+
+  private static final String ENGINE = "jena";
+  private static final String DATASET = "/dataset";
+  private static final String HOST = "http://localhost";;
+
+  private static Properties properties;
+
+  private static final String DATA_FILE = "data.ttl";
+
+  @BeforeClass
+  public static void setUp() {
+    port = Fuseki.choosePort();
+
+    Model model = ModelFactory.createDefaultModel();
+    model.read(DATA_FILE);
+    Dataset ds = DatasetFactory.create(model);
+
+    server = FusekiServer
+      .create()
+      .port(port)
+      .add(DATASET, ds)
+      .build();
+    DataAccessPointRegistry registry = server.getDataAccessPointRegistry();
+    assertTrue(registry.isRegistered(DATASET));
+    assertEquals(1, registry.size());
+    server.start();
+  }
+
+  @AfterClass
+  public static void tearDown() {
+    if (server != null) {
+      server.stop();
+    }
+  }
+
+  @Before
+  public void setUpProperties() {
+    properties = new Properties();
+    properties.put(SparqlInterpreter.SPARQL_ENGINE_TYPE, ENGINE);
+    properties.put(SparqlInterpreter.SPARQL_SERVICE_ENDPOINT, HOST + ":" + 
port + DATASET);
+  }
+
+  @Test
+  public void testWrongQuery() {
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret("SELECT * WHER {", 
null);
+    assertEquals(Code.ERROR, result.code());
+  }
+
+  @Test
+  public void testSuccessfulRawQuery() {
+    properties.put(SparqlInterpreter.SPARQL_REPLACE_URIS, "false");
+    properties.put(SparqlInterpreter.SPARQL_REMOVE_DATATYPES, "false");
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret(
+        "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>" +
+        "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>" +
+        "PREFIX foaf: <http://xmlns.com/foaf/0.1/>" +
+        "PREFIX rel: <http://www.perceive.net/schemas/relationship/>" +
+        "SELECT ?subject ?predicate ?object WHERE { ?subject ?predicate 
?object }", null);
+    assertEquals(Code.SUCCESS, result.code());
+
+    final String expected = "?subject\t?predicate\t?object\n" +
+        "<http://example.org/#spiderman>\t<http://xmlns.com/foaf/0.1/name>" +
+        "\t\"Человек-паук\"@ru\n<http://example.org/#spiderman>\t" +
+        "<http://xmlns.com/foaf/0.1/name>\t\"Spiderman\"\n" +
+        
"<http://example.org/#spiderman>\t<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>"
 +
+        
"\t<http://xmlns.com/foaf/0.1/Person>\n<http://example.org/#spiderman>\t" +
+        "<http://www.perceive.net/schemas/relationship/enemyOf>\t" +
+        
"<http://example.org/#green-goblin>\n<http://example.org/#spiderman>\t" +
+        "<http://example.org/stats#born>\t\"1962-10-15T14:00.00\"^^" +
+        
"<http://www.w3.org/2001/XMLSchema#dateTime>\n<http://example.org/#green-goblin>\t"
 +
+        "<http://xmlns.com/foaf/0.1/name>\t\"Green Goblin\"\n" +
+        "<http://example.org/#green-goblin>\t" +
+        "<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>" +
+        
"\t<http://xmlns.com/foaf/0.1/Person>\n<http://example.org/#green-goblin>\t" +
+        
"<http://www.perceive.net/schemas/relationship/enemyOf>\t<http://example.org/#spiderman>\n";
+    assertEquals(expected, result.message().get(0).getData());
+  }
+
+  @Test
+  public void testSuccessfulReplaceRemoveQuery() {
+    properties.put(SparqlInterpreter.SPARQL_REPLACE_URIS, "true");
+    properties.put(SparqlInterpreter.SPARQL_REMOVE_DATATYPES, "true");
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret(
+        "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>" +
+        "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>" +
+        "PREFIX foaf: <http://xmlns.com/foaf/0.1/>" +
+        "PREFIX rel: <http://www.perceive.net/schemas/relationship/>" +
+        "SELECT ?subject ?predicate ?object WHERE { ?subject ?predicate 
?object }", null);
+    assertEquals(Code.SUCCESS, result.code());
+
+    final String expected = "?subject\t?predicate\t?object\n" +
+        "<http://example.org/#spiderman>\t<foaf:name>" +
+        "\t\"Человек-паук\"@ru\n<http://example.org/#spiderman>\t" +
+        "<foaf:name>\t\"Spiderman\"\n" +
+        "<http://example.org/#spiderman>\t<rdf:type>" +
+        "\t<foaf:Person>\n<http://example.org/#spiderman>\t" +
+        "<rel:enemyOf>\t" +
+        
"<http://example.org/#green-goblin>\n<http://example.org/#spiderman>\t" +
+        "<http://example.org/stats#born>\t1962-10-15T14:00.00\n" +
+        "<http://example.org/#green-goblin>\t<foaf:name>\t\"Green Goblin\"\n" +
+        "<http://example.org/#green-goblin>\t" +
+        "<rdf:type>" +
+        "\t<foaf:Person>\n<http://example.org/#green-goblin>\t" +
+        "<rel:enemyOf>\t<http://example.org/#spiderman>\n";
+
+    assertEquals(expected, result.message().get(0).getData());
+  }
+
+  @Test
+  public void testRemoteEndpoint() {
+    properties.put(SparqlInterpreter.SPARQL_SERVICE_ENDPOINT, 
"http://dbpedia.org/sparql";);
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret(
+        "SELECT DISTINCT ?Concept WHERE {[] a ?Concept} LIMIT 1", null);
+    assertEquals(Code.SUCCESS, result.code());
+
+    final String expected = 
"?Concept\n<http://www.openlinksw.com/schemas/virtrdf#QuadMapFormat>\n";
+    assertEquals(expected, result.message().get(0).getData());
+  }
+
+  @Test
+  public void testEndpointMalformed() {
+    properties.put(SparqlInterpreter.SPARQL_SERVICE_ENDPOINT, "tsohlacol");
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret(
+        "SELECT ?subject ?predicate ?object WHERE { ?subject ?predicate 
?object }", null);
+    assertEquals(Code.ERROR, result.code());
+  }
+
+  @Test
+  public void testEndpointNotFound() {
+    properties.put(SparqlInterpreter.SPARQL_SERVICE_ENDPOINT, 
"http://tsohlacol/";);
+    SparqlInterpreter interpreter = new SparqlInterpreter(properties);
+    interpreter.open();
+
+    final InterpreterResult result = interpreter.interpret(
+        "SELECT ?subject ?predicate ?object WHERE { ?subject ?predicate 
?object }", null);
+    assertEquals(Code.ERROR, result.code());
+  }
+}
diff --git a/sparql/src/test/resources/data.ttl 
b/sparql/src/test/resources/data.ttl
new file mode 100644
index 0000000..984aaee
--- /dev/null
+++ b/sparql/src/test/resources/data.ttl
@@ -0,0 +1,18 @@
+@base <http://example.org/> .
+@prefix : <http://example.org/stats#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix foaf: <http://xmlns.com/foaf/0.1/> .
+@prefix rel: <http://www.perceive.net/schemas/relationship/> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+<#green-goblin>
+    rel:enemyOf <#spiderman> ;
+    a foaf:Person ;
+    foaf:name "Green Goblin" .
+
+<#spiderman>
+    :born "1962-10-15T14:00.00"^^xsd:dateTime ;
+    rel:enemyOf <#green-goblin> ;
+    a foaf:Person ;
+    foaf:name "Spiderman", "Человек-паук"@ru .
\ No newline at end of file
diff --git a/zeppelin-distribution/src/bin_license/LICENSE 
b/zeppelin-distribution/src/bin_license/LICENSE
index c115e9f..24880ff 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -37,6 +37,7 @@ The following components are provided under Apache License.
     (Apache 2.0) Apache Kylin (http://kylin.apache.org/)
     (Apache 2.0) Apache Lens (http://lens.apache.org/)
     (Apache 2.0) Apache Flink (http://flink.apache.org/)
+    (Apache 2.0) Apache Jena (https://jena.apache.org/)
     (Apache 2.0) Apache Beam (http://beam.apache.org/)
     (Apache 2.0) Apache Thrift 0.12.0 (org.apache.thrift:libthrift:0.12.0 - 
http://thrift.apache.org/)
     (Apache 2.0) Apache Lucene (https://lucene.apache.org/)
diff --git a/zeppelin-web/bower.json b/zeppelin-web/bower.json
index 1e9c480..b2cee2a 100644
--- a/zeppelin-web/bower.json
+++ b/zeppelin-web/bower.json
@@ -53,6 +53,7 @@
         "src-noconflict/mode-pig.js",
         "src-noconflict/mode-sh.js",
         "src-noconflict/mode-r.js",
+        "src-noconflict/mode-sparql.js",
         "src-noconflict/keybinding-emacs.js",
         "src-noconflict/ext-language_tools.js",
         "src-noconflict/theme-chrome.js"
diff --git a/zeppelin-web/karma.conf.js b/zeppelin-web/karma.conf.js
index 5daceb9..6ceef8d 100644
--- a/zeppelin-web/karma.conf.js
+++ b/zeppelin-web/karma.conf.js
@@ -62,6 +62,7 @@ module.exports = function(config) {
       'bower_components/ace-builds/src-noconflict/mode-pig.js',
       'bower_components/ace-builds/src-noconflict/mode-sh.js',
       'bower_components/ace-builds/src-noconflict/mode-r.js',
+      'bower_components/ace-builds/src-noconflict/mode-sparql.js',
       'bower_components/ace-builds/src-noconflict/keybinding-emacs.js',
       'bower_components/ace-builds/src-noconflict/ext-language_tools.js',
       'bower_components/ace-builds/src-noconflict/theme-chrome.js',
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index fb831c8..6ddcdbb 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -186,6 +186,7 @@ limitations under the License.
     <script 
src="bower_components/ace-builds/src-noconflict/mode-pig.js"></script>
     <script 
src="bower_components/ace-builds/src-noconflict/mode-sh.js"></script>
     <script 
src="bower_components/ace-builds/src-noconflict/mode-r.js"></script>
+    <script 
src="bower_components/ace-builds/src-noconflict/mode-sparql.js"></script>
     <script 
src="bower_components/ace-builds/src-noconflict/keybinding-emacs.js"></script>
     <script 
src="bower_components/ace-builds/src-noconflict/ext-language_tools.js"></script>
     <script 
src="bower_components/ace-builds/src-noconflict/theme-chrome.js"></script>

Reply via email to