blob: 301a73df2ca04bba649f0ca9cac588d21620fd24 [file] [log] [blame]
package org.apache.stanbol.entityhub.web.writer.sesame;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
import org.apache.stanbol.commons.namespaceprefix.NamespacePrefixService;
import org.apache.stanbol.entityhub.model.sesame.RdfRepresentation;
import org.apache.stanbol.entityhub.model.sesame.RdfValueFactory;
import org.apache.stanbol.entityhub.servicesapi.defaults.NamespaceEnum;
import org.apache.stanbol.entityhub.servicesapi.model.Entity;
import org.apache.stanbol.entityhub.servicesapi.model.Representation;
import org.apache.stanbol.entityhub.servicesapi.model.rdf.RdfResourceEnum;
import org.apache.stanbol.entityhub.servicesapi.query.FieldQuery;
import org.apache.stanbol.entityhub.servicesapi.query.QueryResultList;
import org.apache.stanbol.entityhub.web.ModelWriter;
import org.apache.stanbol.entityhub.web.fieldquery.FieldQueryToJsonUtils;
import org.apache.stanbol.entityhub.yard.sesame.SesameQueryResultList;
import org.codehaus.jettison.json.JSONException;
import org.codehaus.jettison.json.JSONObject;
import org.openrdf.model.Literal;
import org.openrdf.model.Model;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.impl.ValueFactoryImpl;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.Rio;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Component(immediate=true)
@Service
public class SesameModelWriter implements ModelWriter {
private static final Logger log = LoggerFactory.getLogger(SesameModelWriter.class);
private final static RdfValueFactory valueFactory = RdfValueFactory.getInstance();
private final static ValueFactory sesameFactory = ValueFactoryImpl.getInstance();
private final static URI FOAF_DOCUMENT = sesameFactory.createURI(NamespaceEnum.foaf+"Document");
private final static URI FOAF_PRIMARY_TOPIC = sesameFactory.createURI(NamespaceEnum.foaf+"primaryTopic");
private final static URI FOAF_PRIMARY_TOPIC_OF = sesameFactory.createURI(NamespaceEnum.foaf+"isPrimaryTopicOf");
private final static URI RDF_TYPE = sesameFactory.createURI(NamespaceEnum.rdf+"type");
private final static URI EH_SIGN_SITE = sesameFactory.createURI(RdfResourceEnum.site.getUri());
/**
* The URI used for the query result list (static for all responses)
*/
private static final URI QUERY_RESULT_LIST = sesameFactory.createURI(RdfResourceEnum.QueryResultSet.getUri());
/**
* The property used for all results
*/
private static final URI QUERY_RESULT = sesameFactory.createURI(RdfResourceEnum.queryResult.getUri());
/**
* The property used for the JSON serialised FieldQuery (STANBOL-298)
*/
private static final URI FIELD_QUERY = sesameFactory.createURI(RdfResourceEnum.query.getUri());
@Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY)
NamespacePrefixService nsPrefixService;
/**
* The list with the supported {@link MediaType}s as provided by the
* Sesame {@link RDFFormat} class
*/
private List<MediaType> supportedRrfFormats;
@Activate
protected void activate(ComponentContext ctx){
//parse the supported RDF formats
Collection<String> mts = new LinkedHashSet<String>();
for(RDFFormat format : RDFFormat.values()){
mts.addAll(format.getMIMETypes());
}
List<MediaType> formats = new ArrayList<MediaType>(mts.size());
for(String format : mts){
try {
formats.add(MediaType.valueOf(format));
} catch (IllegalArgumentException e){
log.error("Unable to parse MediaType for Sesame RDF format '"
+ format + "'!",e);
}
}
supportedRrfFormats = Collections.unmodifiableList(formats);
}
@Deactivate
protected void deactivate(ComponentContext ctx){
supportedRrfFormats = null;
}
@Override
public Class<? extends Representation> getNativeType() {
return RdfRepresentation.class;
}
@Override
public List<MediaType> supportedMediaTypes() {
return supportedRrfFormats;
}
@Override
public MediaType getBestMediaType(MediaType mediaType) {
for(MediaType supported : supportedRrfFormats){
if(supported.isCompatible(mediaType)){
return supported;
}
}
return null;
}
@Override
public void write(Representation rep, OutputStream out, MediaType mediaType) throws WebApplicationException,
IOException {
writeRdf(toRDF(rep), out, mediaType);
}
@Override
public void write(Entity entity, OutputStream out, MediaType mediaType) throws WebApplicationException,
IOException {
writeRdf(toRDF(entity), out, mediaType);
}
@Override
public void write(QueryResultList<?> result, OutputStream out, MediaType mediaType) throws WebApplicationException,
IOException {
Model queryRdf = toRDF(result);
//we need also to the JSON formatted FieldQuery as a literal to the
//RDF data.
FieldQuery query = result.getQuery();
if(query != null){
try {
JSONObject fieldQueryJson = FieldQueryToJsonUtils.toJSON(query,
nsPrefixService);
if(fieldQueryJson != null){
//add the triple with the fieldQuery
queryRdf.add(QUERY_RESULT_LIST, FIELD_QUERY,
sesameFactory.createLiteral(fieldQueryJson.toString()));
}
} catch (JSONException e) {
log.warn(String.format("Unable to serialize Fieldquery '%s' to JSON! "
+ "Query response will not contain the serialized query.",
query),e);
}
}
//now serialise the data
writeRdf(queryRdf,out,mediaType);
}
/**
* @param data
* @param out
* @param mediaType
*/
private void writeRdf(Model data, OutputStream out, MediaType mediaType) {
RDFFormat rdfFormat = Rio.getWriterFormatForMIMEType(mediaType.toString());
if(rdfFormat == null){
throw new IllegalStateException("JAX-RS called for unsupported mediaType '"
+ mediaType +"'! If this is a valid RDF type this indicates a missing "
+ "Sesame Serializer implementation. Otherwise please report this "
+ "as a bug for the Stanbol Issue Tracker.");
}
try {
Rio.write(data, out, rdfFormat);
} catch (RDFHandlerException e) {
throw new WebApplicationException("Unable to serialize QueryResultList with requested Format '" +
rdfFormat +"'!", e);
}
}
private Model toRDF(Representation representation) {
return valueFactory.toRdfRepresentation(representation).getModel();
}
private void addRDFTo(Model graph, Representation representation) {
graph.addAll(valueFactory.toRdfRepresentation(representation).getModel());
}
private Model toRDF(Entity entity) {
Model graph = new LinkedHashModel();
addRDFTo(graph, entity);
return graph;
}
private void addRDFTo(Model graph, Entity entity) {
addRDFTo(graph, entity.getRepresentation());
addRDFTo(graph, entity.getMetadata());
//now add some triples that represent the Sign
addEntityTriplesToGraph(graph, entity);
}
/**
* Adds the Triples that represent the Sign to the parsed graph. Note that
* this method does not add triples for the representation. However it adds
* the triple (sign,singRepresentation,representation)
*
* @param graph the graph to add the triples
* @param sign the sign
*/
private void addEntityTriplesToGraph(Model graph, Entity sign) {
URI id = sesameFactory.createURI(sign.getId());
URI metaId = sesameFactory.createURI(sign.getMetadata().getId());
//add the FOAF triples between metadata and content
graph.add(id, FOAF_PRIMARY_TOPIC_OF, metaId);
graph.add(metaId, FOAF_PRIMARY_TOPIC, metaId);
graph.add(metaId, RDF_TYPE, FOAF_DOCUMENT);
//add the site to the metadata
//TODO: this should be the HTTP URI and not the id of the referenced site
Literal siteName = sesameFactory.createLiteral(sign.getSite());
graph.add(metaId, EH_SIGN_SITE, siteName);
}
private Model toRDF(QueryResultList<?> resultList) {
final Model resultGraph;
Class<?> type = resultList.getType();
if (String.class.isAssignableFrom(type)) {
resultGraph = new LinkedHashModel(); //create a new ImmutableGraph
for (Object result : resultList) {
//add a triple to each reference in the result set
resultGraph.add(QUERY_RESULT_LIST, QUERY_RESULT, sesameFactory.createURI(result.toString()));
}
} else {
//first determine the type of the resultList
final boolean isSignType;
if (Representation.class.isAssignableFrom(type)) {
isSignType = false;
} else if (Representation.class.isAssignableFrom(type)) {
isSignType = true;
} else {
//incompatible type -> throw an Exception
throw new IllegalArgumentException("Parsed type " + type + " is not supported");
}
//special treatment for RdfQueryResultList for increased performance
if (resultList instanceof SesameQueryResultList) {
resultGraph = ((SesameQueryResultList) resultList).getModel();
if (isSignType) { //if we build a ResultList for Signs, we need to do more things
//first remove all triples representing results
resultGraph.filter(null, QUERY_RESULT, null).clear();
//now add the Sign specific triples and add result triples
//to the Sign IDs
for (Object result : resultList) {
URI signId = sesameFactory.createURI(((Entity) result).getId());
addEntityTriplesToGraph(resultGraph, (Entity) result);
resultGraph.add(QUERY_RESULT_LIST, QUERY_RESULT, signId);
}
}
} else { //any other implementation of the QueryResultList interface
resultGraph = new LinkedHashModel(); //create a new graph
if (Representation.class.isAssignableFrom(type)) {
for (Object result : resultList) {
URI resultId;
if (!isSignType) {
addRDFTo(resultGraph, (Representation) result);
resultId = sesameFactory.createURI(((Representation) result).getId());
} else {
addRDFTo(resultGraph, (Entity) result);
resultId = sesameFactory.createURI(((Entity) result).getId());
}
//Note: In case of Representation this Triple points to
// the representation. In case of Signs it points to
// the sign.
resultGraph.add(QUERY_RESULT_LIST, QUERY_RESULT, resultId);
}
}
}
}
return resultGraph;
}
}