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)  ### 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>