blob: b5d56ccfff8f5076fadcb52b32c73c968e065f2c [file] [log] [blame]
/**
* 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.out ;
import java.io.IOException ;
import java.io.OutputStream ;
import java.io.OutputStreamWriter ;
import java.io.Writer ;
import java.util.* ;
import java.util.Map.Entry;
import java.util.function.Consumer;
import com.fasterxml.jackson.core.JsonGenerationException ;
import com.fasterxml.jackson.databind.JsonMappingException ;
import com.github.jsonldjava.core.JsonLdError ;
import com.github.jsonldjava.core.JsonLdOptions ;
import com.github.jsonldjava.core.JsonLdProcessor ;
import com.github.jsonldjava.utils.JsonUtils ;
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.graph.Graph ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.iri.IRI ;
import static org.apache.jena.rdf.model.impl.Util.* ;
import org.apache.jena.riot.Lang ;
import org.apache.jena.riot.RDFFormat ;
import org.apache.jena.riot.RiotException ;
import org.apache.jena.riot.system.PrefixMap ;
import org.apache.jena.riot.writer.WriterDatasetRIOTBase ;
import org.apache.jena.sparql.core.DatasetGraph ;
import org.apache.jena.sparql.util.Context ;
import org.apache.jena.vocabulary.RDF ;
public class JsonLDWriter extends WriterDatasetRIOTBase
{
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) ;
}
private boolean isPretty() {
return RDFFormat.PRETTY.equals(format.getVariant()) ;
}
@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 void serialize(Writer writer, DatasetGraph dataset, PrefixMap prefixMap, String baseURI) {
final Map<String, Object> ctx = new LinkedHashMap<>() ;
addProperties(ctx, dataset.getDefaultGraph()) ;
addPrefixes(ctx, prefixMap) ;
try {
JsonLdOptions opts = new JsonLdOptions(baseURI);
opts.useNamespaces = true ;
//opts.setUseRdfType(true);
opts.setUseNativeTypes(true);
opts.setCompactArrays(true);
Object obj = JsonLdProcessor.fromRDF(dataset, opts, new JenaRDF2JSONLD()) ;
Map<String, Object> localCtx = new HashMap<>() ;
localCtx.put("@context", ctx) ;
// Unclear as to the way to set better printing.
obj = JsonLdProcessor.compact(obj, localCtx, opts) ;
if ( 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) ;
}
}
private static void addPrefixes(Map<String, Object> ctx, PrefixMap prefixMap) {
Map<String, IRI> pmap = prefixMap.getMapping() ;
for ( Entry<String, IRI> e : pmap.entrySet() ) {
String key = e.getKey() ;
if ( key.isEmpty() )
// Prefix "" is not allowed in JSON-LD
continue ;
IRI iri = e.getValue() ;
ctx.put(e.getKey(), e.getValue().toString()) ;
}
}
private static void addProperties(final Map<String, Object> ctx, Graph graph) {
// Add some properties directly so it becomes "localname": ....
final Set<String> dups = new HashSet<>() ;
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 ( dups.contains(x) )
return ;
if ( ctx.containsKey(x) ) {
// Check different URI
// pmap2.remove(x) ;
// dups.add(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(graph.find(null, null, null)).apply(x) ;
}
}