com.marklogic.xcc.ContentFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.marklogic.xcc.ContentFactory.java

Source

/*
 * Copyright 2003-2016 MarkLogic Corporation
 *
 * 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 com.marklogic.xcc;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.nio.channels.Channels;

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Node;

import com.fasterxml.jackson.databind.JsonNode;
import com.marklogic.xcc.types.XdmNode;

// Need an example of a user-implementation of the {@link Content}
// interface for sources like Web Services or SQL ResultSets.
/**
 * <p>
 * A helper class for creating instances of {@link Content}.
 * </p>
 */
public class ContentFactory {
    private ContentFactory() {
        // This is a helper class, cannot be instantiated
    }

    /**
     * Create a new {@link Content} object from a W3C DOM {@link Document} object. If not explicitly
     * overridden, the document format will be set to XML. This factory method makes use of the
     * standard JAX {@link TransformerFactory} facility to serialize the DOM to a UTF-8-encoded byte
     * stream. See {@link javax.xml.transform.TransformerFactory#newInstance()} for information on
     * how to customize the transformer implementation.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param document
     *            A W3C DOM {@link Document} object which is the content.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newContent(String uri, Document document, ContentCreateOptions createOptions) {
        return newContent(uri, bytesFromW3cDoc(document),
                (createOptions == null) ? ContentCreateOptions.newXmlInstance() : createOptions);
    }

    /**
     * Create a new {@link Content} object from a W3C DOM {@link Node} object.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content. If
     *            not explicitly overridden, the document format will be set to XML. This factory
     *            method makes use of the standard JAX {@link TransformerFactory} facility to
     *            serialize the DOM to a UTF-8-encoded byte stream. See
     *            {@link javax.xml.transform.TransformerFactory#newInstance()} for information on
     *            how to customize the transformer implementation.
     * @param documentNode
     *            A W3C DOM {@link Node} object which is the content. Only Nodes of type Element or
     *            Text are valid.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newContent(String uri, Node documentNode, ContentCreateOptions createOptions) {
        return newContent(uri, bytesFromW3cNode(documentNode),
                (createOptions == null) ? ContentCreateOptions.newXmlInstance() : createOptions);
    }

    /**
     * Create a new {@link Content} object from a Jackson {@link Node} object.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content. If
     *            not explicitly overridden, the document format will be set to JSON.
     * @param documentNode
     *            A Jackson {@link Node} object which is the content. Only Nodes of type Array or
     *            Object are valid.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newJsonContent(String uri, JsonNode documentNode, ContentCreateOptions createOptions) {
        return newContent(uri, bytesFromString(documentNode.toString()),
                (createOptions == null) ? ContentCreateOptions.newJsonInstance() : createOptions);
    }

    /**
     * Create a new {@link Content} object from an {@link XdmNode}. This object may have been
     * constructed locally or received as a member of a {@link ResultSequence}. If not explicitly
     * overridden, the document format will be set to XML.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentNode
     *            An instance of {@link XdmNode} which is the document content. Only nodes of type
     *            {@link com.marklogic.xcc.types.XdmElement},
     *            {@link com.marklogic.xcc.types.XdmText} or
     *            {@link com.marklogic.xcc.types.XdmBinary} are valid.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newContent(String uri, XdmNode documentNode, ContentCreateOptions createOptions) {
        return newContent(uri, bytesFromString(documentNode.asString()),
                (createOptions == null) ? ContentCreateOptions.newXmlInstance() : createOptions);
    }

    /**
     * <p>
     * Create a new {@link Content} object from a File object.
     * </p>
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentFile
     *            A File object from which the content will be read. If the createOptions argument
     *            selects {@link DocumentFormat#BINARY}, the content of the file will be transfered
     *            as an opaque blob. Otherwise, the file is assumed to be UTF-8 encoded text.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newContent(String uri, File documentFile, ContentCreateOptions createOptions) {
        return new FileContent(uri, documentFile, createOptions);
    }

    /**
     * Create a new {@link Content} object from a RandomAccessFile object.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentFile
     *            An open RandomAccessaFile object from which the content will be read. File data
     *            will be read from the current position to end-of-file. If the createOptions
     *            argument selects {@link DocumentFormat#BINARY}, the content of the file will be
     *            transfered as an opaque blob. Otherwise, the file is assumed to be UTF-8 encoded
     *            text.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws java.io.IOException
     *             If there is a problem reading data from the file.
     */
    public static Content newContent(String uri, RandomAccessFile documentFile, ContentCreateOptions createOptions)
            throws IOException {
        return new RandomAccessFileContent(uri, documentFile, createOptions);
    }

