org.ozsoft.xantippe.Document.java Source code

Java tutorial

Introduction

Here is the source code for org.ozsoft.xantippe.Document.java

Source

// This file is part of the Xantippe XML database.
//
// Copyright 2008 Oscar Stigter
//
// 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.ozsoft.xantippe;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ozsoft.xantippe.filestore.FileStore;
import org.ozsoft.xantippe.filestore.FileStoreException;

/**
 * A document stored in the database.
 * 
 * @author Oscar Stigter
 */
public class Document implements Comparable<Document> {

    /** Log */
    private static final Log LOG = LogFactory.getLog(Document.class);

    /** Buffer size for byte stream operations. */
    private static final int BUFFER_SIZE = 8192; // 8 kB

    /** Back-reference to the database. */
    private final DatabaseImpl database;

    /** ID. */
    private final int id;

    /** Name. */
    private String name;

    /** Media type. */
    private final MediaType mediaType;

    /** Document length. */
    private int length = 0;

    /** Creation timestamp. */
    private final long created;

    /** Modification timestamp. */
    private long modified;

    /** Parent collection ID. */
    private int parent;

    /** Compression mode. */
    private CompressionMode compressionMode = CompressionMode.NONE;

    /* package */ Document(DatabaseImpl database, int id, String name, MediaType mediaType, long created,
            long modified, int parent) {
        this.database = database;
        this.id = id;
        this.name = name;
        this.mediaType = mediaType;
        this.created = created;
        this.modified = modified;
        this.parent = parent;

        FileStore fileStore = database.getFileStore();
        if (!fileStore.exists(id)) {
            try {
                fileStore.create(id);
            } catch (FileStoreException e) {
                // Administrative error (should never happen).
                String msg = String.format("Error creating document '%s' in FileStore; ID not unique", this);
                LOG.error(msg, e);
            }
        }

        database.addDocument(this);
    }

    /**
     * Returns the document's name.
     * 
     * @return  the document's name
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the collection this document is stored in.
     * 
     * @return  the collection
     */
    public Collection getParent() {
        return database.getCollection(parent);
    }

    public String getContentType() {
        return mediaType.getContentType();
    }

    /**
     * Returns the document's media type.
     * 
     * @return  the media type
     */
    public MediaType getMediaType() {
        return mediaType;
    }

    /**
     * Returns the document's length in bytes.
     * 
     * @return  the length
     */
    public int getLength() {
        return length;
    }

    /**
     * Returns the document's stored length in bytes.
     * 
     * @return  the stored length
     */
    public int getStoredLength() {
        return database.getFileStore().getLength(id);
    }

    /**
     * Returns the timestamp the document was created.
     * 
     * @return  the timestamp in milliseconds after 01-Jan-1970
     */
    public long getCreated() {
        return created;
    }

    /**
     * Returns the timestamp the document was last modified.
     * 
     * @return  the timestamp in milliseconds after 01-Jan-1970
     */
    public long getModified() {
        return modified;
    }

    /**
     * Returns the document's absolute URI.
     * 
     * @return  the URI
     */
    public String getUri() {
        Collection parent = getParent();
        StringBuilder sb = new StringBuilder();
        if (parent != null) {
            sb.append(parent.getUri());
        }
        sb.append('/');
        sb.append(name);
        return sb.toString();
    }

    /**
     * Returns the document's content.
     * 
     * @return  an InputStream to read the content from
     * 
     * @throws  XmldbException  if the contents could not be retrieved
     */
    public InputStream getContent() throws XmldbException {
        FileStore fileStore = database.getFileStore();

        database.getLockManager().lockRead(this);

        InputStream is = null;
        try {
            is = fileStore.retrieve(id);
            if (compressionMode == CompressionMode.DEFLATE) {
                is = new InflaterInputStream(is);
            }
        } catch (FileStoreException e) {
            String msg = String.format("Could not retrieve document '%s': %s", this, e.getMessage());
            LOG.error(msg, e);
            throw new XmldbException(msg, e);
        } finally {
            database.getLockManager().unlockRead(this);
        }

        return is;
    }

    /**
     * Sets the document's content using an OutputStream.
     * The streamed document's content is written to a temporary file. 
     * When the client closes the stream, the document is stored using the
     * setContent(File) method.
     * 
     * @return  an OutputStream to write the document's content to
     * 
     * @throws XmldbException
     *             if the document is invalid, or it could not be stored
     */
    public OutputStream setContent() throws XmldbException {
        try {
            return new InsertStream(this);
        } catch (IOException e) {
            String msg = String.format("Could not store document: '%s'", this);
            LOG.error(msg, e);
            throw new XmldbException(msg, e);
        }
    }

