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;
+     }
+ 
+ }