All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.day.durbo.DurboOutput Maven / Gradle / Ivy

/*
 * Copyright 1997-2008 Day Management AG
 * Barfuesserplatz 6, 4001 Basel, Switzerland
 * All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Day Management AG, ("Confidential Information"). You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Day.
 */
package com.day.durbo;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.jcr.NamespaceException;
import javax.jcr.Property;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;

import org.apache.commons.io.IOUtils;

import com.day.durbo.impl.DurboOutputStream;
import com.day.durbo.io.ChunkedDeflaterOutputStream;
import com.day.durbo.io.ChunkedInflaterInputStream;

/**
 * The DurboOutput class provides base implementation for the Durbo
 * serialization. The Durbo is hierarchical name-value pair serialization
 * that is optimized for stream based operation. the basic principal is, that
 * every data element is stored using RLE encoding, i.e. consists of the size
 * of the data, followed by the data itself. on the logical level, those elements
 * can be properties or nodes. the properties consist of zero, one or more
 * values, which are either strings or binaries. the properties itself also
 * hold a high-level property type, namely the {@link PropertyType}s from jsr170.
 * the additional (invisible) element 'namespace' is used to sequentially define
 * namespaces that will be used in the subsequent elements.
 * 

* since version 2.1 the deflater of the output stream is reset about every 2^12 * bytes (1mb). this generates a output stream that consists of several * compressed chunks. this means that a possible reader must be prepared for * resetting it's inflater as well. see {@link ChunkedDeflaterOutputStream} and * {@link ChunkedInflaterInputStream} for details. this has only an advantage * during deserialization when large binary properties are kept in * {@link DurboValue}s and needed to be read from the consumer. *

* Version 2.0 introduced: *

    *
  • JCR property type support *
  • namespace support *
  • zip compression *
*

* Version 2.1 introduced: *

    *
  • chunked zip compression *
*

*

*

* durbo:= header {elem}; * header:= hdrVersion hdrContentType hdrEncoding; * hdrVersion := svProp; // with name {@link DurboConstants#PROTOCOL_HEADER} * hdrContentType := svProp; // with name {@link DurboConstants#PROTOCOL_CONTENT_TYPE} * hdrEncoding := svProp; // with name {@link DurboConstants#PROTOCOL_ENCODING} * <p/> * elem:= node | prop | namespace; * node:= NODE_START name elemlist NODE_END; * prop:= svProp | mvProp; * svProp:= PROPERTY|type name value; * mvProp:= PROPERTY|MULTIPLE|type name <# values> {value}; * namespace:= NAMESPACE prefix uri; * prefix:= sValue; * uri := sValue; * name:= sValue; * value:= sValue | bValue; * <p/> * sValue:= <size of string.getBytes()> <string.getBytes()>; * bValue:= <size of data> {byte}; * <p/> * NODE_START:= {@link DurboConstants#NODE_START}; * NODE_END:= {@link DurboConstants#NODE_END}; * PROPERTY:= {@link DurboConstants#PROPERTY}; * MULTIPLE:= {@link DurboConstants#MULTIPLE}; * NAMESPACE:= {@link DurboConstants#NAMESPACE}; * <p/> *

*/ public class DurboOutput implements DurboConstants { /** * version 1.0 binary property type */ private static final int PROPERTY_TYPE_BINARY_V1 = 0x10; /** * version 1.0 string property type */ private static final int PROPERTY_TYPE_STRING_V1 = 0x11; /** * output stream */ private final DurboOutputStream out; /** * the namespace resolver */ private final DurboNamespaceResolver resolver; /** * defined namespaces */ private final Map namespaces = new HashMap(); /** * the version to use */ private final double version; /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. *

* please note that the protocol version is set to {@link DurboConstants#PROTOCOL_VERSION_1} * which is probably what you want. *

* please note that this uses the {@link IdentityNamespaceResolver} and can * therefor generate weird results upon deserialization. * * @param out the output stream * @throws IOException if an error occurs */ public DurboOutput(OutputStream out) throws IOException { this(out, new IdentityNamespaceResolver(), null, false, PROTOCOL_VERSION_1); } /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. *

* please note that this uses the {@link IdentityNamespaceResolver} and can * therefor generate weird results upon deserialization. * * @param out the output stream * @param version the protocol version to use. default is * {@link DurboConstants#PROTOCOL_VERSION} * @throws IOException if an error occurs */ public DurboOutput(OutputStream out, double version) throws IOException { this(out, new IdentityNamespaceResolver(), null, false, version); } /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. * * @param out the output stream * @param resolver the namespace resolver * @throws IOException if an error occurs */ public DurboOutput(OutputStream out, DurboNamespaceResolver resolver) throws IOException { this(out, resolver, null, false); } /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. * * @param out the output stream * @param resolver the namespace resolver * @param version the protocol version to use. default is * {@link DurboConstants#PROTOCOL_VERSION} * @throws IOException if an error occurs */ public DurboOutput(OutputStream out, DurboNamespaceResolver resolver, double version) throws IOException { this(out, resolver, null, false, version); } /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. * * @param out the output stream * @param resolver the namespace resolver * @param contentType the content type to include in the header. default is * {@link DurboConstants#DEFAULT_CONTENT_TYPE} * @param compressed if true output stream will be compressed. * @throws IOException if an error occurs */ public DurboOutput(OutputStream out, DurboNamespaceResolver resolver, String contentType, boolean compressed) throws IOException { this(out, resolver, contentType, compressed, PROTOCOL_VERSION); } /** * Creates a new DurboOutput that uses the given output stream. * this also writes the protocol header. * * @param out the output stream * @param resolver the namespace resolver * @param contentType the content type to include in the header. default is * {@link DurboConstants#DEFAULT_CONTENT_TYPE} * @param compressed if true output stream will be compressed. * @param version the protocol version to use. default is * {@link DurboConstants#PROTOCOL_VERSION} * @throws IOException if an error occurs */ public DurboOutput(OutputStream out, DurboNamespaceResolver resolver, String contentType, boolean compressed, double version) throws IOException { this.resolver = resolver; this.version = version; this.out = new DurboOutputStream(out); if (compressed && version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("Compression is not supported in protocol version " + version); } if (contentType != null && version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("ContentType is not supported in protocol version " + version); } // write header writeProperty(PROTOCOL_HEADER, String.valueOf(version)); if (version >= PROTOCOL_VERSION_2) { writeProperty(PROTOCOL_CONTENT_TYPE, contentType == null ? DEFAULT_CONTENT_TYPE : contentType); writeProperty(PROTOCOL_ENCODING, compressed ? "zip" : ""); } if (compressed) { this.out.enableCompression(); } } /** * closes the output. * * @throws IOException if an I/O error occurs */ public void close() throws IOException { this.out.close(); } /** * writes a namespace definition to the stream. * * @param prefix the namespace prefix * @param uri the namespace uri * @throws IOException if an I/O exception occurs */ public void defineNamespace(String prefix, String uri) throws IOException { if (version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("Namespaces are not supported in protocol version " + version); } if (namespaces.containsKey(prefix)) { // check if correct uri if (!uri.equals(namespaces.get(prefix))) { throw new UnsupportedOperationException("Namespace remapping not implemented."); } } else { out.writeByte(NAMESPACE); out.write(prefix); out.write(uri); namespaces.put(prefix, uri); } } /** * Writes a binary property with the given name and data to the output. * * @param name the name of the property * @param data the data to write to the stream * @throws IOException if an error occurs */ public void writeProperty(String name, byte[] data) throws IOException { writeHeader(PROPERTY | PropertyType.BINARY, name); out.write(data); } /** * Writes a string property with the given name and data * to the output. * * @param name the name of the property * @param data the data to write to the stream * @throws IOException if an error occurs */ public void writeProperty(String name, String data) throws IOException { writeHeader(PROPERTY | PropertyType.STRING, name); out.write(data); } /** * Writes a JCR property to the output * * @param prop the property to write * @throws IOException if an I/O error occurs * @throws RepositoryException if an error occurs */ public void writeProperty(Property prop) throws IOException, RepositoryException { Value[] values = prop.getDefinition().isMultiple() ? prop.getValues() : new Value[]{prop.getValue()}; if (version >= PROTOCOL_VERSION_2) { // check for namespaces in properties if (prop.getType() == PropertyType.NAME) { for (Value value : values) { checkNamespace(value.getString()); } } else if (prop.getType() == PropertyType.PATH) { for (Value value : values) { checkNamespacesInPath(value.getString()); } } } if (prop.getDefinition().isMultiple()) { if (version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("Multivalue properties are not supported in protocol version " + version); } writeHeader(PROPERTY | MULTIPLE | prop.getType(), prop.getName()); out.writeInt(values.length); for (Value value : values) { out.write(value); } } else { writeHeader(PROPERTY | prop.getType(), prop.getName()); out.write(values[0]); } } /** * Writes the durbo property to the output * * @param prop the property * @throws IOException if an I/O error occurs */ public void writeProperty(DurboInput.Property prop) throws IOException { DurboValue[] values = prop.getValues(); if (prop.isMultiple()) { if (version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("Multivalue properties are not supported in protocol version " + version); } writeHeader(PROPERTY | MULTIPLE | prop.getType(), prop.name()); out.writeInt(prop.getValues().length); for (DurboValue value : values) { out.write(value); } } else { writeHeader(PROPERTY | prop.getType(), prop.name()); out.write(values[0]); } } /** * Writes a typed multivalue property. * * @param name name of the property * @param type type of the property * @param values values of the property * @throws IOException if an I/O error occurs */ public void writeProperty(String name, int type, String[] values) throws IOException { writeProperty(name, type, values, true); } /** * Writes a typed single value property. * * @param name name of the property * @param type type of the property * @param value value of the property * @throws IOException if an I/O error occurs */ public void writeProperty(String name, int type, String value) throws IOException { writeProperty(name, type, new String[]{value}, false); } /** * Writes a typed property. * * @param name name of the property * @param type type of the property * @param values values of the property * @param isMultiple true if a mv property * @throws IOException if an I/O error occurs */ private void writeProperty(String name, int type, String[] values, boolean isMultiple) throws IOException { if (version >= PROTOCOL_VERSION_2) { // check for namespaces in properties if (type == PropertyType.NAME) { for (String value : values) { checkNamespace(value); } } else if (type == PropertyType.PATH) { for (String value : values) { checkNamespacesInPath(value); } } } if (isMultiple) { if (version < PROTOCOL_VERSION_2) { throw new UnsupportedOperationException("Multivalue properties are not supported in protocol version " + version); } writeHeader(PROPERTY | MULTIPLE | type, name); out.writeInt(values.length); for (String value : values) { out.write(value); } } else { writeHeader(PROPERTY | type, name); out.write(values[0]); } } /** * Checks the namespaces in all the path elements. * * @param path path to check * @throws IOException if an I/O error occurs * @see #checkNamespace(String) */ private void checkNamespacesInPath(String path) throws IOException { // check path elements int pos, lastpos = 0; while ((pos = path.indexOf('/', lastpos)) >= 0) { if (pos - lastpos > 0) checkNamespace(path.substring(lastpos, pos)); lastpos = pos + 1; } // check rest if (lastpos < path.length()) { checkNamespace(path.substring(lastpos)); } } /** * Write a binary property with the given name and data provided in * the input stream to to the output. * * @param name the name of the property * @param in the data to write to the stream * @throws IOException if an error occurs */ public void writeProperty(String name, InputStream in) throws IOException { // TODO: use buffered write below? ByteArrayOutputStream tmp = new ByteArrayOutputStream(); IOUtils.copy(in, tmp); in.close(); writeProperty(name, tmp.toByteArray()); } /** * Write a property with the given name and data provided in * the input stream to to the output. * * @param name the name of the property * @param in the data to write to the stream * @param size The number of bytes to write from the InputStream. * If this value is negative, as much is read from the input stream * as possible. * @throws IOException if an error occurs */ public void writeProperty(String name, InputStream in, int size) throws IOException { // TODO: use buffered write below? writeHeader(PROPERTY | PropertyType.BINARY, name); if (size < 0) { out.write(in); } else { out.write(in, size); } } /** * Writes an open node marker to the output with the given node * name. * * @param name the name of the node * @throws IOException if an error occurs */ public void openNode(String name) throws IOException { writeHeader(NODE_START, name); } /** * Writes an closing node marker to the output. * * @throws java.io.IOException if an error occurs */ public void closeNode() throws IOException { out.writeByte(NODE_END); } /** * writes a header * * @param type property type of the header * @param name name of the header * @throws IOException if an I/O error occurs */ private void writeHeader(int type, String name) throws IOException { if (version >= PROTOCOL_VERSION_2) { checkNamespace(name); } else { if ((type & PROPERTY) > 0) { if ((type & PROPERTY_TYPE_MASK) == PropertyType.BINARY) { type = PROPERTY_TYPE_BINARY_V1; } else { type = PROPERTY_TYPE_STRING_V1; } } } out.writeByte(type); out.write(name); } /** * checks if the namespace pointed to by the prefix of the name exists * and is already defined in the output. * * @param name name of the namespace * @throws IOException if an I/O error occurs */ private void checkNamespace(String name) throws IOException { int pos = name.indexOf(':'); if (pos > 0) { // check namespace String prefix = name.substring(0, pos); try { defineNamespace(prefix, resolver.getURI(prefix)); } catch (NamespaceException e) { throw new IllegalArgumentException(e.toString()); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy