You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2016/10/03 10:36:13 UTC
[41/41] jena git commit: JENA-1208: Provide controls for JSON-LD
output.
JENA-1208: Provide controls for JSON-LD output.
Merge commit 'refs/pull/139/head' of github.com:apache/jena
This closes #139.
Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/22b2efbd
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/22b2efbd
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/22b2efbd
Branch: refs/heads/master
Commit: 22b2efbdef584fff47e5f64959f9947c4b568716
Parents: 85462d7 5bf2863
Author: Andy Seaborne <an...@apache.org>
Authored: Mon Oct 3 10:48:10 2016 +0100
Committer: Andy Seaborne <an...@apache.org>
Committed: Mon Oct 3 10:48:10 2016 +0100
----------------------------------------------------------------------
.../arq/examples/riot/ExJsonLD.java | 380 ++++++++++++++
.../apache/jena/riot/JsonLDWriteContext.java | 119 +++++
.../java/org/apache/jena/riot/RDFFormat.java | 67 ++-
.../org/apache/jena/riot/RDFWriterRegistry.java | 33 +-
.../apache/jena/riot/out/JenaRDF2JSONLD.java | 100 ----
.../org/apache/jena/riot/out/JsonLDWriter.java | 173 -------
.../apache/jena/riot/writer/JenaRDF2JSONLD.java | 101 ++++
.../apache/jena/riot/writer/JsonLDWriter.java | 413 +++++++++++++++
.../jena/riot/writer/TestJsonLDWriter.java | 502 +++++++++++++++++++
jena-parent/pom.xml | 2 +-
10 files changed, 1602 insertions(+), 288 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/jena/blob/22b2efbd/jena-arq/src-examples/arq/examples/riot/ExJsonLD.java
----------------------------------------------------------------------
diff --cc jena-arq/src-examples/arq/examples/riot/ExJsonLD.java
index 0000000,1de5cdd..677e584
mode 000000,100644..100644
--- a/jena-arq/src-examples/arq/examples/riot/ExJsonLD.java
+++ b/jena-arq/src-examples/arq/examples/riot/ExJsonLD.java
@@@ -1,0 -1,383 +1,380 @@@
+ /**
+ * 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 arq.examples.riot;
+
+ import java.io.ByteArrayOutputStream;
+ import java.io.IOException;
+ import java.io.OutputStream;
+ import java.io.StringReader;
+
+ import org.apache.jena.query.DatasetFactory;
+ import org.apache.jena.rdf.model.Model ;
+ import org.apache.jena.rdf.model.ModelFactory;
+ import org.apache.jena.rdf.model.Resource;
+ import org.apache.jena.riot.JsonLDWriteContext;
+ import org.apache.jena.riot.RDFDataMgr;
+ import org.apache.jena.riot.RDFFormat;
+ import org.apache.jena.riot.WriterDatasetRIOT;
+ import org.apache.jena.riot.system.PrefixMap;
+ import org.apache.jena.riot.system.RiotLib;
+ import org.apache.jena.sparql.core.DatasetGraph;
+ import org.apache.jena.sparql.util.Context;
+ import org.apache.jena.vocabulary.RDF;
+ import org.apache.jena.vocabulary.RDFS;
+
+ import com.github.jsonldjava.core.JsonLdOptions;
+
+ /** Example writing as JSON-LD */
+ public class ExJsonLD
+ {
+ public static void main(String[] args) {
+ (new ExJsonLD()).doIt();
+ }
+
+ void doIt() {
+ doSimpleStuff();
+ moreControl();
+ }
+
+ /**
+ * Simple stuff.
+ *
+ * output using defaults,
+ * in "expanded"", compacted" or "flattened" format
+ * (framed is more difficult, not handled here)
+ */
+ void doSimpleStuff() {
+ Model m = aSimpleModel();
+
+ // to get a default output: just do like for any other lang
+ System.out.println("--- DEFAULT ---");
+ m.write(System.out, "JSON-LD");
+
+ // same thing, using the more modern RDFDataMgr, and specifying the RDFFormat
+ System.out.println("\n--- DEFAULT ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD);
+
+ // output can be pretty (with line breaks), or not
+ System.out.println("\n--- DEFAULT, PRETTY (same as above, BTW) ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD_PRETTY);
+
+ System.out.println("\n--- DEFAULT, FLAT ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD_FLAT);
+
+ // all these default outputs use the JsonLD "Compact" format
+ // (note the "@context" node in the output)
+
+ // if prefixes are defined in the model,
+ // they are used in computing the @context,
+ // and corresponding values are displayed as prefix:localname
+ // (note something nice wrt prefixes in jsonld: look in the output at the value of "seeAlso")
+ m.setNsPrefix("ex", "http://www.ex.com/");
+ m.setNsPrefix("sh", "http://schema.org/");
+ System.out.println("\n--- DEFAULT, model including prefix mappings ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD_PRETTY);
+
+ // Besides "Compact", JSON-LD defines the following kinds of outputs: expanded, flattened, and framed
+ // For each of them, there is a dedicated RDFFormat -- actually, 2 of them (one pretty, one flat)
+ // As previously seen, RDFFormat.JSONLD is just an alias of RDFFormat.JSONLD_COMPACT_PRETYY
+ // Let's try the other ones:
+
+ // Expand is the fastest one
+ // no @context in it
+ System.out.println("\n--- EXPAND ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD_EXPAND_PRETTY);
+
+ // flatten has an @context node
+ System.out.println("\n--- FLATTEN ---");
+ RDFDataMgr.write(System.out, m, RDFFormat.JSONLD_FLATTEN_PRETTY);
+
+ // framed requires some more parameters to run, we'll get back to it later
+ }
+
+
+ /**
+ * To get more control about the output,
+ * we have to use a mechanism provided by jena to pass information
+ * to the writing process
+ *
+ * This requires a few lines of code, see {@link #write(DatasetGraph, RDFFormat, Context)}
+ *
+ * Here we use this write method to see what can be customized.
+ */
+ public void moreControl() {
+ Model m = aSimpleModel();
+ m.setNsPrefix("ex", "http://www.ex.com/");
+ m.setNsPrefix("sh", "http://schema.org/");
+
+ // the write method takes a DatasetGraph as input to represent the data that we want to output
+ // Let's create one from our model:
+ DatasetGraph g = DatasetFactory.create(m).asDatasetGraph();
+
+ // and let's use the write method to output the data in json-ld compact format,
+ // passing a null Context for the moment
+ // (remember, "Context" here is not to be confused with "@context" in JSON-LD,
+ // see {@link #write(DatasetGraph, RDFFormat, Context)})
+ System.out.println("\n--- COMPACT with a null Context: same result as default ---");
+ write(g, RDFFormat.JSONLD_COMPACT_PRETTY, null);
+
+ // A Context is just a way to pass implementation-specific parameters as named values
+ // to a given general interface (the WriterDatasetRIOT, in this case).
+ // In order to make it easier to define the named values relevant here,
+ // there is a subclass of Context that defines setters for the values:
+ // the JsonLDWriteContext class
+
+ // so, the way to proceed is:
+ // JsonLDWriteContext ctx = new JsonLDWriteContext();
+ // ctx.setSomething(...)
+ // write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+
+ // let's see now what can be customized with the JsonLDWriteContext object
+
+ controllingAtContext();
+ settingAtContextToURI();
+ frame();
+ controllingJsonLDApiOptions();
+ }
+
+
+ /**
+ * Shows how to customize the "@context" in "compacted" and "flattened" format.
+ *
+ * To set it to the URI of a vocab, {@link #settingAtContextToURI()}
+ */
+ void controllingAtContext() {
+ Model m = aSimpleModel();
+ m.setNsPrefix("ex", "http://www.ex.com/");
+ m.setNsPrefix("sh", "http://schema.org/");
+ DatasetGraph g = DatasetFactory.create(m).asDatasetGraph();
+ JsonLDWriteContext ctx = new JsonLDWriteContext();
+
+ // When no value for the "@context" is provided,
+ // Jena computes one from the defined prefixes, and from the RDF content.
+ // This default is probably good enough in most of the cases,
+ // but you may want to customize it.
+ // Or, if it is always the same one, you may consider that computing it again and again
+ // (each time that you output data), is a waste of time.
+ // (the computing of the "@context" implies to loop through all the triples).
+ // You may therefore want to compute it once for all, and to pass it to the output process
+
+ // To pass a given "@context" to the writing process,
+ // you pass the corresponding value as a JSON string
+ // using the setJsonLDContext(String) method.
+ // (Alternatively, you can directly pass the object expected by the JSON-LD API)
+
+ // For instance, we can pass a simple context
+ // that uses jsonld "@vocab" keyord to set the "default vocabulary"
+ // to schema.org.
+ String atContextAsJson = "{\"@vocab\":\"http://schema.org/\"}";
+ ctx.setJsonLDContext(atContextAsJson);
+ System.out.println("\n--- COMPACT using a Context that defines @vocab ---");
+ write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+ }
+
+ /**
+ * Shows how to set "@context" to a URI.
+ */
+ void settingAtContextToURI() {
+ // One thing you'll probably want to do is to set the "@context" to the URL of a file
+ // containing the actual JSON-LD context.
+
+ // Let's take one Model that only uses schema.org terms,
+ // and let's try to set the "@Context" to the URL of schema.org
+ // "@context" : "http://schema.org/"
+
+ Model m = aModelThatOnlyUsesSchemaDotOrg();
+ DatasetGraph g = DatasetFactory.create(m).asDatasetGraph();
+ JsonLDWriteContext ctx = new JsonLDWriteContext();
+
+ // The following should work, but unfortunately it doesn't (with JSONLD-java 0.8.3):
+ ctx.setJsonLDContext("\"http://schema.org/\"");
+ System.out.println("\n--- Setting the context to a URI, WRONG WAY: it's slow, and the output is not JSON-LD. Sorry about that. ---");
+ write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+
+ // But don't worry (be happy):
+ // - there is a solution
+ // - and what we tried is not what we would want to do, anyway.
+
+ // The output process needs to have the content of the "@context" at hand
+ // in order to compute the output. So, if passing the URL of the vocab,
+ // the output process must download the vocab before anything.
+ // (that's why the previous attempt was slow)
+ // -> that would not be an very efficient way to output your data.
+ // -> it doesn't work, (with JSONLD-java 0.8.3), but no regret.
+
+ // To achieve the expected result,
+ // you have to do 2 things:
+
+ // 1)
+ // you have to pass the dereferenced content of http://schema.org/
+ // - or the relevant subset of it (as we only use very few terms).
+ // Here it is:
+ String atContextAsJson = "{\"name\":{\"@id\":\"http://schema.org/name\"},\"Person\": {\"@id\": \"http://schema.org/Person\"}}";
+ ctx.setJsonLDContext(atContextAsJson);
+ // Alternatively, we could just pass "null":
+ // ctx.setJsonLDContext(null);
+ // and let jena compute the context (as the model only uses schema.org vocab)
+
+ // 2)
+ // and then you pass the schema.org url using:
+ ctx.setJsonLDContextSubstitution("\"http://schema.org/\"");
+
+ // To summarize:
+ // - ctx.setJsonLDContext allows to define the @context used to compute the output
+ // - ctx.setJsonLDContextSubstitution allows to change the value of the "@context" in the output
+ System.out.println("\n--- COMPACT with @context replaced by schema.org URI ---");
+ write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+
+ // Final note: BEWARE when replacing the context:
+ // if you let some things undefined, the output will be json, not jsonld
+
+ }
+
+ /**
+ * Shows how to apply a frame to the output RDF data
+ */
+ void frame() {
+ // a "frame" is a specific graph layout that is applied to output data
+ // It can be used to filter the output data.
+ // In this example, we show how to output only the resources of a givn rdf:type
+
+ Model m = ModelFactory.createDefaultModel();
+ String ns = "http://schema.org/";
+ Resource person = m.createResource(ns + "Person");
+ Resource s = m.createResource();
+ m.add(s, m.createProperty(ns + "name"), "Jane Doe");
+ m.add(s, m.createProperty(ns + "url"), "http://www.janedoe.com");
+ m.add(s, m.createProperty(ns + "jobTitle"), "Professor");
+ m.add(s, RDF.type, person);
+ s = m.createResource();
+ m.add(s, m.createProperty(ns + "name"), "Gado Salamatou");
+ m.add(s, m.createProperty(ns + "url"), "http://www.salamatou.com");
+ m.add(s, RDF.type, person);
+ s = m.createResource();
+ m.add(s, m.createProperty(ns + "name"), "Not a person");
+ m.add(s, RDF.type, m.createResource(ns + "Event"));
+
+ DatasetGraph g = DatasetFactory.create(m).asDatasetGraph();
+ JsonLDWriteContext ctx = new JsonLDWriteContext();
+
+ // only output the persons using a frame
+
+ String frame = "{\"@type\" : \"http://schema.org/Person\"}";
+ ctx.setFrame(frame);
+ System.out.println("\n--- Using frame to select resources to be output: only output persons ---");
+ write(g, RDFFormat.JSONLD_FRAME_PRETTY, ctx);
+ }
+
+ /**
+ * the JSON-LD java API (that jena uses for JSON-LD I/O) defines a set of options
+ * that can be customized
+ */
+ void controllingJsonLDApiOptions() {
+ Model m = aSimpleModel();
+ m.setNsPrefix("ex", "http://www.ex.com/");
+ m.setNsPrefix("sh", "http://schema.org/");
+ DatasetGraph g = DatasetFactory.create(m).asDatasetGraph();
+ JsonLDWriteContext ctx = new JsonLDWriteContext();
+ JsonLdOptions opts = new JsonLdOptions();
+ ctx.setOptions(opts);
+ opts.setCompactArrays(false); // default is true
+ System.out.println("\n--- COMPACT with CompactArrays false: there is an @graph node");
+ write(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+ }
+
+ /**
+ * Write RDF data as JSON-LD.
+ *
+ * To get more control about the output,
+ * we have to use a mechanism provided by jena to pass information
+ * to the writing process (cf. org.apache.jena.riot.WriterDatasetRIOT and the "Context" mechanism).
+ * For that, we have to create a WriterDatasetRIOT (a one-time use object)
+ * and we pass a "Context" object (not to be confused with the "@context" in JSON-LD) as argument to its write method
+ *
+ * @param out
+ * @param f RDFFormat of the output, eg. RDFFormat.JSONLD_COMPACT_PRETTY
+ * @param g the data that we want to output as JSON-LD
+ * @param ctx the object that allows to control the writing process (a set of parameters)
+ */
+ void write(OutputStream out, DatasetGraph g, RDFFormat f, Context ctx) {
+ // create a WriterDatasetRIOT with the RDFFormat
+ WriterDatasetRIOT w = RDFDataMgr.createDatasetWriter(f) ;
+ PrefixMap pm = RiotLib.prefixMap(g);
+ String base = null;
+ w.write(out, g, pm, base, ctx) ;
+ }
+
+ /** Write RDF data to the console */
+ private void write(DatasetGraph g, RDFFormat f, Context ctx) {
+ write(System.out, g, f, ctx);
+ }
+
+ // following 2 methods: if you want to test
+ // that everything is OK in a roundtrip: model -> jsonld -> model
+ // something like:
+ // String jsonld = write2String(g, RDFFormat.JSONLD_COMPACT_PRETTY, ctx);
+ // Model m2 = parse(jsonld);
+ // System.out.println("ISO : " + m.isIsomorphicWith(m2));
+
+ /** Write RDF data into a String */
+ private String write2String(DatasetGraph g, RDFFormat f, Context ctx) {
- try {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
++ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ write(out, g, f, ctx);
+ out.flush();
- String x = out.toString("UTF-8");
- out.close();
- return x;
++ return out.toString("UTF-8");
+ } catch (IOException e) { throw new RuntimeException(e); }
+ }
+
+ /** Parse a jsonld string into a Model */
+ private Model parse(String jsonld) {
+ Model m = ModelFactory.createDefaultModel();
+ StringReader reader = new StringReader(jsonld);
+ m.read(reader, null, "JSON-LD");
+ return m;
+ }
+
+ private Model aSimpleModel() {
+ Model m = ModelFactory.createDefaultModel();
+ String ns = "http://schema.org/";
+ Resource person = m.createResource(ns + "Person");
+ Resource s;
+ s = m.createResource("http://www.ex.com/janedoe");
+ m.add(s, m.createProperty(ns + "name"), "Jane Doe");
+ m.add(s, RDF.type, person);
+ m.add(s, RDFS.seeAlso, m.createResource("http://www.ex.com/janedoe/moreinfo"));
+ // m.add(s, m.createProperty(ns + "url"), "http://www.janedoe.com");
+ // m.add(s, m.createProperty(ns + "jobTitle"), "Professor");
+ // s = m.createResource();
+ // m.add(s, m.createProperty(ns + "name"), "Salamatou Gado");
+ // m.add(s, m.createProperty(ns + "url"), "http://www.salamatou.com");
+ return m;
+ }
+
+ private Model aModelThatOnlyUsesSchemaDotOrg() {
+ Model m = ModelFactory.createDefaultModel();
+ String ns = "http://schema.org/";
+ Resource person = m.createResource(ns + "Person");
+ Resource s;
+ s = m.createResource("http://www.ex.com/janedoe");
+ m.add(s, m.createProperty(ns + "name"), "Jane Doe");
+ m.add(s, RDF.type, person);
+ return m;
+ }
+ }
+
http://git-wip-us.apache.org/repos/asf/jena/blob/22b2efbd/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
----------------------------------------------------------------------
diff --cc jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
index 0000000,601a34e..d2e0b29
mode 000000,100644..100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/JsonLDWriteContext.java
@@@ -1,0 -1,118 +1,119 @@@
+ /**
+ * 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.jena.riot;
+
+ import java.io.OutputStream;
+
++import org.apache.jena.riot.system.PrefixMap ;
+ import org.apache.jena.riot.writer.JsonLDWriter;
+ import org.apache.jena.sparql.core.DatasetGraph;
+ import org.apache.jena.sparql.util.Context;
+
+ import com.github.jsonldjava.core.JsonLdOptions;
+
+ /**
+ * Set of parameters that can be used to control the writing of JSON-LD.
+ *
+ * This class provides setters to define a "Context" suitable to be passed as
- * last argument to {@link org.apache.jena.riot.WriterDatasetRIOT#write(OutputStream, DatasetGraph, String, Context)}
++ * last argument to {@link org.apache.jena.riot.WriterDatasetRIOT#write(OutputStream, DatasetGraph, PrefixMap, String, Context)}
+ * when the WriterDatasetRIOT has been created with one of the JSON-LD RDFFormat variants (that is, when it is
+ * an instance of {@link org.apache.jena.riot.writer.JsonLDWriter})
+ *
+ * Parameters that are actually useful depend on the JSON-LD output variant.
+ *
+ * None of them is required (default values being used), except for the "frame" one,
+ * when outputting using JSON-LD "frame" output variant.
+ *
+ */
+ public class JsonLDWriteContext extends Context {
+ /**
+ * Set the JSON-LD java API's options
+ *
+ * If not set, a default value is used. (Note that this default
+ * is not the same as the one used by JSON-LD java API).
+ *
+ * @param opts the options as defined by the JSON-LD java API
+ */
+ public void setOptions(JsonLdOptions opts) {
+ set(JsonLDWriter.JSONLD_OPTIONS, opts);
+ }
+
+ /**
+ * Set the value of the JSON-LD "@context" node, used in "Compact" and "Flattened" JSON-LD outputs.
+ *
+ * Only useful for "Compact" and "Flattened" JSON-LD outputs, and not required: if not set,
+ * a value for the "@Context" node is computed, based on the content of the dataset and its prefix mappings.
+ *
+ * @param jsonLdContext the value of the "@context" node (a JSON value). Note that the use of an URI to pass an external context is not supported (as of JSONLD-java API 0.8.3)
+ * @see #setJsonLDContextSubstitution(String) for a way to overcome this problem.
+ *
+ * @see #setJsonLDContext(Object)
+ */
+ public void setJsonLDContext(String jsonLdContext) {
+ set(JsonLDWriter.JSONLD_CONTEXT, jsonLdContext);
+ }
+
+ /**
+ * Set the value of the JSON-LD "@context" node, used in "Compact" and "Flattened" JSON-LD outputs.
+ *
+ * Only useful for "Compact" and "Flattened" JSON-LD outputs, and not required: if not set,
+ * a value for the "@Context" node is computed, based on the content of the dataset and its prefix mappings.
+ *
+ * @param jsonLdContext the context as expected by JSON-LD java API. As of JSON-LD java 0.8.3, a Map
+ * defining the properties and the prefixes is OK. Note that the use an URI to pass an external context is not supported (JSONLD-java RDF 0.8.3)
+ * @see #setJsonLDContextSubstitution(String) for a way to overcome this problem.
+ *
+ * @see #setJsonLDContext(String)
+ */
+ public void setJsonLDContext(Object jsonLdContext) {
+ set(JsonLDWriter.JSONLD_CONTEXT, jsonLdContext);
+ }
+
+ /**
+ * Allow to replace the content of the "@context" node with a given value.
+ *
+ * This is useful, for instance, to allow to set the @content in the output to an URI, such as "@context": "http://schema.org/"
+ * Note that the actual content at this URI is NOT used when computing the output.
+ * The context used to compute the JSONLD output is the one normally used (as defined by a call to -
+ * or the lack of call to - setJsonLdContext)
+ *
+ * Only useful for "Compact" and "Flattened" JSON-LD outputs, and not required
+ *
+ * @param jsonLdContext the value of the "@context" node. Note the string is supposed to be a JSON Value: if passing an URI, the String must be quoted.
+ */
+ public void setJsonLDContextSubstitution(String jsonLdContext) {
+ set(JsonLDWriter.JSONLD_CONTEXT_SUBSTITUTION, jsonLdContext);
+ }
+
+ /**
+ * Set the frame used in a "Frame" output
+ * @param frame the Json Object used as frame for the "frame" output
+ */
+ public void setFrame(String frame) {
+ set(JsonLDWriter.JSONLD_FRAME, frame);
+ }
+
+ /**
+ * Set the frame used in a "Frame" output
+ * @param frame the frame Object expected by the JSON-LD java API
+ */
+ public void setFrame(Object frame) {
+ set(JsonLDWriter.JSONLD_FRAME, frame);
+ }
+
+ }
http://git-wip-us.apache.org/repos/asf/jena/blob/22b2efbd/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
----------------------------------------------------------------------
diff --cc jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
index 0000000,581c4bb..54ff5fb
mode 000000,100644..100644
--- a/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
+++ b/jena-arq/src/main/java/org/apache/jena/riot/writer/JsonLDWriter.java
@@@ -1,0 -1,411 +1,413 @@@
+ /**
+ * 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.jena.riot.writer ;
+
+ import static org.apache.jena.rdf.model.impl.Util.isLangString;
+ import static org.apache.jena.rdf.model.impl.Util.isSimpleString;
+
+ import java.io.IOException ;
+ import java.io.OutputStream ;
+ import java.io.OutputStreamWriter ;
+ import java.io.Writer ;
+ import java.util.LinkedHashMap;
+ import java.util.Map;
+ import java.util.Map.Entry;
+ import java.util.function.Consumer;
+
+ import org.apache.jena.atlas.io.IO ;
+ import org.apache.jena.atlas.iterator.Iter ;
+ import org.apache.jena.atlas.lib.Chars ;
+ import org.apache.jena.atlas.lib.Pair;
+ import org.apache.jena.graph.Graph ;
+ import org.apache.jena.graph.Node ;
+ import org.apache.jena.graph.Triple ;
+ import org.apache.jena.iri.IRI ;
+ import org.apache.jena.riot.Lang ;
+ import org.apache.jena.riot.RDFFormat ;
+ import org.apache.jena.riot.RiotException ;
++import org.apache.jena.riot.WriterDatasetRIOT ;
+ import org.apache.jena.riot.system.PrefixMap ;
+ import org.apache.jena.riot.system.PrefixMapFactory;
+ import org.apache.jena.sparql.core.DatasetGraph ;
+ import org.apache.jena.sparql.util.Context ;
+ import org.apache.jena.sparql.util.Symbol;
+ import org.apache.jena.vocabulary.RDF ;
+
+ import com.fasterxml.jackson.core.JsonGenerationException ;
+ import com.fasterxml.jackson.core.JsonParseException;
+ import com.fasterxml.jackson.databind.JsonMappingException ;
+ import com.github.jsonldjava.core.JsonLdApi;
+ import com.github.jsonldjava.core.JsonLdError ;
+ import com.github.jsonldjava.core.JsonLdOptions ;
+ import com.github.jsonldjava.core.JsonLdProcessor ;
+ import com.github.jsonldjava.core.RDFDataset;
+ import com.github.jsonldjava.utils.JsonUtils ;
+
+ /**
+ * Writer that prints out JSON-LD.
+ *
+ * By default, the output is "compact" (in JSON-LD terminology), and the JSON is "pretty" (using line breaks).
+ * One can choose another form using one of the dedicated RDFFormats (JSONLD_EXPAND_PRETTY, etc.).
+ *
+ * For formats using a context (that is, which have an "@context" node), (compact and expand),
+ * this automatically generates a default one.
+ *
+ * One can pass a jsonld context using the (jena) Context mechanism, defining a (jena) Context
+ * (sorry for this clash of "contexts"), (cf. last argument in
- * {@link java.io.OutputStream.WriterDatasetRIOT#write(OutputStream out, DatasetGraph datasetGraph, PrefixMap prefixMap, String baseURI, Context context)})
++ * {@link WriterDatasetRIOT#write(OutputStream out, DatasetGraph datasetGraph, PrefixMap prefixMap, String baseURI, Context context)})
+ * with:
+ * <pre>
+ * Context jenaContext = new Context()
+ * jenaCtx.set(JsonLDWriter.JSONLD_CONTEXT, contextAsJsonString);
+ * </pre>
+ * where contextAsJsonString is a JSON string containing the value of the "@context".
+ *
+ * It is possible to change the content of the "@context" node in the output using the {@link #JSONLD_CONTEXT_SUBSTITUTION} Symbol.
+ *
+ * For a frame output, one must pass a frame in the jenaContext using the {@link #JSONLD_FRAME} Symbol.
+ *
+ * It is also possible to define the different options supported
+ * by JSONLD-java using the {@link #JSONLD_OPTIONS} Symbol
+ *
+ * The {@link org.apache.jena.riot.JsonLDWriteContext} is a convenience class that extends Context and
+ * provides methods to set the values of these different Symbols that are used in controlling the writing of JSON-LD.
+ */
+ public class JsonLDWriter extends WriterDatasetRIOTBase
+ {
+ private static final String SYMBOLS_NS = "http://jena.apache.org/riot/jsonld#" ;
+ private static Symbol createSymbol(String localName) {
+ return Symbol.create(SYMBOLS_NS + localName);
+ }
+ /**
+ * Expected value: the value of the "@context"
+ * (a JSON String, or the object expected by the JSONLD-java API) */
+ public static final Symbol JSONLD_CONTEXT = createSymbol("JSONLD_CONTEXT");
+ /**
+ * Expected value: the value of the "@context" to be put in final output (a JSON String)
+ * This is NOT the context used to produce the output (given by JSONLD_CONTEXT,
+ * or computed from the input RDF. It is something that will replace the @context content.
+ * This is useful<ol><li>for the cases you want to have a URI as value of @context,
+ * without having JSON-LD java to download it and</li><li>as a trick to
+ * change the URIs in your result.</li></ol>
+ *
+ * Only for compact and flatten formats.
+ *
+ * Note that it is supposed to be a JSON String: to set the value of @context to a URI,
+ * the String must be quoted.*/
+ public static final Symbol JSONLD_CONTEXT_SUBSTITUTION = createSymbol("JSONLD_CONTEXT_SUBSTITUTION");
+ /** value: a JSON String, or the frame object expected by JsonLdProcessor.frame */
+ public static final Symbol JSONLD_FRAME = createSymbol("JSONLD_FRAME");
+ /** value: the option object expected by JsonLdProcessor (instance of JsonLdOptions) */
+ public static final Symbol JSONLD_OPTIONS = createSymbol("JSONLD_OPTIONS");
+ /**
+ * if creating a (jsonld) context from dataset, should we include all the prefixes defined in graph's prefix mappings
+ * value: a Boolean (default: true) */
+ public static final Symbol JSONLD_ADD_ALL_PREFIXES_TO_CONTEXT = createSymbol("JSONLD_ADD_ALL_PREFIXES_TO_CONTEXT");
+
+ private final RDFFormat format ;
+
+ public JsonLDWriter(RDFFormat syntaxForm) {
+ format = syntaxForm ;
+ }
+
+ @Override
+ public Lang getLang() {
+ return format.getLang() ;
+ }
+
+ @Override
+ public void write(Writer out, DatasetGraph dataset, PrefixMap prefixMap, String baseURI, Context context) {
+ serialize(out, dataset, prefixMap, baseURI, context) ;
+ }
+
+ @Override
+ public void write(OutputStream out, DatasetGraph dataset, PrefixMap prefixMap, String baseURI, Context context) {
+ Writer w = new OutputStreamWriter(out, Chars.charsetUTF8) ;
+ write(w, dataset, prefixMap, baseURI, context) ;
+ IO.flush(w) ;
+ }
+
+ private RDFFormat.JSONLDVariant getVariant() {
+ return (RDFFormat.JSONLDVariant) format.getVariant();
+ }
+
+ private JsonLdOptions getJsonLdOptions(String baseURI, Context jenaContext) {
+ JsonLdOptions opts = null;
+ if (jenaContext != null) {
+ opts = (JsonLdOptions) jenaContext.get(JSONLD_OPTIONS);
+ }
+ if (opts == null) {
+ opts = defaultJsonLdOptions(baseURI);
+ }
+ return opts;
+ }
+
+ // jena is not using same default as JSONLD-java
+ // maybe we should have, but it's too late now:
+ // changing it now would imply some unexpected changes in current users' outputs
+ static private JsonLdOptions defaultJsonLdOptions(String baseURI) {
+ JsonLdOptions opts = new JsonLdOptions(baseURI);
+ opts.useNamespaces = true ; // this is NOT jsonld-java's default
+ // opts.setUseRdfType(true); // false -> use "@type"
+ opts.setUseNativeTypes(true); // this is NOT jsonld-java's default
+ opts.setCompactArrays(true); // this is jsonld-java's default
+ return opts;
+ }
+
+ private void serialize(Writer writer, DatasetGraph dataset, PrefixMap prefixMap, String baseURI, Context jenaContext) {
+ try {
+ JsonLdOptions opts = getJsonLdOptions(baseURI, jenaContext) ;
+
+ // we can benefit from the fact we know that there are no duplicates in the jsonld RDFDataset that we create
+ // (optimization in jsonld-java 0.8.3)
+ // see https://github.com/jsonld-java/jsonld-java/pull/173
+
+ // with this, we cannot call the json-ld fromRDF method that assumes no duplicates in RDFDataset
+ // Object obj = JsonLdProcessor.fromRDF(dataset, opts, new JenaRDF2JSONLD()) ;
+ final RDFDataset jsonldDataset = (new JenaRDF2JSONLD()).parse(dataset);
+ @SuppressWarnings("deprecation") // JsonLdApi.fromRDF(RDFDataset, boolean) is "experimental" rather than "deprecated"
+ Object obj = (new JsonLdApi(opts)).fromRDF(jsonldDataset, true); // true because we know that we don't have any duplicate in jsonldDataset
+
+ RDFFormat.JSONLDVariant variant = getVariant();
+ if (variant.isExpand()) {
+ // nothing more to do
+
+ } else if (variant.isFrame()) {
+ Object frame = null;
+ if (jenaContext != null) {
+ frame = jenaContext.get(JSONLD_FRAME);
+ }
+ if (frame == null) {
+ throw new IllegalArgumentException("No frame object found in jena Context");
+ }
+
+ if (frame instanceof String) {
+ frame = JsonUtils.fromString((String) frame);
+ }
+ obj = JsonLdProcessor.frame(obj, frame, opts);
+
+ } else { // compact or flatten
+ // we need a (jsonld) context. Get it from jenaContext, or create one:
+ Object ctx = getJsonldContext(dataset, prefixMap, jenaContext);
+
+ if (variant.isCompact()) {
+ obj = JsonLdProcessor.compact(obj, ctx, opts);
+
+ } else if (variant.isFlatten()) {
+ obj = JsonLdProcessor.flatten(obj, ctx, opts);
+
+ } else {
+ throw new IllegalArgumentException("Unexpected " + RDFFormat.JSONLDVariant.class.getName() + ": " + variant);
+ }
+
+ // replace @context in output?
+ if (jenaContext != null) {
+ Object ctxReplacement = jenaContext.get(JSONLD_CONTEXT_SUBSTITUTION);
+ if (ctxReplacement != null) {
+ if (obj instanceof Map) {
- Map map = (Map) obj;
++ @SuppressWarnings("unchecked")
++ Map<String, Object> map = (Map<String, Object>) obj;
+ if (map.containsKey("@context")) {
+ map.put("@context", JsonUtils.fromString(ctxReplacement.toString()));
+ }
+ }
+ }
+ }
+ }
+
+ if (variant.isPretty()) {
+ JsonUtils.writePrettyPrint(writer, obj) ;
+ } else {
+ JsonUtils.write(writer, obj) ;
+ }
+ writer.write("\n") ;
+
+ } catch (JsonLdError | JsonMappingException | JsonGenerationException e) {
+ throw new RiotException(e) ;
+ } catch (IOException e) {
+ IO.exception(e) ;
+ }
+ }
+
+ //
+ // getting / creating a (jsonld) context
+ //
+
+ /** Get the (jsonld) context from the jena context, or create one */
+ private static Object getJsonldContext(DatasetGraph dataset, PrefixMap prefixMap, Context jenaContext) throws JsonParseException, IOException {
+ Object ctx = null;
+ boolean isCtxDefined = false; // to allow jenaContext to set ctx to null. Useful?
+
+ if (jenaContext != null) {
+ if (jenaContext.isDefined(JSONLD_CONTEXT)) {
+ isCtxDefined = true;
+ Object o = jenaContext.get(JSONLD_CONTEXT);
+ if (o != null) {
+ if (o instanceof String) { // supposed to be a json string
+ String jsonString = (String) o;
+ ctx = JsonUtils.fromString(jsonString);
+ } else {
+ ctx = o;
+ }
+ }
+ }
+ }
+
+ if (!isCtxDefined) {
+ // if no ctx passed via jenaContext, create one in order to have localnames as keys for properties
+ ctx = createJsonldContext(dataset.getDefaultGraph(), prefixMap, addAllPrefixesToContextFlag(jenaContext)) ;
+
+ // I don't think this should be done: the JsonLdProcessor begins
+ // by looking whether the argument passed is a map with key "@context" and if so, takes corresponding value
+ // Then, better not to do this: we create a map for nothing, and worse,
+ // if the context object has been created by a user and passed through the (jena) context
+ // in case he got the same idea, we would end up with 2 levels of maps and it would not work
+ // Map<String, Object> localCtx = new HashMap<>() ;
+ // localCtx.put("@context", ctx) ;
+ }
+ return ctx;
+ }
+
+ static Object createJsonldContext(Graph g) {
+ return createJsonldContext(g, PrefixMapFactory.create(g.getPrefixMapping()), true);
+ }
+
+ private static Object createJsonldContext(Graph g, PrefixMap prefixMap, boolean addAllPrefixesToContext) {
+ final Map<String, Object> ctx = new LinkedHashMap<>() ;
+
+ // Add properties (in order to get: "localname": ....)
+ addProperties(ctx, g);
+
+ // Add prefixes
+ addPrefixes(ctx, g, prefixMap, addAllPrefixesToContext);
+
+ return ctx ;
+ }
+
+ /** Add properties to jsonld context. */
+ static void addProperties(Map<String, Object> ctx, Graph g) {
+ Consumer<Triple> x = new Consumer<Triple>() {
+ @Override
+ public void accept(Triple item) {
+ Node p = item.getPredicate() ;
+ Node o = item.getObject() ;
+
+ if ( p.equals(RDF.type.asNode()) )
+ return ;
+ String x = p.getLocalName() ;
+
+ if ( ctx.containsKey(x) ) {
+ } else if ( o.isBlank() || o.isURI() ) {
+ // add property as a property (the object is an IRI)
+ Map<String, Object> x2 = new LinkedHashMap<>() ;
+ x2.put("@id", p.getURI()) ;
+ x2.put("@type", "@id") ;
+ ctx.put(x, x2) ;
+ } else if ( o.isLiteral() ) {
+ String literalDatatypeURI = o.getLiteralDatatypeURI() ;
+ if ( literalDatatypeURI != null ) {
+ // add property as a typed attribute (the object is a
+ // typed literal)
+ Map<String, Object> x2 = new LinkedHashMap<>() ;
+ x2.put("@id", p.getURI()) ;
+ if (! isLangString(o) && ! isSimpleString(o) )
+ // RDF 1.1 : Skip if rdf:langString or xsd:string.
+ x2.put("@type", literalDatatypeURI) ;
+ ctx.put(x, x2) ;
+ } else {
+ // add property as an untyped attribute (the object is
+ // an untyped literal)
+ ctx.put(x, p.getURI()) ;
+ }
+ }
+ }
+ } ;
+ Iter.iter(g.find(null, null, null)).apply(x) ;
+ }
+
+ /**
+ * Add the prefixes to jsonld context.
+ *
+ * @param ctx
+ * @param g
+ * @param prefixMap
+ * @param addAllPrefixesToContext true to add all prefixes in prefixMap to the jsonld context,
+ * false to only add those which are actually used in g (false is useful for instance
+ * when downloading schema.org: we get a very long list of prefixes.
+ */
+ // if adding all the prefixes in PrefixMap to ctx
+ // one pb is, many of the prefixes may be actually unused in the graph.
+ // This happens for instance when downloading schema.org: a very long list of prefixes
+ // hence the addAllPrefixesToContext param
+ private static void addPrefixes(Map<String, Object> ctx, Graph g, PrefixMap prefixMap, boolean addAllPrefixesToContext) {
+ if (prefixMap != null) {
+ Map<String, IRI> mapping = prefixMap.getMapping();
+ if (addAllPrefixesToContext) {
+ for ( Entry<String, IRI> e : mapping.entrySet() ) {
+ addOnePrefix(ctx, e.getKey(), e.getValue().toString());
+ }
+ } else {
+ // only add those that are actually used
+ Consumer<Triple> x = new Consumer<Triple>() {
+ @Override
+ public void accept(Triple item) {
+ Node node = item.getSubject();
+ if (node.isURI()) addPrefix2Ctx(node.getURI());
+ node = item.getPredicate() ;
+ addPrefix2Ctx(node.getURI());
+ node = item.getObject() ;
+ if (node.isURI()) addPrefix2Ctx(node.getURI());
+ }
+
+ private void addPrefix2Ctx(String resUri) {
+ Pair<String, String> pair = prefixMap.abbrev(resUri);
+ if (pair != null) {
+ String prefix = pair.getLeft();
+ addOnePrefix(ctx, prefix, mapping.get(prefix).toString());
+ }
+ }
+ } ;
+ Iter.iter(g.find(null, null, null)).apply(x) ;
+ }
+ }
+ }
+
+ /** Add one prefix to jsonld context */
+ static void addOnePrefix(Map<String, Object> ctx, String prefix, String value) {
+ if (!prefix.isEmpty()) { // Prefix "" is not allowed in JSON-LD -- could probably be replaced by "@vocab"
+ ctx.put(prefix, value);
+ }
+ }
+
+ private static boolean addAllPrefixesToContextFlag(Context jenaContext) {
+ if (jenaContext != null) {
+ Object o = jenaContext.get(JSONLD_ADD_ALL_PREFIXES_TO_CONTEXT);
+ if (o != null) {
+ if (o instanceof Boolean) {
+ return ((Boolean) o).booleanValue();
+ } else {
+ throw new IllegalArgumentException("Value attached to JSONLD_ADD_ALL_PREFIXES_TO_CONTEXT shoud be a Boolean");
+ }
+ }
+ }
+ // default
+ return true;
+ }
+
+ }