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}