Package org.apache.xindice.core

Source Code of org.apache.xindice.core.Collection$ColDocumentSet

/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* 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.
*
* CVS $Id: Collection.java,v 1.52 2004/03/20 13:43:23 vgritsenko Exp $
*/

package org.apache.xindice.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.data.DocumentSet;
import org.apache.xindice.core.data.EmptyDocumentSet;
import org.apache.xindice.core.data.EmptyNodeSet;
import org.apache.xindice.core.data.Key;
import org.apache.xindice.core.data.NodeSet;
import org.apache.xindice.core.data.Record;
import org.apache.xindice.core.data.RecordSet;
import org.apache.xindice.core.data.Value;
import org.apache.xindice.core.filer.Filer;
import org.apache.xindice.core.indexer.IndexManager;
import org.apache.xindice.core.indexer.Indexer;
import org.apache.xindice.core.meta.MetaData;
import org.apache.xindice.core.meta.inline.InlineMetaMap;
import org.apache.xindice.core.meta.inline.InlineMetaService;
import org.apache.xindice.core.meta.inline.ResourceTypeReader;
import org.apache.xindice.core.query.QueryEngine;
import org.apache.xindice.util.Configurable;
import org.apache.xindice.util.Configuration;
import org.apache.xindice.util.Named;
import org.apache.xindice.util.XindiceException;
import org.apache.xindice.xml.NamespaceMap;
import org.apache.xindice.xml.NodeSource;
import org.apache.xindice.xml.SymbolTable;
import org.apache.xindice.xml.TextWriter;
import org.apache.xindice.xml.XMLSerializable;
import org.apache.xindice.xml.dom.DBDocument;
import org.apache.xindice.xml.dom.DOMCompressor;
import org.apache.xindice.xml.dom.DOMParser;
import org.apache.xindice.xml.dom.DocumentImpl;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.ArrayList;

/**
* Collection represents a collection of Documents maintains links to
* the Filer storage implementation, and the Indexes associated with
* the Collection.
*
* @version CVS $Revision: 1.52 $, $Date: 2004/03/20 13:43:23 $
*/
public class Collection extends CollectionManager implements Named, DBObject, Configurable {

    private static final Log log = LogFactory.getLog(Collection.class);

    private static final String CACHE = "cache";
    private static final String CLASS = "class";
    private static final String CLASSNAME = "xindice-class";
    private static final String COMPRESSED = "compressed";
    private static final String FILER = "filer";
    private static final String INDEXES = "indexes";
    private static final String INLINE_METADATA = "inline-metadata";
    private static final String NAME = "name";
    private static final String SYMBOLS = "symbols";

    private static final DocumentSet EMPTY_DOCUMENTSET = new EmptyDocumentSet();
    private static final NodeSet EMPTY_NODESET = new EmptyNodeSet();
    private static final String[] EMPTY_STRING_ARRAY = {};

    private static int host_id;
    static {
        try {
            InetAddress a = InetAddress.getLocalHost();
            byte[] b = a.getAddress();
            host_id = 0;
            host_id += b[0];
            host_id += (b[1] << 8);
            host_id += (b[2] << 16);
            host_id += (b[3] << 24);
            host_id = Math.abs(host_id);
        } catch (Exception e) {
            if (log.isWarnEnabled()) {
                log.warn("ignored exception", e);
            }
        }
    }

    /**
     * ColContainer
     */
    private class ColContainer implements Container {
        private Document document;
        private Key key;

        public ColContainer(Key key, Document document) {
            this.key = key;
            this.document = document;
        }

        public void commit() throws DBException {
            putDocument(this.key, this.document /*, false */);
        }

        public void commit(Document doc) throws DBException {
            this.document = doc;
            commit();
        }

        public String getCanonicalName() throws DBException {
            return Collection.this.getCanonicalDocumentName(key);
        }

        public Collection getCollection() {
            return Collection.this;
        }

        public Document getDocument() {
            return this.document;
        }

        public Key getKey() {
            return this.key;
        }

        public void remove() throws DBException {
            Collection.this.remove(key);
        }

        public Document rollback() throws DBException {
            this.document = Collection.this.getDocument(key);
            return this.document;
        }
    }

    /**
     * ColDocumentSet
     */
    private class ColDocumentSet implements DocumentSet {
        private RecordSet set;

        public ColDocumentSet(RecordSet set) {
            this.set = set;
        }

        public Container getNextContainer() throws DBException {
            if (set.hasMoreRecords()) {
                Record rec = set.getNextRecord();
                Key key = rec.getKey();
                Value val = rec.getValue();
                if (val.getLength() > 0) {
                    try {
                        if (compressed) {
                            Document doc = new DocumentImpl(val.getData(), symbols, new NodeSource(Collection.this, key));
                            return new ColContainer(key, doc);
                        } else {
                            return new ColContainer(key, DOMParser.toDocument(val));
                        }
                    } catch (Exception e) {
                        if (log.isWarnEnabled()) {
                            log.warn("ignored exception", e);
                        }
                    }
                }
            }
            return null;
        }

        public Document getNextDocument() throws DBException {
            Container c = getNextContainer();
            if (c != null) {
                return c.getDocument();
            } else {
                return null;
            }
        }

        public boolean hasMoreDocuments() throws DBException {
            return set.hasMoreRecords();
        }
    }


    private String canonicalName;
    // Object ID Stuff
    private int collectionId;
    private File collectionRoot;
    private boolean compressed;
    private long documentId = System.currentTimeMillis();
    private DocumentCache documentCache;
    private Filer filer;
    private IndexManager indexManager;
    private InlineMetaService inlineMetaService;
    private boolean internalSymbols;
    private String name;
    private final Object oidMutex = new Object();
    private String oidTemplate;
    private Collection parent;
    private SymbolTable symbols;