    /**
     * Create a new, non-rewindable {@link Content} object from a {@link URL}.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentUrl
     *            A {@link URL} object that represents the location from which the content can be
     *            fetched.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws IOException
     *             If there is a problem creating a {@link URL} or opening a data stream.
     */
    public static Content newUnBufferedContent(String uri, URL documentUrl, ContentCreateOptions createOptions)
            throws IOException {
        return new InputStreamContent(uri, documentUrl.openStream(), createOptions);
    }

    /**
     * Create a new, non-rewindable {@link Content} object from a {@link URI}.
     * 
     * @param uri
     *            The {@link URI} (name) with which the document will be inserted into the content
     *            store. If the URI already exists in the store, it will be replaced with the new
     *            content.
     * @param documentUri
     *            A URI object that represents the location from which the content can be fetched.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws IOException
     *             If there is a problem creating a {@link URL} or opening a data stream.
     */
    public static Content newUnBufferedContent(String uri, URI documentUri, ContentCreateOptions createOptions)
            throws IOException {
        return newUnBufferedContent(uri, documentUri.toURL(), createOptions);
    }

    /**
     * Create a new {@link Content} object from a URI, buffering so that it's rewindable and only
     * accesses the URL one time.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentUrl
     *            A {@link URL} object that represents the location from which the content can be
     *            fetched.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws IOException
     *             If there is a problem creating a {@link URL} or reading the data from the stream.
     */
    public static Content newContent(String uri, URL documentUrl, ContentCreateOptions createOptions)
            throws IOException {
        return new ByteArrayContent(uri, bytesFromStream(documentUrl.openStream()), createOptions);
    }

    /**
     * Create a new {@link Content} object from a URI, buffering so that it's rewindable and only
     * accesses the URL one time.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentUri
     *            A {@link URI} object that represents the location from which the content can be
     *            fetched. Unlike {@link URL}s, {@link URI} objects are not validated when they are
     *            created. This method converts the {@link URI} to a {@link URL}. If this parameter
     *            does not represent a valid URL, an exception will be thrown.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws IOException
     *             If there is a problem creating a {@link URL} or reading the data from the stream.
     * @throws IllegalArgumentException
     *             If the URI is not absolute.
     */
    public static Content newContent(String uri, URI documentUri, ContentCreateOptions createOptions)
            throws IOException {
        return newContent(uri, documentUri.toURL(), createOptions);
    }

    /**
     * Create a new {@link Content} object from a String.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentString
     *            A String which will be stored as the content of the document. If the createOptions
     *            argument selects {@link DocumentFormat#BINARY}, the bytes representing the UTF-8
     *            encoding of the String will be stored.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @see DocumentFormat
     */
    public static Content newContent(String uri, String documentString, ContentCreateOptions createOptions) {
        return newContent(uri, bytesFromString(documentString), createOptions);
    }

    /**
     * Create a new {@link Content} object from a byte array.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentBytes
     *            A byte array which will be stored as the content of the document. If the
     *            createOptions argument is null, then {@link DocumentFormat#BINARY} will be
     *            assumed.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @see DocumentFormat
     */
    public static Content newContent(String uri, byte[] documentBytes, ContentCreateOptions createOptions) {
        return new ByteArrayContent(uri, documentBytes, createOptions);
    }

