001package ca.uhn.fhir.util.rdf;
002
003/*-
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.io.OutputStream;
024import java.util.Iterator;
025import java.util.Map.Entry;
026
027import org.apache.jena.atlas.io.IndentedWriter;
028import org.apache.jena.graph.Triple;
029import org.apache.jena.riot.system.RiotLib;
030import org.apache.jena.riot.system.StreamOps;
031import org.apache.jena.riot.system.StreamRDF;
032import org.apache.jena.riot.writer.WriterStreamRDFBlocks;
033import org.apache.jena.riot.writer.WriterStreamRDFPlain;
034import org.apache.jena.shared.PrefixMapping;
035import org.apache.jena.shared.impl.PrefixMappingImpl;
036import org.apache.jena.vocabulary.RDF;
037
038
039/**
040 * Writes an iterator over triples to N-Triples or Turtle
041 * in a streaming fashion, that is, without needing to hold
042 * the entire thing in memory.
043 * <p>
044 * Instances are single-use.
045 * <p>
046 * There doesn't seem to be a pre-packaged version of this
047 * functionality in Jena/ARQ that doesn't require a Graph or Model.
048 */
049public class StreamingRDFWriter {
050        private final OutputStream out;
051        private final Iterator<Triple> triples;
052        private int dedupWindowSize = 10000;
053
054        public StreamingRDFWriter(OutputStream out, Iterator<Triple> triples) {
055                this.out = out;
056                this.triples = triples;
057        }
058
059        public void setDedupWindowSize(int newSize) {
060                this.dedupWindowSize = newSize;
061        }
062
063        public void writeNTriples() {
064                StreamRDF writer = new WriterStreamRDFPlain(new IndentedWriter(out));
065                if (dedupWindowSize > 0) {
066                        writer = new StreamRDFDedup(writer, dedupWindowSize);
067                }
068                writer.start();
069                StreamOps.sendTriplesToStream(triples, writer);
070                writer.finish();
071        }
072
073        public void writeTurtle(String baseIRI, PrefixMapping prefixes, boolean writeBase) {
074                // Auto-register RDF prefix so that rdf:type is displayed well
075                // All other prefixes come from the query and should be as author intended
076                prefixes = ensureRDFPrefix(prefixes);
077
078                if (writeBase) {
079                        // Jena's streaming Turtle writers don't output base even if it is provided,
080                        // so we write it directly.
081                        IndentedWriter w = new IndentedWriter(out);
082                        RiotLib.writeBase(w, baseIRI);
083                        w.flush();
084                }
085
086                StreamRDF writer = new WriterStreamRDFBlocks(out);
087                if (dedupWindowSize > 0) {
088                        writer = new StreamRDFDedup(writer, dedupWindowSize);
089                }
090                writer.start();
091                writer.base(baseIRI);
092                for (Entry<String, String> e : prefixes.getNsPrefixMap().entrySet()) {
093                        writer.prefix(e.getKey(), e.getValue());
094                }
095                StreamOps.sendTriplesToStream(triples, writer);
096                writer.finish();
097        }
098
099        private PrefixMapping ensureRDFPrefix(PrefixMapping prefixes) {
100                // Some prefix already registered for the RDF namespace -- good enough
101                if (prefixes.getNsURIPrefix(RDF.getURI()) != null) return prefixes;
102                // rdf: is registered to something else -- give up
103                if (prefixes.getNsPrefixURI("rdf") != null) return prefixes;
104                // Register rdf:
105                PrefixMapping newPrefixes = new PrefixMappingImpl();
106                newPrefixes.setNsPrefixes(prefixes);
107                newPrefixes.setNsPrefix("rdf", RDF.getURI());
108                return newPrefixes;
109        }
110}