    protected Collection() {
    }

    /**
     * @param parentCollection
     */
    public Collection(Collection parentCollection) {
        this.parent = parentCollection;
    }

    private void checkFiler(int faultCode) throws DBException {
        if (filer == null) {
            throw new DBException(faultCode,
                                  "Collection '" + name + "' cannot store resources (no filer)");
        }
    }

    /**
     * @see org.apache.xindice.core.DBObject#close()
     */
    public boolean close() throws DBException {
        if (filer != null) {
            indexManager.close();
            filer.close();
        }
        super.close();
        return true;
    }

    /**
     * @see org.apache.xindice.core.DBObject#create()
     */
    public boolean create() throws DBException {
        // update the meta information if necessary
        updateCollectionMeta();

        DBObserver.getInstance().createCollection(this);
        return true;
    }

    /**
     * @see org.apache.xindice.core.CollectionManager#createCollection(java.lang.String, org.apache.xindice.util.Configuration)
     */
    public final Collection createCollection(String path, Configuration config) throws DBException {
        Collection col = super.createCollection(path, config);
        getDatabase().flushConfig();
        return col;
    }

    /**
     * createIndexer creates a new Indexer object and any associated
     * system resources that the Indexer will need.
     *
     * @param config The Indexer's configuration
     * @return The newly created Indexer
     */
    public final Indexer createIndexer(Configuration config) throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
        Indexer idx = indexManager.create(config);
        getDatabase().flushConfig();
        return idx;
    }

    /**
     * createNewKey allocates a new key to be used as a key in the
     * collection. Passed in <code>key</code> parameter string value
     * used for the key. If passed key parameter is null, new OID is generated.
     *
     * @param key The Key hint, can be null
     * @return The newly generated Key
     */
    protected final Key createNewKey(Object key) {
        if (key == null) {
            return createNewOID();
        } else if (key instanceof Key) {
            return (Key) key;
        } else {
            return new Key(key.toString());
        }
    }

    /**
     * createNewOID allocates a new Object ID to be used as a Key in the
     * Collection.
     *
     * @return The newly generated key
     */
    public final Key createNewOID() {
        long ct = System.currentTimeMillis();
        synchronized (oidMutex) {
            if (ct <= documentId) {
                ct = documentId + 1;
            }
            documentId = ct;
        }

        StringBuffer sb = new StringBuffer(oidTemplate);
        String document = Long.toString(documentId, 16);
        sb.insert(32 - document.length(), document);
        sb.setLength(32);
        return new Key(sb.toString());
    }

    private String debugHeader() {
        return "["
                + Thread.currentThread().getName()
                + "] '"
                + (parent != null ? parent.getCanonicalName() : "")
                + "/"
                + name
                + "' ";
    }

    /**
     * @see org.apache.xindice.core.DBObject#drop()
     */
    public boolean drop() throws DBException {
        if (this == getDatabase()) {
            throw new DBException(FaultCodes.DBE_CANNOT_DROP,
                                  "You cannot drop the database");
        }

        DBObserver.getInstance().dropCollection(this);

        // Drop the meta if necessary
        if (isMetaEnabled()) {
            getMetaSystemCollection().dropCollectionMeta(this);
        }

        // Drop Child Collections
        String[] cols = listCollections();
        for (int i = 0; i < cols.length; i++) {
            dropCollection(getCollection(cols[i]));
        }

        if (filer != null) {
            // Drop Indexers and Filer
            indexManager.drop();
            filer.drop();
        }

        getCollectionRoot().delete();

        // Drop symbols
        if (!internalSymbols) {
            getSystemCollection().dropSymbols(this);
        }

        getDatabase().flushConfig();
        return true;
    }

    /**
     * @see org.apache.xindice.core.CollectionManager#dropCollection(org.apache.xindice.core.Collection)
     */
    public final boolean dropCollection(Collection collection) throws DBException {
        boolean success = super.dropCollection(collection);
        getDatabase().flushConfig();
        return success;
    }

    /**
     * dropIndexer physically removes the specified Indexer and any
     * associated system resources that the Indexer uses.
     *
     * @param index The Indexer to drop
     * @return Whether or not the Indexer was dropped
     */
    public final boolean dropIndexer(Indexer index) throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);

        if (index == null) {
            throw new DBException(FaultCodes.IDX_INDEX_NOT_FOUND,
                                  "Index value is null");
        }

        boolean success = indexManager.drop(index.getName());
        getDatabase().flushConfig();
        return success;
    }

    /**
     * @see org.apache.xindice.core.DBObject#exists()
     */
    public boolean exists() throws DBException {
        return true;
    }

    /**
     * @throws DBException
     */
    public final void flushSymbolTable() throws DBException {
        if (symbols.isDirty() && !internalSymbols) {
            getSystemCollection().saveSymbols(this, symbols);
        }
    }

    /**
     * Retrieve a binary database entry by key.
     * This low-level method will not update non-inline metadata.
     *
     * @param key identifying the desired database entry
     * @return byte[] containing the binary database entry
     * @throws DBException if inline-metadata is not enabled
     *         (binary resource cannot be stored in a collection
     *             which does not have inline-metadata enabled),
     *         in case of backing store error, and in case of
     *         header corruption
     */
    public final byte[] getBinary(Object key) throws DBException {
        if (log.isTraceEnabled()) {
            log.trace(debugHeader() + "Get binary: " + key);
        }

        if (inlineMetaService == null) {
            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                  "Collection '" + getCanonicalName() +
                                  "' has no binary resources (inline metadata is not enabled)");
        }

        Object entry = getEntry(key);
        if (entry == null) {
            return null;
        }

        if (!(entry instanceof byte[])) {
            throw new DBException(FaultCodes.COL_INVALID_RESULT,
                                  "Resource '" + key + "' in collection '" +
                                  getCanonicalName() + "' is not a binary resource");
        }

        return (byte[]) entry;
    }

    /**
     * getCanonicalDocumentName returns the canonical name for the specified
     * Key in relation to this Collection.
     * <br>
     * ex: /local/test/ocs/ytd
     *
     * @param key The Key
     * @return The canonical name
     */
    public final String getCanonicalDocumentName(Key key) {
        return getCanonicalDocumentName(key.toString());
    }

    /**
     * From the document key and this collection canonical name,
     * composes canonical document name.
     *
     * @param key document key
     * @return The canonical document name
     */
    public final String getCanonicalDocumentName(String key) {
        StringBuffer sb = new StringBuffer();
        sb.append(canonicalName);
        sb.append('/');
        sb.append(key);
        return sb.toString();
    }

    /**
     * getCanonicalName returns the canonical name for this Object.
     * <br>
     * ex: /local/test/ocs
     *
     * @return The canonical name
     */
    public final String getCanonicalName() {
        return canonicalName;
    }

    /**
     * Return the MetaData for this collection.
     *
     * If metadata is not enabled in the configuration, the MetaData object
     * returned will be null.
     *
     * @return MetaData this collection's metadata.
     */
    public MetaData getCollectionMeta() throws DBException {
        if (!isMetaEnabled()) {
            if (log.isWarnEnabled()) {
                log.warn("Meta information requested but not enabled in config!");
            }
            return null;
        }

        MetaSystemCollection metacol = getMetaSystemCollection();
        MetaData meta = metacol.getCollectionMeta(this);
        if (null == meta) {
            long now = System.currentTimeMillis();
            meta = new MetaData(MetaData.COLLECTION, getCanonicalName(), now, now);
            metacol.setCollectionMeta(this, meta);
        }

        return meta;
    }

    /**
     * @return The collection root
     */
    public final File getCollectionRoot() {
        return collectionRoot;
    }

    /**
     * getContainer retrieves a Container from the Collection.  The Container
     * encapsulates all information needed in dealing with a Document outside
     * of the context of a Collection (ex: DocumentContext).
     *
     * @param docKey The Document Key
     * @return The Container
     */
    public final Container getContainer(Object docKey) throws DBException {
        Key key = createNewKey(docKey);
        Document doc = getDocument(key);
        return doc != null ? new ColContainer(key, doc) : null;
    }

    /**
     * getDatabase returns the Database owner for this Collection.
     *
     * @return The Database
     */
    public Database getDatabase() {
        return parent.getDatabase();
    }

    /**
     * getDocument retrieves a Document by Key.
     *
     * @param key The Document Key
     * @return The Document
     */
    public final Document getDocument(Object key) throws DBException {
        if (log.isDebugEnabled()) {
            log.debug(debugHeader() + "Get document: " + key);
        }

        Object entry = getEntry(key);
        if (entry == null) {
            return null;
        }

        if (!(entry instanceof Document)) {
            throw new DBException(FaultCodes.COL_INVALID_RESULT,
                                  "Resource '" + key + "' in collection '" +
                                  getCanonicalName() + "' is not a document");
        }

        return (Document) entry;
    }

    /**
     * getDocumentCount returns the count of Documents being maintained
     * by this Collection.
     *
     * @return The Document count
     */
    public final long getDocumentCount() throws DBException {
        // a collection in which you are unable to file documents will have no filer
        // (for example the root collection). Rather than throwing an exception return
        // a constant result (nothing)
        return null == filer ? 0 : filer.getRecordCount();
    }

    /**
     * Return the MetaData object for a document within this collection.
     *
     * If metadata is not enabled, the MetaData object returned will be null.
     * @param id the document whose metadata you want
     */
    public MetaData getDocumentMeta(String id) throws DBException {
        if (!isMetaEnabled()) {
            if (log.isWarnEnabled()) {
                log.warn("Meta information requested but not enabled in config!");
            }
            return null;
        }

        if (null == getEntry(id)) {
            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                  "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
        }

        MetaSystemCollection metacol = getMetaSystemCollection();
        MetaData meta = metacol.getDocumentMeta(this, id);

        /*
        TimeRecord rec = null;
        if( null == meta || !meta.hasContext() )
           rec = getDatabase().getTime(path);

        long created = (null != rec) ? rec.getCreatedTime() : System.currentTimeMillis();
        long modified = (null != rec) ? rec.getModifiedTime() : System.currentTimeMillis();
        */

        // this is wrong.. but it should work for now...
        long now = System.currentTimeMillis();
        if (null == meta) {
            meta = new MetaData(MetaData.DOCUMENT, getCanonicalDocumentName(id), now, now);
            metacol.setDocumentMeta(this, id, meta);
        } else if (!meta.hasContext()) {
            meta.setContext(now, now);
        }

        return meta;
    }

    /**
     * getDocumentSet returns the set of Documents being maintained
     * by this Collection.
     *
     * @return The DocumentSet
     */
    public final DocumentSet getDocumentSet() throws DBException {
        // a collection in which you are unable to file documents will have no filer
        // (for example the root collection). Rather than throwing an exception return
        // a constant result (nothing)
        return null == filer ? EMPTY_DOCUMENTSET : new ColDocumentSet(filer.getRecordSet());
    }

    /**
     * Retrieve a database entry by key.
     *
     * If no matching entry is found, null is returned; if
     * an XML entry is found, a Document is returned; if a
     * binary entryis found, byte[] is returned.
     *
     * This low-level method will not update non-inline metadata.
     *
     * @param docKey identifying the desired database entry
     * @return Object containing the database entry, or null if no
     *         matching entry is found
     * @throws DBException in case of backing store error,
     *         and in case of header corruption
     */
    public final Object getEntry(Object docKey) throws DBException {

        // I would prefer to throw an exception (NPE) in this case,
        // but we have a test indicating a null return...
        if (docKey == null) {
            return null;
        }

        String localDebugHeader = null;
        if (log.isTraceEnabled()) {
            localDebugHeader = debugHeader() + "getEntry: docKey=<" + docKey + ">: ";
            log.trace(localDebugHeader);
        }

        checkFiler(FaultCodes.COL_NO_FILER);

        Key key = createNewKey(docKey);

        /*
         * If the key has a corresponding value in the cache, return it
         * and save a disk access.
         *
         * At some point the current document-centric cache implementation
         * needs to be converted to an entry cache which can hold both
         * Document and byte[].
         */
        if (documentCache != null) {
            Document document = documentCache.getDocument(this, key);
            if (document != null) {
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "Returning cached: " + document);
                }
                return document;
            }
        }

        Record record = filer.readRecord(key);
        if (record == null) {
            return null;
        }

        Value value;
        InlineMetaMap metaMap = null;
        if (inlineMetaService == null) {
            value = record.getValue();

            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "Type is not available, Length=" + value.getLength());
            }
        } else {
            InlineMetaService.DatabaseEntry databaseEntry = inlineMetaService.readDatabaseEntry(record.getValue());
            metaMap = databaseEntry.map;
            value = databaseEntry.value;

            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "Type=" + metaMap.get("type") + ", Length=" + value.getLength());
            }
        }

        if (inlineMetaService == null || metaMap.get("type").equals(ResourceTypeReader.XML)) {
            Document document;
            if (compressed) {
                document = new DocumentImpl(value.getData(), symbols, new NodeSource(this, key));
                flushSymbolTable();
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader +
                              "Compressed XML document=<" + TextWriter.toString(document) + ">");
                }

                if (documentCache != null) {
                    documentCache.putDocument(this, key, value.getData());
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "Pre parseDocument(): value=<" + value + ">");
                }

                document = parseDocument(key, value.toString());
            }

            DBObserver.getInstance().loadDocument(this, record, document);
            return document;
        } else {
            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "Binary document");
            }

            return value.getData();
        }
    }

    /**
     * getFiler returns the low-level Filer instances underlying the
     * Collection instance.
     *
     * @return The requested Filer
     */
    public final Filer getFiler() {
        return filer;
    }

    /**
     * getIndexer retrieves an Indexer by name.
     *
     * @param name The Indexer name
     * @return The Indexer (or null)
     */
    public final Indexer getIndexer(String name) throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
        return indexManager.get(name);
    }

    /**
     * return the IndexManager being used by this Collection.
     *
     * @return The IndexManager
     */
    public final IndexManager getIndexManager() throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
        return indexManager;
    }

    /**
     * Return the MetaSystemCollection for the database containing this
     * collection.
     *
     * @return MetaSystemCollection
     */
    private MetaSystemCollection getMetaSystemCollection() {
        return getDatabase().getMetaSystemCollection();
    }

    public final String getName() {
        return name;
    }

    /**
     * getObject instantiates and returns an XMLSerializable object based on the
     * provided Key.  Xindice takes care of instantiating the correct class, but
     * only if a class was registered with the Document in the first place.
     *
     * @param key The Document Key
     * @return an Castable XMLSerializable Instance
     */
    public final XMLSerializable getObject(Object key) throws DBException {
        if (log.isDebugEnabled()) {
            log.debug(debugHeader() + "Get object: " + key);
        }
        String className = null;

        Document doc = getDocument(key);
        if (doc != null) {
            NodeList childNodes = doc.getChildNodes();
            int size = childNodes.getLength();
            for (int i = 0; i < size; i++) {
                Node n = childNodes.item(i);
                if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE && n.getNodeName().equals(CLASSNAME)) {
                    className = n.getNodeValue().trim();
                    break;
                }
            }

            if (className != null) {
                try {
                    XMLSerializable obj = (XMLSerializable) Class.forName(className).newInstance();
                    obj.streamFromXML(doc.getDocumentElement());
                    return obj;
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn("ignored exception", e);
                    }
                }
            }
        }

        return null;
    }

    /**
     * getParentCollection returns the parent Collection of this
     * Collection.
     *
     * @return The parent Collection (or null)
     */
    public final Collection getParentCollection() throws DBException {
        return parent;
    }

    /**
     * getQueryEngine returns the Database's Query Engine
     *
     * @return The Query Engine
     */
    public QueryEngine getQueryEngine() throws DBException {
        return getDatabase().getQueryEngine();
    }

    /**
     * getSymbols returns the SymbolTable in use by this
     * Collection.
     *
     * @return The Symbol Table
     */
    public final SymbolTable getSymbols() throws DBException {
        return symbols;
    }

    /**
     * getSystemCollection returns the System Collection.
     *
     * @return The System Collection
     */
    public SystemCollection getSystemCollection() throws DBException {
        return getDatabase().getSystemCollection();
    }

    /**
     * Insert a binary object into a Xindice Collection.  A unique key
     * is automatically generated. by which the binary object can be
     * retrieved in the future.  Note: because the key is automatically
     * unique, this insert method will never cause a collision with an
     * object already in the database.
     *
     * @param bytes The bytes making up the binary object to insert
     * @return Key automatically generated for the binary object
     * @throws DBException if inline-metadata is not enabled, or an
     *         error occurs while saving.
     */
    public Key insertBinary(byte[] bytes) throws DBException {
        return insertBinary(null, bytes);
    }

    /**
     * insertBinary inserts a new binary object into a Xindice Collection.
     *
     * @param docKey The document Key
     * @param bytes The document to insert
     * @throws DBException if inline-metadata is not enabled, the key is
     *         already in the database, or an error occurs while saving.
     */
    public Key insertBinary(Object docKey, byte[] bytes) throws DBException {
        if (inlineMetaService == null) {
            throw new DBException(FaultCodes.COL_CANNOT_STORE,
                                  "Cannot insert a binary resource in '" + getCanonicalName() +
                                  "' (inline-metadata is not enabled)");
        }

        Key key = createNewKey(docKey);
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Insert binary: " + key);
        }
        putBinary(key, bytes, true);

        // update the meta information if necessary
        updateCollectionMeta();
        return key;
    }

    /**
     * insertDocument inserts a new Document into a Xindice Collection.
     *
     * @param document The Document
     * @return The new Object Identifier
     */
    public final Key insertDocument(Document document) throws DBException {
        return insertDocument(null, document);
    }

    /**
     * insertDocument inserts a new Document into a Xindice Collection.
     *
     * @param docKey The document Key
     * @param document The document to insert
     */
    public final Key insertDocument(Object docKey, Document document) throws DBException {
        Key key = createNewKey(docKey);
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Insert document: " + key);
        }
        putDocument(key, document /*, true */);

        // update the meta information if necessary
        updateCollectionMeta();
        return key;
    }

    /**
     * insertObject inserts an XMLSerializable object into the Collection and
     * returns a newly generated Key.  Xindice takes care of associating the
     * implementation class with the XMLSerializable object.
     *
     * @param obj The Object to insert
     * @return The newly generated Key
     */
    public final Key insertObject(XMLSerializable obj) throws DBException {
        return insertObject(null, obj);
    }

    /**
     * insertObject inserts an XMLSerializable object into the Collection based
     * on the specified Key.  Xindice takes care of associating the
     * implementation class with the XMLSerializable object.
     *
     * @param objKey The Key to use
     * @param obj The Object to insert
     */
    public final Key insertObject(String objKey, XMLSerializable obj) throws DBException {
        Key key = createNewKey(objKey);
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Insert object: " + key);
        }
        putObject(key, obj /*, true */);

        // update the meta information if necessary
        updateCollectionMeta();
        return key;
    }

    /**
     * Returns whether or not meta data is enabled.
     * @return boolean whether or not meta data is enabled.
     */
    public boolean isMetaEnabled() {
        return getDatabase().isMetaEnabled();
    }

    public boolean isOpened() throws DBException {
        return true;
    }

    /**
     * listDocuments returns a list of all document keys stored by this
     * collection.
     *
     * @return the list of document keys
     */
    public final String[] listDocuments() throws DBException {
        // a collection in which you are unable to file documents will have no filer
        // (for example the root collection). Rather than throwing an exception return
        // a constant result (nothing)
        if (null == filer) {
            return EMPTY_STRING_ARRAY;
        } else {
            // TODO: ArrayList length is limited to the int, while filer record count is long

            // give a hint to the size of the record set, saves on arraylist array copies.
            ArrayList temp = new ArrayList((int) filer.getRecordCount());

            RecordSet set = filer.getRecordSet();
            while (set.hasMoreRecords()) {
                Key key = set.getNextKey();
                temp.add(key.toString());
            }

            return (String[]) temp.toArray(new String[0]);
        }
    }

    /**
     * listIndexers returns a list of the currently registered Indexers
     * as an array of String.
     *
     * @return The Indexer list
     */
    public final String[] listIndexers() throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
        return indexManager.list();
    }

    public final boolean open() throws DBException {
        return true;
    }

    /**
     * Turns an XML string into a parsed document and caches it.
     *
     * @param key The key to use when caching
     * @param xml The string to parse
     * @return A parsed DOM document or null if failure
     */
    private Document parseDocument(Key key, String xml) throws DBException {
        Document doc = null;
        try {
            doc = DOMParser.toDocument(xml);

            // Have to move it to Xindice DOM for XMLObject AutoLinking
            byte[] b = DOMCompressor.Compress(doc, symbols);
            doc = new DocumentImpl(b, symbols, new NodeSource(this, key));

            if (documentCache != null) {
                documentCache.putDocument(this, key, b);
            }
        } catch (Exception e) {
            throw new DBException(FaultCodes.COL_DOCUMENT_MALFORMED,
                                  "Unable to parse document '" + key + "' in '" + getCanonicalName() + "'", e);
        }
        return doc;
    }

    /*
     * Lowest-level method for saving a binary entry into the database. At this moment,
     * presence of inline metadata is known.
     * It now does update non-inline metadata if the user has configured it.
     */
    private void putBinary(Key key, byte[] bytes, boolean create) throws DBException {
        if (!create) {
            byte[] storedBytes = getBinary(key);
            if (storedBytes == null) {
                // TODO: Do we need a COL_KEY_ALREADY_PRESENT fault so that the caller can interpret this exception?
                throw new DBException(FaultCodes.COL_CANNOT_STORE,
                                      "Error storing binary resource '" + key + "' in '" + getCanonicalName() +
                                      "': the 'create' flag is false and the key is already in database");
            }
        }

        InlineMetaMap map = inlineMetaService.getEmptyMap();
        map.put("type", ResourceTypeReader.BINARY);
        Value value = inlineMetaService.createValue(map, bytes, 0, bytes.length);
        filer.writeRecord(key, value);

        // update the meta for this document
        updateDocumentMeta(key.toString());
    }

    /**
     * This is the lowest-level method for storing a record into the backing store.
     * It now does update non-inline metadata if the user has configured it.
     */
    private void putDocument(Key key, Document document) throws DBException {
        final String localDebugHeader = debugHeader() + "putDocument: docKey=<" + key + ">: ";
        if (log.isTraceEnabled()) {
            log.trace(localDebugHeader + "document=<" + TextWriter.toString(document) + ">");
        }

        checkFiler(FaultCodes.COL_NO_FILER);

        if (document instanceof DBDocument) {
            // FIXME: This is a shitty shitty hack... Kill immediately
            DBDocument dbDoc = (DBDocument) document;
            if (dbDoc.getSource() == null) {
                dbDoc.setSource(new NodeSource(this, key));
            }
        }

        /*
         * The possibilities are restricted because only XML
         * is handled by this method.  There are only a few
         * pieces of information that need to be constructed:
         * 1) the xindice DOM document is needed for all XML objects, as
         *         it is handed to the IndexManager and the DBObserver.
         * 2) the packed document, if this is a compressed XML object,
         *        is needed for the cache and the BTree (via the Value object).
         * 3) the string-converted-to-utf-8 bytes, if this is a non-compressed
         *        XML object, is needed for the BTree (via the Value object).
         * 4) A Value object, with a header if headers are enabled, and
         *        otherwise without headers, for the BTree.
         */

        byte[] documentBytes = null;

        if (compressed) {
            try {
                documentBytes = DOMCompressor.Compress(document, symbols);
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "length=" + documentBytes.length);
                }

                // Why must it be re-created?
                document = new DocumentImpl(documentBytes, symbols, new NodeSource(this, key));
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "packedDocument: length=" + documentBytes.length +
                              " document=<" + TextWriter.toString(document) + ">");
                }
            } catch (Exception e) {
                throw new DBException(FaultCodes.COL_CANNOT_STORE,
                                      localDebugHeader + "Error compressing Document '" + key + "'", e);
            }
        } else {
            try {
                documentBytes = TextWriter.toString(document).getBytes("utf-8");
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "utf8Document: length=" + documentBytes.length +
                              " document=<" + new String(documentBytes, "utf-8") + ">");
                }
            } catch (UnsupportedEncodingException e) {
                // Should never happen
                throw new DBException(FaultCodes.GEN_FATAL_ERROR,
                                      "utf-8 encoding not supported", e);
            }
        }

        // TODO: Move into if(compressed) ?
        flushSymbolTable();

        // Temporary until insert and update are separate
        Document oldDoc = getDocument(key);
        if (oldDoc != null) {
            indexManager.removeDocument(key, oldDoc);
        }
        indexManager.addDocument(key, document);

        /*
         * Construct the Value object that is stored in the BTree.
         */
        Value value;
        if (inlineMetaService == null) {
            value = new Value(documentBytes);
        } else {
            InlineMetaMap map = inlineMetaService.getEmptyMap();
            map.put("type", ResourceTypeReader.XML);
            value = inlineMetaService.createValue(map, documentBytes, 0, documentBytes.length);
        }
        filer.writeRecord(key, value);

        // Cache Stuff
        if (documentCache != null) {
            if (compressed) {
                documentCache.putDocument(this, key, documentBytes);
            } else {
                documentCache.putDocument(this, key, document);
            }
        }

        // update the meta for this document
        updateDocumentMeta(key.toString());
        DBObserver.getInstance().putDocument(this, key, document, oldDoc == null);
    }

    private void putObject(Key key, XMLSerializable obj) throws DBException {
        if (log.isTraceEnabled()) {
            log.trace(debugHeader() + "putObject: key=<" + key + "> class=<" + obj.getClass().getName() + ">");
        }

        Document doc = new DocumentImpl();
        ProcessingInstruction pi = doc.createProcessingInstruction(CLASSNAME, obj.getClass().getName());
        doc.appendChild(pi);
        Element elem = obj.streamToXML(doc);
        doc.appendChild(elem);
        putDocument(key, doc /*, create */);
    }

    /**
     * queryCollection performs a query against the current collection
     * using the specified style and query String.
     *
     * @param style The query style to use (ex: XPath)
     * @param query The query to execute
     * @param nsMap The namespace Map (if any)
     * @return The resulting NodeSet
     */
    public final NodeSet queryCollection(String style, String query, NamespaceMap nsMap) throws DBException {
        if (log.isDebugEnabled()) {
            log.debug(debugHeader() + "Query collection, query " + query);
        }

        // A collection in which you are unable to file documents will have no filer
        // (for example the root collection). Rather than throwing an exception return
        // a constant result (nothing)
        return null == filer ? EMPTY_NODESET : getQueryEngine().query(this, style, query, nsMap, null);
    }

    /**
     * queryDocument performs a query against a single Document using
     * the specified style, query string, and Document ID.
     *
     * @param style The query style to use (ex: XPath)
     * @param query The query to execute
     * @param nsMap The namespace Map (if any)
     * @param key The Document to query
     * @return The resulting NodeSet
     */
    public final NodeSet queryDocument(String style, String query, NamespaceMap nsMap, Object key) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Query document " + key + ", query: " + query);
        }

        checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
        Key[] k = null;
        if (key instanceof Key[]) {
            k = (Key[]) key;
        } else {
            k = new Key[]{createNewKey(key)};
        }
        return getQueryEngine().query(this, style, query, nsMap, k);
    }

    /**
     * remove removes an object from the Collection based on its Key,
     * regardless of it's type.
     *
     * @param key The Object's Key
     */
    public final void remove(Object key) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Remove " + key);
        }

        checkFiler(FaultCodes.COL_NO_FILER);

        Key objKey = createNewKey(key);

        Object oldDoc = getEntry(objKey);
        if (oldDoc != null && oldDoc instanceof Document) {
            indexManager.removeDocument(objKey, (Document)oldDoc);
        }

        if (documentCache != null) {
            documentCache.removeDocument(this, objKey);
        }

        if (!filer.deleteRecord(objKey)) {
            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                  "Resource '" + objKey + "' does not exist in '" + getCanonicalName() + "'");
        }

        // update the meta for this collection if necessary
        updateCollectionMeta();
        // remove the document meta
        if (isMetaEnabled()) {
            getMetaSystemCollection().dropDocumentMeta(this, objKey.toString());
        }
        DBObserver.getInstance().dropDocument(this, objKey);
    }

    protected final void setCanonicalName(String canonicalName) {
        this.canonicalName = canonicalName;

        // Calculate The OID Template
        collectionId = Math.abs(canonicalName.hashCode());
        StringBuffer sb = new StringBuffer("00000000000000000000000000000000");
        String host = Integer.toString(host_id, 16);
        String collection = Integer.toString(collectionId, 16);
        sb.insert(8 - host.length(), host);
        sb.insert(16 - collection.length(), collection);
        sb.setLength(32);
        oidTemplate = sb.toString();
    }

    /**
     * Reset the metadata object for this collection.
     * @param meta the Metadata to use
     */
    public void setCollectionMeta(MetaData meta) throws DBException {
        if (!isMetaEnabled()) {
            if (log.isWarnEnabled()) {
                log.warn("Meta information requested but not enabled in config!");
            }
            return;
        }

        if (null != meta) {
            if (meta.getType() != MetaData.COLLECTION) {
                throw new DBException(FaultCodes.GEN_UNKNOWN,
                                      "Mismatch type of meta data for collection " + getCanonicalName());
            }

            MetaSystemCollection metacol = getMetaSystemCollection();
            MetaData current = metacol.getCollectionMeta(this);
            current.copyDataFrom(meta);
            metacol.setCollectionMeta(this, current);
        }
    }

    protected final void setCollectionRoot(File collectionRoot) {
        this.collectionRoot = collectionRoot;
        if (!collectionRoot.exists()) {
            if (log.isTraceEnabled()) {
                log.trace("Creating directories: " + collectionRoot);
            }
            collectionRoot.mkdirs();
        }
    }

    public void setConfig(Configuration config) throws XindiceException {
        name = config.getAttribute(NAME);
        compressed = config.getBooleanAttribute(COMPRESSED, true);

        /*
         * If inline metadata is desired, get an InlineMetaService object.
         */
        if (config.getBooleanAttribute(INLINE_METADATA, false)) {
            inlineMetaService = new InlineMetaService();
        }

        /*
         * Wait to set up the local debug header until everything needed
         * by debugHeader() is complete!
         */
        final String localDebugHeader = debugHeader() + "setConfig: ";

        // Set parent
        if (parent != null) {
            setCanonicalName(parent.getCanonicalName() + '/' + name);
            setCollectionRoot(new File(parent.getCollectionRoot(), name));
            if (log.isDebugEnabled()) {
                log.debug(localDebugHeader + "Root=<" + getCollectionRoot() + ">");
            }
        }

        if (log.isDebugEnabled()) {
            log.debug(localDebugHeader
                      + (compressed ? "Compressed" : "NOT Compressed")
                      + ", "
                      + (inlineMetaService == null ? "Inline metadata DISABLED" : "Inline metadata ENABLED")
            );
        }

        if (config.getBooleanAttribute(CACHE, true)) {
            documentCache = getDatabase().getDocumentCache();
        }

        // If no Filer is defined, skip Symbols and Indexes
        Configuration filerConfig = config.getChild(FILER);
        if (filerConfig != null) {
            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "Have filer config...");
            }

            // Symbol Table Setup
            Configuration symConfig = config.getChild(SYMBOLS);
            internalSymbols = (symConfig != null);
            if (internalSymbols) {
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader
                              + "Internal symbols=<" + TextWriter.toString(symConfig.getElement()) + ">");
                }

                try {
                    symbols = new SymbolTable(symConfig.getElement());
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn(localDebugHeader + "Error building symbol table from internal symbols", e);
                    }
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "No internal symbols...");
                }

                try {
                    symbols = getSystemCollection().loadSymbols(this);
                    if (log.isDebugEnabled()) {
                        log.debug(localDebugHeader + "Loaded symbols=<" +
                                  TextWriter.toString(symbols.streamToXML(new DocumentImpl())) + ">");
                    }
                } catch (Exception e) {
                    if (log.isWarnEnabled()) {
                        log.warn(localDebugHeader + "Error building symbol table from system collection", e);
                    }
                }
            }

            String className = filerConfig.getAttribute(CLASS);
            if (log.isDebugEnabled()) {
                log.debug(localDebugHeader + "Filer class=<" + className + ">");
            }
            try {
                filer = (Filer) Class.forName(className).newInstance();
                //            filer.setCollection(this);
                filer.setLocation(getCollectionRoot(), getName());
                filer.setConfig(filerConfig);
                if (!filer.exists()) {
                    filer.create();
                }
                filer.open();
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    log.warn("Filer '" + className + "' is not available", e);
                }
            }

            // Index Manager
            try {
                indexManager = new IndexManager(this);
                Configuration idxConfig = config.getChild(INDEXES, true);
                indexManager.setConfig(idxConfig);
            } catch (Exception e) {
                if (log.isWarnEnabled()) {
                    log.warn("Failed to initialize indexer", e);
                }
            }
        }

        // Last thing to do is to init child collections
        super.setConfig(config);

        // observer
        DBObserver.getInstance().setCollectionConfig(this, config);
    }

    /**
     * setDocument overwrites/updates an existing Document in a
     * Xindice Collection.
     *
     * @param docKey The Document Key
     * @param document The Document
     */
    public final void setDocument(Object docKey, Document document) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Set document " + docKey);
        }
        putDocument(createNewKey(docKey), document /*, false */);
    }

    /**
     * Set the metadata associated with a document within this collection.
     *
     * @param id the document name
     * @param meta the metadata object to be used.
     */
    public void setDocumentMeta(String id, MetaData meta) throws DBException {
        if (!isMetaEnabled()) {
            if (log.isWarnEnabled()) {
                log.warn("Meta information requested but not enabled in config!");
            }
            return;
        }

        Object obj = getEntry(id);
        if (null == obj) {
            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                  "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
        }

        if (null != meta) {
            if (meta.getType() == MetaData.UNKNOWN || meta.getType() == MetaData.COLLECTION) {
                throw new DBException(FaultCodes.GEN_UNKNOWN,
                                      "Mismatch type of meta data for document " + getCanonicalDocumentName(id));
            }

            if (log.isInfoEnabled()) {
                log.info(debugHeader() + "Set document meta " + id);
            }
            MetaSystemCollection metacol = getMetaSystemCollection();
            MetaData current = metacol.getDocumentMeta(this, id);
            current.copyDataFrom(meta);
            metacol.setDocumentMeta(this, id, current);
        }
    }

    /**
     * @param string
     */
    protected void setName(String string) {
        name = string;
    }

    /**
     * setObject sets an XMLSerializable object in the Collection based on the
     * provided Key.  Xindice takes care of associating the implementation class
     * with the XMLSerializable object.
     *
     * @param key The Key to use
     * @param obj The Object to set
     */
    public final void setObject(Object key, XMLSerializable obj) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Set object " + key);
        }
        putObject(createNewKey(key), obj /*, false */ );
    }

    /**
     * update the modified time of this collection when appropriate
     */
    protected void updateCollectionMeta() {
        // update the meta data if necessary
        if (isMetaEnabled()) {
            MetaSystemCollection metacol = getMetaSystemCollection();
            MetaData meta = null;
            try {
                meta = metacol.getCollectionMeta(this);
            } catch (DBException e) {
                // something strange has happened.. can't get the
                // meta data for this collection
                if (log.isWarnEnabled()) {
                    log.warn("Error fetching meta for collection '" + getCanonicalName() + "'", e);
                }
                return;
            }

            if (log.isTraceEnabled()) {
                log.trace(debugHeader() + "Updating modified time for collection");
            }
            long now = System.currentTimeMillis();
            if (null == meta) {
                meta = new MetaData(MetaData.COLLECTION, getCanonicalName(), now, now);
            } else if (!meta.hasContext()) {
                // Newly created meta. Update its created and modified time.
                meta.setContext(now, now);
            } else {
                // This collection already has a meta. So update its modified time.
                meta.setContext(0, now);
            }

            try {
                metacol.setCollectionMeta(this, meta);
            } catch (DBException e) {
                if (log.isWarnEnabled()) {
                    log.warn("Error setting meta for collection '" + getCanonicalName() + "'", e);
                }
                return;
            }
        }
    }

    /**
     * update the modified time of this document when appropriate
     */
    protected void updateDocumentMeta(String id) throws DBException {
        // update the meta data if necessary
        if (!isMetaEnabled()) {
            return;
        }

        Object obj = getEntry(id);
        if (null == obj) {
            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                  "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
        }

        MetaSystemCollection metacol = getMetaSystemCollection();
        MetaData meta = metacol.getDocumentMeta(this, id);
        String path = getCanonicalDocumentName(id);

        /*
        TimeRecord rec = null;
        if( null == meta || !meta.hasContext() )
           rec = getDatabase().getTime(path);

        long created = (null != rec) ? rec.getCreatedTime() : System.currentTimeMillis();
        long modified = (null != rec) ? rec.getModifiedTime() : System.currentTimeMillis();
        */

        // this is wrong.. but it should work for now...
        if (log.isTraceEnabled()) {
            log.trace(debugHeader() + "Updating modified time for document '" + id + "'");
        }
        long now = System.currentTimeMillis();
        if (null == meta) {
            meta = new MetaData(MetaData.DOCUMENT, path, now, now);
        } else if (!meta.hasContext()) {
            meta.setContext(now, now);
        } else {
            meta.setContext(0, now);
        }
        metacol.setDocumentMeta(this, id, meta);
    }
}
TOP

Related Classes of org.apache.xindice.core.Collection$ColDocumentSet

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.