    /**
     * Create a new {@link Content} object from a subset of a byte array.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new content.
     * @param documentBytes
     *            A byte array which will be stored as the content of the document. If the
     *            createOptions argument is null, then {@link DocumentFormat#BINARY} will be
     *            assumed.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @param offset
     *            The starting point of the content in the array.
     * @param length
     *            The length of the content in the array.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @see DocumentFormat
     * @since 3.2
     */
    public static Content newContent(String uri, byte[] documentBytes, int offset, int length,
            ContentCreateOptions createOptions) {
        return new ByteArrayContent(uri, documentBytes, offset, length, createOptions);
    }

    // ---------------------------------------------------------------

    /**
     * Create a new {@link Content} object by consuming the given InputStream and buffereing it in
     * memory. This factory method immediately reads the stream to the end and buffers it. This
     * could result in an OutOfMemoryError if the stream contains a large amount of data. The
     * provided documentStream will be closed.
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new value.
     * @param documentStream
     *            The stream making up the document content.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     * @throws IOException
     *             If there is a problem reading the documentStream.
     */
    public static Content newContent(String uri, InputStream documentStream, ContentCreateOptions createOptions)
            throws IOException {
        return new ByteArrayContent(uri, bytesFromStream(documentStream), createOptions);
    }

    /**
     * <p>
     * Create a new non-rewindable {@link Content} object for the given InputStream. Note that the
     * {@link Content} instance returned is not rewindable (
     * {@link com.marklogic.xcc.Content#isRewindable()} == false) which means that auto-retry cannot
     * be performed is there is a problem inserting the content.
     * </p>
     * 
     * @param uri
     *            The URI (name) with which the document will be inserted into the content store. If
     *            the URI already exists in the store, it will be replaced with the new value.
     * @param documentStream
     *            The stream making up the document content.
     * @param createOptions
     *            Creation meta-information to be applied when the content is inserted into the
     *            contentbase. These options control the document format (json, xml, text, binary) and
     *            access permissions.
     * @return A non-rewindable {@link Content} object suitable for passing to
     *         {@link Session#insertContent(Content)}
     */
    public static Content newUnBufferedContent(String uri, InputStream documentStream,
            ContentCreateOptions createOptions) {
        return new InputStreamContent(uri, documentStream, createOptions);
    }

    // ---------------------------------------------------------------

    private static byte[] bytesFromString(String string) {
        try {
            return string.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            return string.getBytes();
        }
    }

    private static byte[] bytesFromStream(InputStream is) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        byte[] buffer = new byte[64 * 1024];
        int rc;

        while ((rc = is.read(buffer)) != -1) {
            os.write(buffer, 0, rc);
        }

        is.close();
        os.flush();
        buffer = os.toByteArray();
        os.close();

