Package org.apache.xindice.core

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

/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*
* $Id: Collection.java 594628 2007-11-13 20:16:05Z vgritsenko $
*/

package org.apache.xindice.core;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xindice.core.cache.DocumentCache;
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.Entry;
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.lang.ref.WeakReference;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Map;
import java.util.WeakHashMap;

/**
* Collection represents a collection of Documents maintains links to
* the Filer storage implementation, and the Indexes associated with
* the Collection.
*
* @version $Revision: 594628 $, $Date: 2007-11-13 15:16:05 -0500 (Tue, 13 Nov 2007) $
*/
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 byte ACTION_INSERT = 1;
    private static final byte ACTION_UPDATE = 2;
    private static final byte ACTION_STORE = 3;

    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, ACTION_STORE);
        }

        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 name;
    private String canonicalName;
    private Collection parent;

    // Object ID Stuff
    private final Object oidMutex = new Object();
    private String oidTemplate;
    private long documentId;

    private SymbolTable symbols;

    private File collectionRoot;
    private boolean compressed;
    private Filer filer;
    private InlineMetaService inlineMetaService;
    private DocumentCache cache;
    private IndexManager indexManager;

    // document keys identity map
    private final Map identityMap = new WeakHashMap();


    protected Collection() {
        documentId = System.currentTimeMillis();
    }

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


    // -- Internal Implementation Methods -----------------------------------

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

    private Key getIdentityKey(Key key) {
        synchronized (identityMap) {
            Key id = null;
            WeakReference ref = (WeakReference) identityMap.get(key);
            if (ref != null) {
                id = (Key) ref.get();
            }
            if (id == null) {
                id = key;
                identityMap.put(id, new WeakReference(id));
            }

            return id;
        }
    }

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

    /**
     * @throws DBException if operation failed
     */
    private void flushSymbolTable() throws DBException {
        if (symbols.isDirty()) {
            getSystemCollection().saveSymbols(this, symbols);
        }
    }

    /**
     * @param name collection name
     */
    protected void setName(String name) {
        this.name = name;
    }

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

        // Calculate The OID Template
        StringBuffer sb = new StringBuffer("00000000000000000000000000000000");
        String host = Integer.toString(host_id, 16);
        sb.insert(8 - host.length(), host);

        String collection = Integer.toString(Math.abs(canonicalName.hashCode()), 16);
        sb.insert(16 - collection.length(), collection);

        sb.setLength(32);
        oidTemplate = sb.toString();
    }

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

    /**
     * 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());
        }
    }

    /**
     * Turns an XML string into a parsed document retrieved
     * from the uncompressed collection.
     *
     * @param key The key to use when caching
     * @param xml The string to parse
     * @return A parsed DOM document or null if failure
     * @throws DBException if operation failed
     */
    private Document parseDocument(Key key, String xml) throws DBException {
        try {
            Document doc = DOMParser.toDocument(xml);
            ((DBDocument) doc).setSource(new NodeSource(this, key));

            // Have to compress to update collection's SymbolTable,
            // which is used even for uncompressed collections
            DOMCompressor.compress(doc, symbols);

            return doc;
        } catch (Exception e) {
            throw new DBException(FaultCodes.COL_DOCUMENT_MALFORMED,
                                  "Unable to parse document '" + key + "' in '" + getCanonicalName() + "'", e);
        }
    }


    // -- Database Object Methods -------------------------------------------

    public boolean isOpened() {
        // Collection without filer is always open ... for now.
        //noinspection SimplifiableIfStatement
        if (filer == null) {
            return true;
        }

        return filer.isOpened();
    }

    /**
     * @see org.apache.xindice.core.DBObject#exists()
     */
    public boolean exists() throws DBException {
        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;
    }

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

    /**
     * @see org.apache.xindice.core.DBObject#drop()
     */
    public boolean drop() throws DBException {
        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 (!symbols.isReadOnly()) {
            getSystemCollection().dropSymbols(this);
        }

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

    /**
     * @see org.apache.xindice.core.DBObject#close()
     */
    public boolean close() throws DBException {
        // Close children collections first
        super.close();

        // Close its own filer
        if (filer != null) {
            indexManager.close();
            filer.close();
        }

        return true;
    }


    // -- CollectionManager methods -----------------------------------------

    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)) {
            cache = 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);
            if (symConfig != null) {
                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader +
                              "Internal symbols=<" + TextWriter.toString(symConfig.getElement()) + ">");
                }

                try {
                    symbols = new SymbolTable(symConfig.getElement(), true);
                } 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 loading 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.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, getDatabase().getTimer());
                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);
    }

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

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

    // -- Core Collection API Public Methods --------------------------------

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

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


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

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

    public final String getName() {
        return name;
    }

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

    /**
     * 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) {
        return canonicalName + '/' + key;
    }

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

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

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

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


    // -- Core Collection API Public Methods: Index Management --------------

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

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

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

    /**
     * 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
     * @throws DBException if operation failed
     */
    public final Indexer createIndexer(Configuration config) throws DBException {
        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
        Indexer idx = indexManager.create(config);
        getDatabase().flushConfig();
        return idx;
    }

    /**
     * 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
     * @throws DBException if operation failed
     */
    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;
    }


    // -- Core Collection API Public Methods: Data Management ---------------

    private Record writeRecord(Key key, boolean isDocument, byte[] bytes) throws DBException {
        // Construct the Value object that is stored in the BTree.
        Value value;
        if (inlineMetaService == null) {
            value = new Value(bytes);
        } else {
            InlineMetaMap map = inlineMetaService.getEmptyMap();
            if (isDocument) {
                map.put("type", ResourceTypeReader.XML);
            } else {
                map.put("type", ResourceTypeReader.BINARY);
            }
            value = inlineMetaService.createValue(map, bytes, 0, bytes.length);
        }

        return filer.writeRecord(key, value);
    }

    /**
     * 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());
    }

    /**
     * Retrieve a database entry by key.
     *
     * If no matching entry is found, null is returned, otherwise this
     * method returns Entry that identifies resource type and holds its
     * value and metadata.
     *
     * @param id identifying the desired database entry
     * @return Entry containing the database entry and its metadata, or null
     *         if no matching entry is found
     * @throws DBException in case of backing store error,
     *         and in case of header corruption
     */
    public final Entry getEntry(Object id) throws DBException {
        // I would prefer to throw an exception (NPE) in this case,
        // but we have a test indicating a null return...
        if (id == null) {
            return null;
        }

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

        checkFiler(FaultCodes.COL_NO_FILER);

        Key key = getIdentityKey(createNewKey(id));
        synchronized (key) {
            /*
             * If the key has a corresponding value in the cache, return it
             * and save a disk access.
             */
            if (cache != null) {
                Entry entry = cache.getEntry(this, key);
                if (entry != null) {
                    if (log.isTraceEnabled()) {
                        log.trace(localDebugHeader + "Returning cached: " + entry.getValue());
                    }

                    return entry;
                }
            }

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

            Value value;
            boolean isDocument;
            if (inlineMetaService == null) {
                value = record.getValue();
                isDocument = true;

                if (log.isTraceEnabled()) {
                    log.trace(localDebugHeader + "Type is not available, Length=" + value.getLength());
                }
            } else {
                InlineMetaService.DatabaseEntry databaseEntry = inlineMetaService.readDatabaseEntry(record.getValue());
                Object type = databaseEntry.map.get("type");
                value = databaseEntry.value;
                isDocument = type.equals(ResourceTypeReader.XML);

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

            Map entryMeta = Entry.createMetaMap(record);
            if (isDocument) {
                Document document;
                if (compressed) {
                    document = new DocumentImpl(value.getData(), symbols, new NodeSource(this, key));
                    if (log.isTraceEnabled()) {
                        log.trace(localDebugHeader +
                                  "Compressed XML document=<" + TextWriter.toString(document) + ">");
                    }
                } else {
                    // FIXME There should be no reason here to re-compress the document & flush symbols table?
                    document = parseDocument(key, value.toString());
                    if (log.isTraceEnabled()) {
                        log.trace(localDebugHeader +
                                  "Uncompressed XML document=<" + TextWriter.toString(document) + ">");
                    }
                }

                // Symbol table could have been updated above, flush it to the disk.
                flushSymbolTable();

                if (cache != null) {
                    cache.putEntry(this, key,
                                   compressed ? DocumentCache.COMPRESSED : DocumentCache.UNCOMPRESSED,
                                   value, entryMeta);
                }

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

                if (cache != null) {
                    cache.putEntry(this, key, DocumentCache.BINARY, value, entryMeta);
                }

                return new Entry(key, value.getData(), entryMeta);
            }
        }
    }

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

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

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

        return (Document) entry.getValue();
    }

    /**
     * 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
     * @throws DBException if operation failed
     */
    public final XMLSerializable getObject(Object key) throws DBException {
        if (log.isDebugEnabled()) {
            log.debug(debugHeader() + "Get object: " + key);
        }

        Document doc = getDocument(key);
        if (doc != null) {
            String className = 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;
    }

    /**
     * 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)");
        }

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

        if (entry.getEntryType() != Entry.BINARY) {
            throw new DBException(FaultCodes.COL_INVALID_RESULT,
                                  "Resource '" + key + "' in collection '" +
                                  getCanonicalName() + "' is not a binary resource");
        }

        return (byte[]) entry.getValue();
    }

    /**
     * insertDocument inserts a new Document into a Xindice Collection.
     *
     * @param document The Document
     * @return The new Object Identifier
     * @throws DBException if operation failed
     */
    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
     * @return key for inserted document
     * @throws DBException if operation failed
     */
    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, ACTION_INSERT);

        // 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
     * @throws DBException if operation failed
     */
    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
     * @return key for the inserted object
     * @throws DBException if operation failed
     */
    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, ACTION_INSERT);

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

    /**
     * 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
     * @return key for the inserted binary
     * @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, ACTION_INSERT);

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

    /**
     * setDocument inserts or updates an existing Document in a
     * Xindice Collection.
     *
     * @param docKey The Document Key
     * @param document The Document
     * @return True if new document entry was created, false otherwise
     * @throws DBException if operation failed
     */
    public final boolean setDocument(Object docKey, Document document) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Set document " + docKey);
        }

        boolean res = putDocument(createNewKey(docKey), document, ACTION_STORE);
        if (res) {
            updateCollectionMeta();
        }

        return res;
    }

    /**
     * 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
     * @throws DBException if operation failed
     */
    public final void setObject(Object key, XMLSerializable obj) throws DBException {
        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Set object " + key);
        }
        putObject(createNewKey(key), obj, ACTION_STORE);
    }

    /**
     * setBinary inserts or updates binary object into a Xindice Collection.
     *
     * @param docKey The document Key
     * @param bytes The document to insert
     * @return true if new binary was created, false otherwise
     * @throws DBException if inline-metadata is not enabled, the key is
     *         already in the database, or an error occurs while saving.
     */
    public boolean setBinary(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)");
        }

        if (log.isInfoEnabled()) {
            log.info(debugHeader() + "Set binary " + docKey);
        }

        boolean res = putBinary(createNewKey(docKey), bytes, ACTION_STORE);
        if (res) {
            updateCollectionMeta();
        }

        return res;
    }

    /**
     * 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.
     *
     * <p>putDocument attempts to perform requested action, and success depends on
     * action and presense of the key in the collection.
     *
     * @param key Entry key
     * @param document Document to store
     * @param action It can be either ACTION_INSERT, ACTION_UPDATE or ACTION_STORE
     * @return True if new document entry was created, false otherwise
     * @throws DBException<ul>
     * <li>FaultCodes.COL_DUPLICATE_RESOURCE If entry with that key already present in
     * collection and action is ACTION_INSERT</li>
     * <li>FaultCodes.COL_DOCUMENT_NOT_FOUND If entry with that key is not present in
     * collection and action is ACTION_UPDATE
     * </ul>
     */
    private boolean putDocument(Key key, Document document, byte action) 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;

        if (compressed) {
            // Create compressed document bytes to be stored in the filer
            documentBytes = DOMCompressor.compress(document, symbols);
            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "length=" + documentBytes.length);
            }

            // Create xindice document with just compressed bytes.
            // Passed in document might not necessarily be xindice document,
            // but we should be passing only our documents to index manager.
            document = new DocumentImpl(documentBytes, symbols, new NodeSource(this, key));

            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "packedDocument: length=" + documentBytes.length +
                          " document=<" + TextWriter.toString(document) + ">");
            }
        } else {
            // Create uncompressed document bytes to be stored in the filer
            String documentChars = TextWriter.toString(document);
            try {
                documentBytes = documentChars.getBytes("utf-8");
            } catch (UnsupportedEncodingException e) {
                // Should never happen
                throw new DBException(FaultCodes.GEN_FATAL_ERROR,
                                      "utf-8 encoding not supported", e);
            }

            // Create xindice document from the string.
            // In addition to converting passed document to xindice document
            // instance, parseDocument() also updates the symbol table,
            // if necessary.
            document = parseDocument(key, documentChars);

            if (log.isTraceEnabled()) {
                log.trace(localDebugHeader + "utf8Document: length=" + documentBytes.length +
                          " document=<" + documentChars + ">");
            }
        }

        // Symbol table could have been updated above, flush it to the disk.
        flushSymbolTable();

        key = getIdentityKey(key);
        Entry entry;
        synchronized (key) {
            // Temporary until insert and update are separate
            entry = getEntry(key);

            if (action == ACTION_INSERT && entry != null) {
                throw new DBException(FaultCodes.COL_DUPLICATE_RESOURCE);
            } else if (action == ACTION_UPDATE && entry == null) {
                throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND);
            }

            if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
                indexManager.removeDocument(key, (Document) entry.getValue());
            }
            indexManager.addDocument(key, document);

            // Construct the Value object and store in the filer.
            Record record = writeRecord(key, true, documentBytes);

            // Cache Stuff
            if (cache != null) {
                cache.putEntry(this, key,
                               compressed ? DocumentCache.COMPRESSED : DocumentCache.UNCOMPRESSED,
                               new Value(documentBytes), Entry.createMetaMap(record));
            }

            // Update the meta for this document
            updateDocumentMeta(record);
        }

        DBObserver.getInstance().putDocument(this, key, document, entry == null);
        return entry == null;
    }

    private boolean putObject(Key key, XMLSerializable obj, byte action) 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);
        return putDocument(key, doc, action);
    }

    /**
     * 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.
     * <br/><br/>
     * putBinary attempts to perform requested action, and success depends on action
     * and presense of the key in the collection.
     * <br/><br/>
     * @param key Entry key
     * @param bytes Value
     * @param action It can be either ACTION_INSERT, ACTION_UPDATE or ACTION_STORE
     * @return True if new binary entry was created, false otherwise
     * @throws DBException<ul>
     * <li>FaultCodes.COL_DUPLICATE_RESOURCE If entry with that key already present in
     * collection and action is ACTION_INSERT</li>
     * <li>FaultCodes.COL_DOCUMENT_NOT_FOUND If entry with that key is not present in
     * collection and action is ACTION_UPDATE
     * </ul>
     */
    private boolean putBinary(Key key, byte[] bytes, byte action) throws DBException {
        Entry entry;

        synchronized (getIdentityKey(key)) {
            entry = getEntry(key);
            if (action == ACTION_INSERT && entry != null) {
                    throw new DBException(FaultCodes.COL_DUPLICATE_RESOURCE,
                                          "Error inserting binary resource '" + key + "' in '" + getCanonicalName() +
                                          "': key is already in database");
            } else if (action == ACTION_UPDATE && entry == null) {
                    throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                          "Error updating binary resource '" + key + "' in '" + getCanonicalName() +
                                          "': key does not exist in database");
            }

            if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
                // binary resources aren't stored in indexes
                indexManager.removeDocument(key, (Document) entry.getValue());
            }

            // Construct the Value object and store in the filer.
            Record record = writeRecord(key, false, bytes);

            if (cache != null) {
                cache.putEntry(this, key, DocumentCache.BINARY, new Value(bytes), Entry.createMetaMap(record));
            }

            // update the meta for this document
            updateDocumentMeta(record);
        }

        return entry == null;
    }

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

        checkFiler(FaultCodes.COL_NO_FILER);

        Key objKey = getIdentityKey(createNewKey(id));
        synchronized (objKey) {
            Entry entry = getEntry(objKey);
            if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
                indexManager.removeDocument(objKey, (Document) entry.getValue());
            }

            if (cache != null) {
                cache.removeEntry(this, objKey);
            }

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

            // remove the document meta
            if (isMetaEnabled()) {
                getMetaSystemCollection().dropDocumentMeta(this, objKey.toString());
            }
        }

        // update the meta for this collection if necessary
        updateCollectionMeta();

        DBObserver.getInstance().dropDocument(this, objKey);
    }


    // -- Core Collection API Public Methods: Meta Data Management ----------

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

    /**
     * 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.
     * @throws DBException if operation failed
     */
    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;
    }

    /**
     * Reset the metadata object for this collection.
     *
     * @param meta the Metadata to use
     * @throws DBException if operation failed
     */
    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);
        }
    }

    /**
     * Retrieve a database entry metadata by key.
     *
     * If no matching entry is found, null is returned, otherwise this method
     * return Entry that holds metadata only.
     *
     * @param id identifying the desired database entry
     * @return Entry containing the metadata of 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 Entry getEntryMeta(Object id) throws DBException {
        if (id == null) {
            return null;
        }

        checkFiler(FaultCodes.COL_NO_FILER);

        Key key = getIdentityKey(createNewKey(id));
        synchronized (key) {
            /*
             * If the key has a corresponding value in the cache, return it
             * and save a disk access.
             */
            if (cache != null) {
                Entry entry = cache.getEntryMeta(this, key);
                if (entry != null) {
                    return entry;
                }
            }

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

            Map entryMeta = Entry.createMetaMap(record);
            return new Entry(key, entryMeta);
        }
    }

    /**
     * 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
     * @return meta data for requested resource
     * @throws DBException if operation failed
     */
    public MetaData getDocumentMeta(String id) throws DBException {
        if (!isMetaEnabled()) {
            if (log.isWarnEnabled()) {
                log.warn("Meta information requested but not enabled in config!");
            }
            return null;
        }

        Key key = getIdentityKey(createNewKey(id));
        synchronized (key) {
            if (getEntryMeta(id) == null) {
                throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                      "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
            }

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

            /*
            FIXME It is more efficient to store (and retrieve) created/modified timestamps
                  from the Record itself instead of storing them in the separate MetaData
                  object. Storing in the Record avoids writing two documents on each update
                  (Document itself and its MetaData).
                  Retrieval of the timestamps from Record can be implemented via TimeRecord.

            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 (meta == null) {
                meta = new MetaData(MetaData.DOCUMENT, getCanonicalDocumentName(id), now, now);
                metacol.setDocumentMeta(this, id, meta);
            } else if (!meta.hasContext()) {
                meta.setContext(now, now);
            }

            return meta;
        }
    }

    /**
     * Set the metadata associated with a document within this collection.
     *
     * @param id the document name
     * @param meta the metadata object to be used.
     * @throws DBException if operation failed
     */
    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;
        }

        Key key = getIdentityKey(createNewKey(id));
        synchronized (key) {
            if (getEntryMeta(id) == null) {
                throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
                                      "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
            }

            if (meta != null) {
                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);
            }
        }
    }

    /**
     * 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;
            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);
                }
            }
        }
    }

    /**
     * update the modified time of this document when appropriate
     *
     * @param record database record for which metadata should be updated
     * @throws DBException if operation failed
     */
    protected void updateDocumentMeta(Record record) throws DBException {
        // update the meta data if necessary
        if (!isMetaEnabled()) {
            return;
        }

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

        if (log.isTraceEnabled()) {
            log.trace(debugHeader() + "Updating modified time for document '" + id + "'");
        }

        long now = System.currentTimeMillis();
        // Creation and modification time are not necessary will be available.
        // Some filers may not have that information, in which case current time is used
        Long time = (Long) record.getMetaData(Record.CREATED);
        long created = time != null ? time.longValue() : now;

        time = (Long) record.getMetaData(Record.MODIFIED);
        long modified = time != null ? time.longValue() : now;

        if (null == meta) {
            meta = new MetaData(MetaData.DOCUMENT, path, created, modified);
        } else if (!meta.hasContext()) {
            meta.setContext(created, modified);
        } else {
            meta.setContext(0, modified);
        }

        metacol.setDocumentMeta(this, id, meta);
    }


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

    /**
     * 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
     * @throws DBException if operation failed
     */
    public final Container getContainer(Object docKey) throws DBException {
        Key key = createNewKey(docKey);
        Document doc = getDocument(key);
        return doc != null ? new ColContainer(key, doc) : null;
    }

    /**
     * getDocumentCount returns the count of Documents being maintained
     * by this Collection.
     *
     * @return The Document count
     * @throws DBException if operation failed
     */
    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();
    }

    /**
     * getDocumentSet returns the set of Documents being maintained
     * by this Collection.
     *
     * @return The DocumentSet
     * @throws DBException if operation failed
     */
    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());
    }

    /**
     * listDocuments returns a list of all entry keys stored by this
     * collection.
     *
     * @return the list of entry keys
     * @throws DBException if operation failed
     */
    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[temp.size()]);
        }
    }

    /**
     * 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
     * @throws DBException if operation failed
     */
    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
     * @throws DBException if operation failed
     */
    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;
        if (key instanceof Key[]) {
            k = (Key[]) key;
        } else {
            k = new Key[]{createNewKey(key)};
        }
        return getQueryEngine().query(this, style, query, nsMap, k);
    }
}
TOP

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

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.