/** * Copyright Intellectual Reserve, Inc. * * Licensed 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.gedcomx.fileformat; import org.gedcomx.Gedcomx; import org.gedcomx.rt.GedcomxConstants; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Date; import java.util.Map; import java.util.jar.*; /** * Class to help in writing a GEDCOM X file. */ public class GedcomxOutputStream { private final GedcomxEntrySerializer serializer; private final JarOutputStream gedxOutputStream; private final Manifest mf; private int entryCount = 0; public GedcomxOutputStream(OutputStream gedxOutputStream, GedcomxEntrySerializer serializer) throws IOException { this.serializer = serializer; this.gedxOutputStream = new JarOutputStream(gedxOutputStream); this.mf = new Manifest(); this.mf.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0"); } /** * Constructs a GEDCOM X output stream. * * NOTE: This class uses the GedcomXFileJAXBContextFactory to create a JAXB context from which to derive the marshaller that is used to marshal resources into the output stream. * GedcomXFileJAXBContextFactory creates a context that includes some default resource classes. The classes passed via this constructor will supplement these defaults; they will * not overwrite or replace these defaults. Please see the documentation for GedcomXFileJAXBContextFactory to review the list of default classes. * * @param gedxOutputStream an output stream to which the GEDCOM X resources will appended * @param classes classes representing resources that will be marshaled (via JAXB) into the GEDCOM X output stream */ public GedcomxOutputStream(OutputStream gedxOutputStream, Class<?>... classes) throws IOException { this(gedxOutputStream, new JacksonJsonSerialization(classes)); } /** * Add an attribute to the GEDCOM X output stream. * * @param name The name of the attribute. * @param value The value of the attribute. */ public void addAttribute(String name, String value) { this.mf.getMainAttributes().putValue(name, value); } /** * Add a resource to the GEDCOM X output stream. * * @param resource The resource. */ public void addResource(Gedcomx resource) throws IOException { addResource(resource, new Date()); } /** * Add a resource to the GEDCOM X output stream. * * @param resource The resource. * @param lastModified timestamp when the resource was last modified (can be null) */ public void addResource(Gedcomx resource, Date lastModified) throws IOException { StringBuilder entryName = new StringBuilder("tree"); if (this.entryCount > 0) { entryName.append(this.entryCount); } entryName.append(this.serializer.suggestFilenameExtension()); addResource(entryName.toString(), resource, lastModified); } /** * Add a resource to the GEDCOM X output stream. * * @param entryName The name by which this resource shall be known within the GEDCOM X file. * @param resource The resource. * @param lastModified timestamp when the resource was last modified (can be null) */ public void addResource(String entryName, Gedcomx resource, Date lastModified) throws IOException { addResource(GedcomxConstants.GEDCOMX_JSON_MEDIA_TYPE, entryName, resource, lastModified, null); } /** * Add a resource to the GEDCOM X output stream. * * * @param contentType The content type of the resource. * @param entryName The name by which this resource shall be known within the GEDCOM X file. * @param resource The resource. * @param lastModified timestamp when the resource was last modified (can be null) */ public void addResource(String contentType, String entryName, Object resource, Date lastModified) throws IOException { addResource(contentType, entryName, resource, lastModified, null); } /** * Add a resource to the GEDCOM X output stream. * * @param contentType The content type of the resource. * @param entryName The name by which this resource shall be known within the GEDCOM X file. * @param resource The resource. * @param lastModified timestamp when the resource was last modified (can be null) * @param attributes The attributes of the resource. */ public void addResource(String contentType, String entryName, Object resource, Date lastModified, Map<String, String> attributes) throws IOException { putNextEntry(contentType, entryName, lastModified, attributes); this.serializer.serialize(resource, this.gedxOutputStream); this.entryCount++; } /** * Add a resource to the GEDCOM X output stream. * * @param contentType The content type of the resource. * @param entryName The name by which this resource shall be known within the GEDCOM X file. * @param resource The resource. * @param lastModified timestamp when the resource was last modified (can be null) * @param attributes The attributes of the resource. */ public void addResource(String contentType, String entryName, InputStream resource, Date lastModified, Map<String, String> attributes) throws IOException { putNextEntry(contentType, entryName, lastModified, attributes); byte[] buffer = new byte[1024]; int len = resource.read(buffer); while (len >= 0) { this.gedxOutputStream.write(buffer, 0, len); len = resource.read(buffer); } this.entryCount++; } protected void putNextEntry(String contentType, String entryName, Date lastModified, Map<String, String> attributes) throws IOException { if (contentType.trim().length() == 0) { throw new IllegalArgumentException("contentType must not be null or empty."); } entryName = entryName.replaceAll("\\\\", "/"); entryName = entryName.charAt(0) == '/' ? entryName.substring(1) : entryName; JarEntry gedxEntry = new JarEntry(entryName); // will throw a runtime exception if entryName is not okay Attributes entryAttrs = new Attributes(); if (lastModified != null) { entryAttrs.putValue("X-DC-modified", GedcomxTimeStampUtil.formatAsXmlUTC(lastModified)); } entryAttrs.put(Attributes.Name.CONTENT_TYPE, contentType); if (attributes != null) { for (Map.Entry<String, String> entry : attributes.entrySet()) { entryAttrs.putValue(entry.getKey(), entry.getValue()); } } if (!entryAttrs.isEmpty()) { this.mf.getEntries().put(entryName, entryAttrs); } this.gedxOutputStream.putNextEntry(gedxEntry); } /** * Closes the GEDCOM X output stream as well as the stream being filtered. */ public void close() throws IOException { this.gedxOutputStream.putNextEntry(new JarEntry(JarFile.MANIFEST_NAME)); this.mf.write(this.gedxOutputStream); this.gedxOutputStream.close(); } }