001/** 002 * Copyright 2015 DuraSpace, Inc. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.fcrepo.http.commons.responses; 017 018import static com.google.common.collect.Maps.filterEntries; 019import static javax.ws.rs.core.Response.Status.NOT_ACCEPTABLE; 020import static org.fcrepo.kernel.api.utils.UncheckedBiConsumer.uncheck; 021import static org.openrdf.model.impl.ValueFactoryImpl.getInstance; 022import static org.openrdf.model.util.Literals.createLiteral; 023import static org.slf4j.LoggerFactory.getLogger; 024 025import java.io.OutputStream; 026import java.util.function.Function; 027 028import javax.ws.rs.WebApplicationException; 029import javax.ws.rs.core.MediaType; 030import javax.ws.rs.core.StreamingOutput; 031 032import org.fcrepo.kernel.api.utils.iterators.RdfStream; 033 034import org.openrdf.model.Resource; 035import org.openrdf.model.Statement; 036import org.openrdf.model.URI; 037import org.openrdf.model.Value; 038import org.openrdf.model.ValueFactory; 039import org.openrdf.rio.RDFFormat; 040import org.openrdf.rio.RDFHandlerException; 041import org.openrdf.rio.RDFWriter; 042import org.openrdf.rio.RDFWriterRegistry; 043import org.openrdf.rio.Rio; 044import org.openrdf.rio.WriterConfig; 045import org.slf4j.Logger; 046 047import com.google.common.util.concurrent.AbstractFuture; 048import com.hp.hpl.jena.graph.Node; 049import com.hp.hpl.jena.graph.Triple; 050 051/** 052 * Serializes an {@link RdfStream}. 053 * 054 * @author ajs6f 055 * @since Oct 30, 2013 056 */ 057public class RdfStreamStreamingOutput extends AbstractFuture<Void> implements 058 StreamingOutput { 059 060 private static final Logger LOGGER = 061 getLogger(RdfStreamStreamingOutput.class); 062 063 private static ValueFactory vfactory = getInstance(); 064 065 /** 066 * This field is used to determine the correct {@link org.openrdf.rio.RDFWriter} created for the 067 * {@link javax.ws.rs.core.StreamingOutput}. 068 */ 069 private final RDFFormat format; 070 071 /** 072 * This field is used to determine the {@link org.openrdf.rio.WriterConfig} details used by the created 073 * {@link org.openrdf.rio.RDFWriter}. 074 */ 075 private final MediaType mediaType; 076 077 private final RdfStream rdfStream; 078 079 /** 080 * Normal constructor 081 * 082 * @param rdfStream the rdf stream 083 * @param mediaType the media type 084 */ 085 public RdfStreamStreamingOutput(final RdfStream rdfStream, 086 final MediaType mediaType) { 087 super(); 088 089 if (LOGGER.isDebugEnabled()) { 090 for (final RDFFormat writeableFormats : RDFWriterRegistry.getInstance().getKeys()) { 091 LOGGER.debug("Discovered RDF writer writeableFormats: {} with mimeTypes: {}", 092 writeableFormats.getName(), String.join(" ", writeableFormats.getMIMETypes())); 093 } 094 } 095 final RDFFormat format = Rio.getWriterFormatForMIMEType(mediaType.toString()); 096 if (format != null) { 097 this.format = format; 098 this.mediaType = mediaType; 099 LOGGER.debug("Setting up to serialize to: {}", format); 100 } else { 101 throw new WebApplicationException(NOT_ACCEPTABLE); 102 } 103 104 this.rdfStream = rdfStream; 105 } 106 107 @Override 108 public void write(final OutputStream output) { 109 LOGGER.debug("Serializing RDF stream in: {}", format); 110 try { 111 write(() -> rdfStream.transform(toStatement::apply), output, format, mediaType); 112 } catch (final RDFHandlerException e) { 113 setException(e); 114 LOGGER.debug("Error serializing RDF", e); 115 throw new WebApplicationException(e); 116 } 117 } 118 119 private void write(final Iterable<Statement> model, 120 final OutputStream output, 121 final RDFFormat dataFormat, 122 final MediaType dataMediaType) 123 throws RDFHandlerException { 124 final WriterConfig settings = WriterConfigHelper.apply(dataMediaType); 125 final RDFWriter writer = Rio.createWriter(dataFormat, output); 126 writer.setWriterConfig(settings); 127 128 /** 129 * We exclude: 130 * - xmlns, which Sesame helpfully serializes, but normal parsers may complain 131 * about in some serializations (e.g. RDF/XML where xmlns:xmlns is forbidden by XML); 132 */ 133 filterEntries(rdfStream.namespaces(), e -> !e.getKey().equals("xmlns")) 134 .forEach(uncheck(writer::handleNamespace)); 135 136 Rio.write(model, writer); 137 } 138 139 protected static final Function<? super Triple, Statement> toStatement = t -> { 140 final Resource subject = getResourceForSubject(t.getSubject()); 141 final URI predicate = vfactory.createURI(t.getPredicate().getURI()); 142 final Value object = getValueForObject(t.getObject()); 143 return vfactory.createStatement(subject, predicate, object); 144 }; 145 146 private static Resource getResourceForSubject(final Node subjectNode) { 147 return subjectNode.isBlank() ? vfactory.createBNode(subjectNode.getBlankNodeLabel()) 148 : vfactory.createURI(subjectNode.getURI()); 149 } 150 151 protected static Value getValueForObject(final Node object) { 152 if (object.isURI()) { 153 return vfactory.createURI(object.getURI()); 154 } else if (object.isBlank()) { 155 return vfactory.createBNode(object.getBlankNodeLabel()); 156 } else if (object.isLiteral()) { 157 final String literalValue = object.getLiteralLexicalForm(); 158 159 final String literalDatatypeURI = object.getLiteralDatatypeURI(); 160 161 if (!object.getLiteralLanguage().isEmpty()) { 162 return vfactory.createLiteral(literalValue, object.getLiteralLanguage()); 163 } else if (literalDatatypeURI != null) { 164 final URI uri = vfactory.createURI(literalDatatypeURI); 165 return vfactory.createLiteral(literalValue, uri); 166 } else { 167 return createLiteral(vfactory, object.getLiteralValue()); 168 } 169 } 170 throw new AssertionError("Unable to convert " + object + 171 " to a value, it is neither URI, blank, nor literal!"); 172 } 173}