    /**
     * Sets the document's content based on a file.
     * 
     * In case of an XML document and depending on the validation mode of the
     * collection, the document may be validated against its schema before it
     * is stored.
     * 
     * In case of a schema it is parsed and registered by its target namespace.
     * 
     * Depending on the compression mode of the collection, the document may be
     * stored in compressed form.
     *  
     * @param   file  the file with the document's content
     * 
     * @throws  IllegalArgumentException  if the file is null 
     * 
     * @throws  XmldbException
     *              if the file does not exist, could not be read or parsed, or
     *              the document could not be compressed or stored
     */
    public void setContent(File file) throws XmldbException {
        if (file == null) {
            throw new IllegalArgumentException("Null file");
        }

        if (!file.isFile()) {
            String msg = "File not found: " + file;
            LOG.error(msg);
            throw new XmldbException(msg);
        }

        if (!file.canRead()) {
            String msg = "No read permission on file: " + file;
            LOG.error(msg);
            throw new XmldbException(msg);
        }

        Collection parentCol = getParent();

        if (mediaType == MediaType.XML) {
            String uri = getUri();
            ValidationMode vm = parentCol.getValidationMode(true);
            switch (vm) {
            case ON:
                // Validate document against mandatory schema. 
                database.getValidator().validate(file, uri, true);
                break;
            case AUTO:
                // Validate document if possible (namespace and schema).
                database.getValidator().validate(file, uri, false);
                break;
            case OFF:
                // No validation.
                break;
            default:
                // Should never happen.
                LOG.error("Invalid validation mode: " + vm);
            }
        } else if (mediaType == MediaType.SCHEMA) {
            try {
                database.getValidator().addSchema(file, id);
            } catch (Exception e) {
                String msg = String.format("Invalid schema file: '%s': %s", file, e.getMessage());
                LOG.error(msg);
                throw new XmldbException(msg);
            }
        }

        // Use compression mode as configured for collection.
        compressionMode = parentCol.getCompressionMode(true);

        File storedFile = null;

        try {
            if (compressionMode != CompressionMode.NONE) {
                // Compress document to a temporary file.
                storedFile = File.createTempFile("xantippe-", null);
                OutputStream os;
                if (compressionMode == CompressionMode.DEFLATE) {
                    os = new DeflaterOutputStream(new FileOutputStream(storedFile));
                } else {
                    // Should never happen.
                    String msg = "Invalid compression mode: " + compressionMode;
                    LOG.error(msg);
                    throw new XmldbException(msg);
                }
                InputStream is = new FileInputStream(file);
                byte[] buffer = new byte[BUFFER_SIZE];
                int read;
                while ((read = is.read(buffer)) > 0) {
                    os.write(buffer, 0, read);
                }
                os.close();
                is.close();
            } else {
                // No compression; store document as-is.
                storedFile = file;
            }

            database.getLockManager().lockWrite(this);

            database.getFileStore().store(id, storedFile);

            if (mediaType == MediaType.XML) {
                parentCol.indexDocument(this, file);
            }

            length = (int) file.length();

            updateModified();

        } catch (IOException e) {
            String msg = String.format("Error while compressing document '%s': %s", this, e.getMessage());
            LOG.error(msg, e);
            throw new XmldbException(msg, e);

        } catch (FileStoreException e) {
            String msg = String.format("Error while storing document '%s': %s", this, e.getMessage());
            LOG.error(msg, e);
            throw new XmldbException(msg, e);

        } finally {
            database.getLockManager().unlockWrite(this);

            if (compressionMode != CompressionMode.NONE && storedFile != null) {
                // Always clean up temporary (compressed) file.
                storedFile.delete();
            }
        }
    }

    /**
     * Sets a manual key value for this document.
     * 
     * An index value for this document is added to the collecting the document
     * is stored in.
     * 
     * @param   name   the key name
     * @param   value  the key value
     * 
     * @throws  IllegalArgumentException
     *              if the name is null or empty, or the value is null
     */
    public void setKey(String name, Object value) {
        if (name == null || name.length() == 0) {
            throw new IllegalArgumentException("Null or empty name");
        }
        if (value == null) {
            throw new IllegalArgumentException("Null value");
        }

        getParent().addIndexValue(name, value, id);
    }

    /**
     * Returns the hash code of this object.
     * 
     * @return  the hash code
     */
    @Override // Object
    public int hashCode() {
        return id;
    }

    /**
     * Return true if this document is equal to the specified Object, otherwise
     * false.
     * 
     * @param  obj  the Object this document is compared to
     * 
     * @return  true if equal, otherwise false
     */
    @Override // Object
    public boolean equals(Object obj) {
        if (obj instanceof Document) {
            Document doc = (Document) obj;
            return doc.getId() == id;
        } else {
            return false;
        }
    }

    /**
     * Compares this document with the specified document.
     * 
     * Documents are compared based on their name (alphabetically).
     * 
     * @param  doc  the document to compare this document with
     * 
     * @return  the comparison value 
     */
    public int compareTo(Document doc) {
        return name.compareTo(doc.getName());
    }

    /**
     * Returns a string representation of this document.
     * 
     * A document is represented by its absolute URI.
     * 
     * @return  the string representation
     */
    @Override
    public String toString() {
        return getUri();
    }

    /* package */ int getId() {
        return id;
    }

    /* package */ CompressionMode getCompressionMode() {
        return compressionMode;
    }

    /* package */ void setCompressionMode(CompressionMode compressionMode) {
        this.compressionMode = compressionMode;
    }

    /* package */ void delete() {
        database.deleteDocument(this);
        String msg = String.format("Deleted document '%s'", this);
        LOG.debug(msg);
    }

    private void updateModified() {
        modified = System.currentTimeMillis();
    }

}