        return (buffer);
    }

    private static TransformerFactory transformerFactory = null;

    private static synchronized TransformerFactory getTransformerFactory() {
        if (transformerFactory == null) {
            transformerFactory = TransformerFactory.newInstance();
        }

        return transformerFactory;
    }

    static byte[] bytesFromW3cNode(Node node) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Result rslt = new StreamResult(bos);
        Source src = new DOMSource(node);
        Transformer transformer;

        try {
            transformer = getTransformerFactory().newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            transformer.transform(src, rslt);
        } catch (TransformerException e) {
            throw new RuntimeException("Cannot serialize Node: " + e, e);
        }

        return bos.toByteArray();
    }

    static byte[] bytesFromW3cDoc(Document document) {
        return (bytesFromW3cNode(document.getDocumentElement()));
    }

    // ---------------------------------------------------------------
    // ---------------------------------------------------------------

    private static class ByteArrayContent implements Content {
        private final String uri;
        private final byte[] bytes;
        private final int offset;
        private final int length;
        private final ContentCreateOptions options;

        public ByteArrayContent(String uri, byte[] bytes, int offset, int length, ContentCreateOptions options) {
            if ((uri == null) || (uri.length() == 0)) {
                throw new IllegalArgumentException("URI may not be null or zero-length");
            }
            this.uri = uri;
            this.bytes = bytes;
            this.offset = offset;
            this.length = length;
            this.options = options;
        }

        public ByteArrayContent(String uri, byte[] bytes, ContentCreateOptions options) {
            this(uri, bytes, 0, bytes.length, options);
        }

        public String getUri() {
            return (uri);
        }

        public InputStream openDataStream() {
            return new ByteArrayInputStream(bytes, offset, length);
        }

        public ContentCreateOptions getCreateOptions() {
            return options;
        }

        public boolean isRewindable() {
            return true;
        }

        public void rewind() {
        }

        public long size() {
            return length;
        }

        public void close() {
        }
    }

    private static class FileContent implements Content {
        private final String uri;
        private final ContentCreateOptions options;
        private final File file;
        private InputStream activeStream;

        public FileContent(String uri, File file, ContentCreateOptions options) {
            if ((uri == null) || (uri.length() == 0)) {
                throw new IllegalArgumentException("URI may not be null or zero-length");
            }
            this.uri = uri;
            this.file = file;
            this.options = options;
        }

        public String getUri() {
            return uri;
        }

        public InputStream openDataStream() throws FileNotFoundException {
            activeStream = new BufferedInputStream(new FileInputStream(file));

            return activeStream;
        }

        public ContentCreateOptions getCreateOptions() {
            return options;
        }

        public boolean isRewindable() {
            return true;
        }

        public void rewind() throws IOException {
            close();
        }

        public long size() {
            return file.length();
        }

        public void close() {
            if (activeStream != null) {
                try {
                    activeStream.close();
                } catch (IOException e) {
                    // don't care
                }

                activeStream = null;
            }
        }
    }

    private static class RandomAccessFileContent implements Content {
        private final String uri;
        private final ContentCreateOptions options;
        private final RandomAccessFile raFile;
        private final long start;

        public RandomAccessFileContent(String uri, RandomAccessFile raFile, ContentCreateOptions options)
                throws IOException {
            if ((uri == null) || (uri.length() == 0)) {
                throw new IllegalArgumentException("URI may not be null or zero-length");
            }
            this.uri = uri;
            this.raFile = raFile;
            this.options = options;

            start = raFile.getFilePointer();
        }

        public String getUri() {
            return uri;
        }

        public InputStream openDataStream() throws IOException {
            raFile.seek(start);

            return Channels.newInputStream(raFile.getChannel());
        }

        public ContentCreateOptions getCreateOptions() {
            return options;
        }

        public boolean isRewindable() {
            return true;
        }

        public void rewind() throws IOException {
            raFile.seek(start);
        }

        public long size() {
            try {
                return raFile.length() - start;
            } catch (IOException e) {
                return -1;
            }
        }

        public void close() {
            try {
                raFile.close();
            } catch (IOException e) {
                // smother it, we don't care
            }
        }
    }

    private static class InputStreamContent implements Content {
        private final String uri;
        private final ContentCreateOptions options;
        private InputStream is;

        public InputStreamContent(String uri, InputStream is, ContentCreateOptions options) {
            this.uri = uri;
            this.is = is;
            this.options = options;
        }

        public String getUri() {
            return uri;
        }

        public InputStream openDataStream() throws FileNotFoundException {
            if (is == null) {
                throw new IllegalStateException("Data stream has already been consumed");
            }

            InputStream isTmp = is;

            is = null;

            return isTmp;
        }

        public ContentCreateOptions getCreateOptions() {
            return options;
        }

        public boolean isRewindable() {
            return false;
        }

        public void rewind() throws IOException {
            if (is == null) {
                throw new IllegalStateException("This Content is not rewindable");
            }
        }

        public long size() {
            return -1;
        }

        public void close() {
            is = null;
        }
    }
}