/* * Licensed to DuraSpace under one or more contributor license agreements. * See the NOTICE file distributed with this work for additional information * regarding copyright ownership. * * DuraSpace 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.fcrepo.http.commons.responses; import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; import static org.apache.jena.riot.Lang.JSONLD; import static org.apache.jena.riot.Lang.RDFXML; import static org.apache.jena.riot.RDFLanguages.contentTypeToLang; import static org.apache.jena.riot.RDFLanguages.getRegisteredLanguages; import static org.apache.jena.riot.RDFFormat.RDFXML_PLAIN; import static org.apache.jena.riot.RDFFormat.JSONLD_COMPACT_FLAT; import static org.apache.jena.riot.RDFFormat.JSONLD_EXPAND_FLAT; import static org.apache.jena.riot.RDFFormat.JSONLD_FLATTEN_FLAT; import static org.apache.jena.riot.system.StreamRDFWriter.defaultSerialization; import static org.apache.jena.riot.system.StreamRDFWriter.getWriterStream; import static org.fcrepo.kernel.api.RdfCollectors.toModel; import static org.slf4j.LoggerFactory.getLogger; import java.io.IOException; import java.io.OutputStream; import java.util.Map; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.StreamingOutput; import com.google.common.util.concurrent.AbstractFuture; import org.apache.jena.riot.RiotException; import org.apache.jena.rdf.model.Model; import org.apache.jena.riot.Lang; import org.apache.jena.riot.RDFDataMgr; import org.apache.jena.riot.RDFFormat; import org.apache.jena.riot.system.StreamRDF; import org.fcrepo.kernel.api.RdfStream; import org.slf4j.Logger; /** * Serializes an {@link RdfStream}. * * @author ajs6f * @since Oct 30, 2013 */ public class RdfStreamStreamingOutput extends AbstractFuture<Void> implements StreamingOutput { private static final Logger LOGGER = getLogger(RdfStreamStreamingOutput.class); private static final String JSONLD_COMPACTED = "http://www.w3.org/ns/json-ld#compacted"; private static final String JSONLD_FLATTENED = "http://www.w3.org/ns/json-ld#flattened"; private final Lang format; private final MediaType mediaType; private final RdfStream rdfStream; private final Map<String, String> namespaces; /** * Normal constructor * * @param rdfStream the rdf stream * @param namespaces a namespace mapping * @param mediaType the media type */ public RdfStreamStreamingOutput(final RdfStream rdfStream, final Map<String, String> namespaces, final MediaType mediaType) { super(); if (LOGGER.isDebugEnabled()) { getRegisteredLanguages().forEach(format -> { LOGGER.debug("Discovered RDF writer writeableFormats: {} with mimeTypes: {}", format.getName(), String.join(" ", format.getAltContentTypes())); }); } final Lang format = contentTypeToLang(mediaType.getType() + "/" + mediaType.getSubtype()); if (format != null) { this.format = format; this.mediaType = mediaType; LOGGER.debug("Setting up to serialize to: {}", format); } else { throw new WebApplicationException(NOT_ACCEPTABLE); } this.rdfStream = rdfStream; this.namespaces = namespaces; } @Override public void write(final OutputStream output) { try { LOGGER.debug("Serializing RDF stream in: {}", format); write(rdfStream, output, format, mediaType, namespaces); } catch (final IOException | RiotException e) { setException(e); LOGGER.debug("Error serializing RDF", e.getMessage()); throw new WebApplicationException(e); } } private static void write(final RdfStream rdfStream, final OutputStream output, final Lang dataFormat, final MediaType dataMediaType, final Map<String, String> nsPrefixes) throws IOException { final RDFFormat format = defaultSerialization(dataFormat); // For formats that can be block-streamed (n-triples, turtle) if (format != null) { LOGGER.debug("Stream-based serialization of {}", dataFormat.toString()); final StreamRDF stream = new SynchonizedStreamRDFWrapper(getWriterStream(output, format)); stream.start(); nsPrefixes.forEach(stream::prefix); rdfStream.forEach(stream::triple); stream.finish(); // For formats that require analysis of the entire model and cannot be streamed directly (rdfxml, n3) } else { LOGGER.debug("Non-stream serialization of {}", dataFormat.toString()); final Model model = rdfStream.collect(toModel()); model.setNsPrefixes(nsPrefixes); // use block output streaming for RDFXML if (RDFXML.equals(dataFormat)) { RDFDataMgr.write(output, model.getGraph(), RDFXML_PLAIN); } else if (JSONLD.equals(dataFormat)) { final RDFFormat jsonldFormat = getFormatFromMediaType(dataMediaType); RDFDataMgr.write(output, model.getGraph(), jsonldFormat); } else { RDFDataMgr.write(output, model.getGraph(), dataFormat); } } } private static RDFFormat getFormatFromMediaType(final MediaType mediaType) { final String profile = mediaType.getParameters().getOrDefault("profile", ""); if (profile.equals(JSONLD_COMPACTED)) { return JSONLD_COMPACT_FLAT; } else if (profile.equals(JSONLD_FLATTENED)) { return JSONLD_FLATTEN_FLAT; } return JSONLD_EXPAND_FLAT; } }