/**
* 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.lang ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.Reader ;
import java.util.List ;
import java.util.Map ;
import java.util.Map.Entry;
import java.util.Objects;
import com.fasterxml.jackson.core.JsonLocation ;
import com.fasterxml.jackson.core.JsonProcessingException ;
import com.github.jsonldjava.core.* ;
import com.github.jsonldjava.utils.JsonUtils ;
import org.apache.jena.atlas.io.IO ;
import org.apache.jena.atlas.lib.InternalErrorException ;
import org.apache.jena.atlas.web.ContentType ;
import org.apache.jena.datatypes.RDFDatatype ;
import org.apache.jena.datatypes.xsd.XSDDatatype ;
import org.apache.jena.graph.Node ;
import org.apache.jena.graph.NodeFactory ;
import org.apache.jena.graph.Triple ;
import org.apache.jena.riot.RDFLanguages ;
import org.apache.jena.riot.ReaderRIOT ;
import org.apache.jena.riot.RiotException ;
import org.apache.jena.riot.system.* ;
import org.apache.jena.sparql.core.Quad ;
import org.apache.jena.sparql.util.Context ;
public class JsonLDReader implements ReaderRIOT
{
private ErrorHandler errorHandler = ErrorHandlerFactory.getDefaultErrorHandler() ;
private ParserProfile parserProfile = null ;
@Override public ErrorHandler getErrorHandler() { return errorHandler ; }
@Override public void setErrorHandler(ErrorHandler errorHandler) { this.errorHandler = errorHandler ; }
@Override public ParserProfile getParserProfile() { return parserProfile ; }
@Override public void setParserProfile(ParserProfile parserProfile) { this.parserProfile = parserProfile ; }
@Override
public void read(Reader reader, String baseURI, ContentType ct, StreamRDF output, Context context) {
try {
Object jsonObject = JsonUtils.fromReader(reader) ;
read$(jsonObject, baseURI, ct, output, context) ;
}
catch (JsonProcessingException ex) {
// includes JsonParseException
// The Jackson JSON parser, or addition JSON-level check, throws up something.
JsonLocation loc = ex.getLocation() ;
errorHandler.error(ex.getOriginalMessage(), loc.getLineNr(), loc.getColumnNr());
throw new RiotException(ex.getOriginalMessage()) ;
}
catch (IOException e) {
errorHandler.error(e.getMessage(), -1, -1);
IO.exception(e) ;
}
}
@Override
public void read(InputStream in, String baseURI, ContentType ct, StreamRDF output, Context context) {
try {
Object jsonObject = JsonUtils.fromInputStream(in) ;
read$(jsonObject, baseURI, ct, output, context) ;
}
catch (JsonProcessingException ex) {
// includes JsonParseException
// The Jackson JSON parser, or addition JSON-level check, throws up something.
JsonLocation loc = ex.getLocation() ;
errorHandler.error(ex.getOriginalMessage(), loc.getLineNr(), loc.getColumnNr());
throw new RiotException(ex.getOriginalMessage()) ;
}
catch (IOException e) {
errorHandler.error(e.getMessage(), -1, -1);
IO.exception(e) ;
}
}
private void read$(Object jsonObject, String baseURI, ContentType ct, final StreamRDF output, Context context) {
if ( parserProfile == null )
parserProfile = RiotLib.profile(RDFLanguages.JSONLD, baseURI, errorHandler) ;
output.start() ;
try {
JsonLdTripleCallback callback = new JsonLdTripleCallback() {
@Override
public Object call(RDFDataset dataset) {
// Copy across namespaces
for (Entry<String, String> namespace : dataset.getNamespaces().entrySet()) {
output.prefix(namespace.getKey(), namespace.getValue());
}
// Copy triples and quads
for ( String gn : dataset.keySet() ) {
Object x = dataset.get(gn) ;
if ( "@default".equals(gn) ) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> triples = (List<Map<String, Object>>)x ;
for ( Map<String, Object> t : triples ) {
Node s = createNode(t, "subject") ;
Node p = createNode(t, "predicate") ;
Node o = createNode(t, "object") ;
Triple triple = parserProfile.createTriple(s, p, o, -1, -1) ;
output.triple(triple) ;
}
} else {
@SuppressWarnings("unchecked")
List<Map<String, Object>> quads = (List<Map<String, Object>>)x ;
Node g = createURI(gn) ;
for ( Map<String, Object> q : quads ) {
Node s = createNode(q, "subject") ;
Node p = createNode(q, "predicate") ;
Node o = createNode(q, "object") ;
Quad quad = parserProfile.createQuad(g, s, p, o, -1, -1) ;
output.quad(quad) ;
}
}
}
return null ;
}
} ;
JsonLdOptions options = new JsonLdOptions(baseURI);
options.useNamespaces = true;
JsonLdProcessor.toRDF(jsonObject, callback, options) ;
}
catch (JsonLdError e) {
errorHandler.error(e.getMessage(), -1, -1);
throw new RiotException(e) ;
}
output.finish() ;
}
private LabelToNode labels = SyntaxLabels.createLabelToNode() ;
public static String LITERAL = "literal" ;
public static String BLANK_NODE = "blank node" ;
public static String IRI = "IRI" ;
private Node createNode(Map<String, Object> tripleMap, String key) {
@SuppressWarnings("unchecked")
Map<String, Object> x = (Map<String, Object>)(tripleMap.get(key)) ;
return createNode(x) ;
}
private static final String xsdString = XSDDatatype.XSDstring.getURI() ;
private Node createNode(Map<String, Object> map) {
String type = (String)map.get("type") ;
String lex = (String)map.get("value") ;
if ( type.equals(IRI) )
return createURI(lex) ;
else if ( type.equals(BLANK_NODE) )
return labels.get(null, lex) ; //??
else if ( type.equals(LITERAL) ) {
String lang = (String)map.get("language") ;
String datatype = (String)map.get("datatype") ;
if ( Objects.equals(xsdString, datatype) )
// In RDF 1.1, simple literals and xsd:string are the same.
// During migration, we prefer simple literals to xsd:strings.
datatype = null ;
if ( lang == null && datatype == null )
return parserProfile.createStringLiteral(lex,-1, -1) ;
if ( lang != null )
return parserProfile.createLangLiteral(lex, lang, -1, -1) ;
RDFDatatype dt = NodeFactory.getType(datatype) ;
return parserProfile.createTypedLiteral(lex, dt, -1, -1) ;
} else
throw new InternalErrorException("Node is not a IRI, bNode or a literal: " + type) ;
}
private Node createURI(String str) {
if ( str.startsWith("_:") )
return labels.get(null, str) ;
else
return parserProfile.createURI(str, -1, -1) ;
}
private Node createLiteral(String lex, String datatype, String lang) {
if ( lang == null && datatype == null )
return NodeFactory.createLiteral(lex) ;
if ( lang != null )
return NodeFactory.createLiteral(lex, lang) ;
RDFDatatype dt = NodeFactory.getType(datatype) ;
return NodeFactory.createLiteral(lex, dt) ;
}
}