/* * 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.stanbol.entityhub.web.writer.clerezza; import static org.apache.clerezza.rdf.core.serializedform.SupportedFormat.TEXT_RDF_NT; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import org.apache.clerezza.commons.rdf.Triple; import org.apache.clerezza.commons.rdf.Graph; import org.apache.clerezza.commons.rdf.IRI; import org.apache.clerezza.commons.rdf.Literal; import org.apache.clerezza.commons.rdf.impl.utils.TripleImpl; import org.apache.clerezza.rdf.core.LiteralFactory; import org.apache.clerezza.rdf.core.serializedform.Serializer; import org.apache.clerezza.rdf.core.serializedform.SupportedFormat; import org.apache.clerezza.rdf.ontologies.RDF; import org.apache.felix.scr.annotations.Component; 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.indexedgraph.IndexedGraph; import org.apache.stanbol.commons.namespaceprefix.NamespacePrefixService; import org.apache.stanbol.entityhub.model.clerezza.RdfRepresentation; import org.apache.stanbol.entityhub.model.clerezza.RdfValueFactory; import org.apache.stanbol.entityhub.query.clerezza.RdfQueryResultList; 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.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(immediate=true) @Service public class ClerezzaModelWriter implements ModelWriter { private final Logger log = LoggerFactory.getLogger(ClerezzaModelWriter.class); /** * {@link MediaType} instance for {@link SupportedFormat#TURTLE} */ public static final MediaType TURTLE_TYPE = MediaType.valueOf(SupportedFormat.TURTLE); /** * {@link MediaType} instance for <code>application/ld+json</code> */ public static final MediaType JSONLD_TYPE = MediaType.valueOf("application/ld+json"); /** * {@link MediaType} instance for {@link SupportedFormat#N3} */ public static final MediaType N3_TYPE = MediaType.valueOf(SupportedFormat.N3); /** * {@link MediaType} instance for {@link SupportedFormat#RDF_JSON} */ public static final MediaType RDF_JSON_TYPE = MediaType.valueOf(SupportedFormat.RDF_JSON); /** * {@link MediaType} instance for {@link SupportedFormat#RDF_XML} */ public static final MediaType RDF_XML_TYPE = MediaType.valueOf(SupportedFormat.RDF_XML); /** * {@link MediaType} instance for {@link SupportedFormat#X_TURTLE} */ public static final MediaType X_TURTLE_TYPE = MediaType.valueOf(SupportedFormat.X_TURTLE); public static final MediaType N_TRIPLE_TYPE = MediaType.valueOf(SupportedFormat.N_TRIPLE); /** * Support for the deprecated <code>text/rdf+nt</code> media type */ public static final MediaType TEXT_RDF_NT = MediaType.valueOf(SupportedFormat.TEXT_RDF_NT); /** * Read-only list of the supported RDF formats */ public static final List<MediaType> SUPPORTED_RDF_TYPES = Collections.unmodifiableList( Arrays.asList(TURTLE_TYPE, JSONLD_TYPE, N3_TYPE, N_TRIPLE_TYPE, RDF_JSON_TYPE, RDF_XML_TYPE, X_TURTLE_TYPE, TEXT_RDF_NT)); //some Concepts and Relations we use to represent Entities private final static IRI FOAF_DOCUMENT = new IRI(NamespaceEnum.foaf+"Document"); private final static IRI FOAF_PRIMARY_TOPIC = new IRI(NamespaceEnum.foaf+"primaryTopic"); private final static IRI FOAF_PRIMARY_TOPIC_OF = new IRI(NamespaceEnum.foaf+"isPrimaryTopicOf"); private final static IRI SIGN_SITE = new IRI(RdfResourceEnum.site.getUri()); // private final static IRI ENTITY_TYPE = new IRI(RdfResourceEnum.Entity.getUri()); private final static RdfValueFactory valueFactory = RdfValueFactory.getInstance(); /** * The URI used for the query result list (static for all responses) */ private static final IRI QUERY_RESULT_LIST = new IRI(RdfResourceEnum.QueryResultSet.getUri()); /** * The property used for all results */ private static final IRI QUERY_RESULT = new IRI(RdfResourceEnum.queryResult.getUri()); /** * The property used for the JSON serialised FieldQuery (STANBOL-298) */ private static final IRI FIELD_QUERY = new IRI(RdfResourceEnum.query.getUri()); /** * This Serializer only supports UTF-8 */ public static final String CHARSET = Charset.forName("UTF-8").toString(); /** * The literal factory used (currently {@link LiteralFactory#getInstance()}, * but we might use a custom one for Stanbol therefore it is better to * have it as a field */ static final LiteralFactory literalFactory = LiteralFactory.getInstance(); /** * The Clerezza {@link Serializer} service */ @Reference protected Serializer ser; @Reference(cardinality=ReferenceCardinality.OPTIONAL_UNARY) protected NamespacePrefixService nsPrefixService; @Override public Class<? extends Representation> getNativeType() { return RdfRepresentation.class; } @Override public List<MediaType> supportedMediaTypes() { return SUPPORTED_RDF_TYPES; } @Override public MediaType getBestMediaType(MediaType mediaType) { for(MediaType supported : SUPPORTED_RDF_TYPES){ 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 { Graph 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(new TripleImpl(QUERY_RESULT_LIST, FIELD_QUERY, literalFactory.createTypedLiteral(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 tc * @param out * @param mediaType */ private void writeRdf(Graph tc, OutputStream out, MediaType mediaType) { String charset = mediaType.getParameters().get("charset"); if(charset == null){ charset = ModelWriter.DEFAULT_CHARSET; } if(!CHARSET.equalsIgnoreCase(charset)){ log.warn("Unsupported Charset {} requested (will use {})!",charset,CHARSET); } ser.serialize(out, tc , new StringBuilder(mediaType.getType()) .append('/').append(mediaType.getSubtype()).toString()); } private Graph toRDF(Representation representation) { Graph graph = new IndexedGraph(); addRDFTo(graph, representation); return graph; } private void addRDFTo(Graph graph, Representation representation) { graph.addAll(valueFactory.toRdfRepresentation(representation).getRdfGraph()); } private Graph toRDF(Entity entity) { Graph graph = new IndexedGraph(); addRDFTo(graph, entity); return graph; } private void addRDFTo(Graph 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(Graph graph, Entity sign) { IRI id = new IRI(sign.getId()); IRI metaId = new IRI(sign.getMetadata().getId()); //add the FOAF triples between metadata and content graph.add(new TripleImpl(id, FOAF_PRIMARY_TOPIC_OF, metaId)); graph.add(new TripleImpl(metaId, FOAF_PRIMARY_TOPIC, metaId)); graph.add(new TripleImpl(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 = literalFactory.createTypedLiteral(sign.getSite()); graph.add(new TripleImpl(metaId, SIGN_SITE, siteName)); } private Graph toRDF(QueryResultList<?> resultList) { final Graph resultGraph; Class<?> type = resultList.getType(); if (String.class.isAssignableFrom(type)) { resultGraph = new IndexedGraph(); //create a new ImmutableGraph for (Object result : resultList) { //add a triple to each reference in the result set resultGraph.add(new TripleImpl(QUERY_RESULT_LIST, QUERY_RESULT, new IRI(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 RdfQueryResultList) { resultGraph = ((RdfQueryResultList) resultList).getResultGraph(); if (isSignType) { //if we build a ResultList for Signs, that we need to do more things //first remove all triples representing results Iterator<Triple> resultTripleIt = resultGraph.filter(QUERY_RESULT_LIST, QUERY_RESULT, null); while (resultTripleIt.hasNext()) { resultTripleIt.next(); resultTripleIt.remove(); } //now add the Sign specific triples and add result triples //to the Sign IDs for (Object result : resultList) { IRI signId = new IRI(((Entity) result).getId()); addEntityTriplesToGraph(resultGraph, (Entity) result); resultGraph.add(new TripleImpl(QUERY_RESULT_LIST, QUERY_RESULT, signId)); } } } else { //any other implementation of the QueryResultList interface resultGraph = new IndexedGraph(); //create a new graph if (Representation.class.isAssignableFrom(type)) { for (Object result : resultList) { IRI resultId; if (!isSignType) { addRDFTo(resultGraph, (Representation) result); resultId = new IRI(((Representation) result).getId()); } else { addRDFTo(resultGraph, (Entity) result); resultId = new IRI(((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(new TripleImpl(QUERY_RESULT_LIST, QUERY_RESULT, resultId)); } } } } return resultGraph; } }