You are viewing a plain text version of this content. The canonical link for it is here.
Posted to xindice-dev@xml.apache.org by vg...@apache.org on 2007/11/12 19:06:01 UTC

svn commit: r594237 - /xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java

Author: vgritsenko
Date: Mon Nov 12 10:06:00 2007
New Revision: 594237

URL: http://svn.apache.org/viewvc?rev=594237&view=rev
Log:
rearranging methods - use logical groupings

Modified:
    xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java

Modified: xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java
URL: http://svn.apache.org/viewvc/xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java?rev=594237&r1=594236&r2=594237&view=diff
==============================================================================
--- xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java (original)
+++ xml/xindice/trunk/java/src/org/apache/xindice/core/Collection.java Mon Nov 12 10:06:00 2007
@@ -324,6 +324,49 @@
         }
     }
 
+    /**
+     * 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 -------------------------------------------
 
@@ -664,7 +707,42 @@
     }
 
 
-    // ----------------------------------------------------------------------
+    // -- 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
@@ -682,23 +760,29 @@
     }
 
     /**
-     * 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.
+     * dropIndexer physically removes the specified Indexer and any
+     * associated system resources that the Indexer uses.
      *
-     * @param key The Key hint, can be null
-     * @return The newly generated Key
+     * @param index The Indexer to drop
+     * @return Whether or not the Indexer was dropped
+     * @throws DBException if operation failed
      */
-    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());
+    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 ---------------
+
     /**
      * createNewOID allocates a new Object ID to be used as a Key in the
      * Collection.
@@ -722,47 +806,130 @@
     }
 
     /**
-     * dropIndexer physically removes the specified Indexer and any
-     * associated system resources that the Indexer uses.
+     * Retrieve a database entry by key.
      *
-     * @param index The Indexer to drop
-     * @return Whether or not the Indexer was dropped
-     * @throws DBException if operation failed
+     * 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 docKey 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 boolean dropIndexer(Indexer index) throws DBException {
-        checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
+    public final Entry getEntry(Object docKey) throws DBException {
 
-        if (index == null) {
-            throw new DBException(FaultCodes.IDX_INDEX_NOT_FOUND,
-                                  "Index value is null");
+        // 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;
         }
 
-        boolean success = indexManager.drop(index.getName());
-        getDatabase().flushConfig();
-        return success;
+        String localDebugHeader = null;
+        if (log.isTraceEnabled()) {
+            localDebugHeader = debugHeader() + "getEntry: docKey=<" + docKey + ">: ";
+            log.trace(localDebugHeader);
+        }
+
+        checkFiler(FaultCodes.COL_NO_FILER);
+
+        Key key = getIdentityKey(createNewKey(docKey));
+        synchronized (key) {
+
+            /*
+             * 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) {
+                Entry entry = documentCache.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;
+            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());
+                }
+            }
+
+            Map entryMeta = Entry.createMetaMap(record);
+            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.putDocumentEntry(this, key, value.getData(), entryMeta);
+                    }
+                } else {
+                    String documentChars = value.toString();
+                    if (log.isTraceEnabled()) {
+                        log.trace(localDebugHeader + "Pre parseDocument(): value=<" + documentChars + ">");
+                    }
+
+                    // FIXME There should be no reason here to re-compress the document & flush symbols table?
+                    document = parseDocument(key, documentChars);
+                    flushSymbolTable();
+
+                    if (documentCache != null) {
+                        documentCache.putDocumentEntry(this, key, documentChars, entryMeta);
+                    }
+                }
+
+                DBObserver.getInstance().loadDocument(this, record, document);
+                return new Entry(key, document, entryMeta);
+            } else {
+                if (log.isTraceEnabled()) {
+                    log.trace(localDebugHeader + "Binary document");
+                }
+
+                return new Entry(key, value.getData(), entryMeta);
+            }
+        }
     }
 
     /**
-     * Retrieve a binary database entry by key.
-     * This low-level method will not update non-inline metadata.
+     * getDocument retrieves a Document by Key.
      *
-     * @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
+     * @param key The Document Key
+     * @return The Document
+     * @throws DBException if operation failed
      */
-    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)");
+    public final Document getDocument(Object key) throws DBException {
+        if (log.isDebugEnabled()) {
+            log.debug(debugHeader() + "Get document: " + key);
         }
 
         Entry entry = getEntry(key);
@@ -770,76 +937,7 @@
             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();
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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) {
+        if (entry.getEntryType() != Entry.DOCUMENT) {
             throw new DBException(FaultCodes.COL_INVALID_RESULT,
                                   "Resource '" + key + "' in collection '" +
                                   getCanonicalName() + "' is not a document");
@@ -849,268 +947,6 @@
     }
 
     /**
-     * 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();
-    }
-
-    /**
-     * 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 (getEntry(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;
-        }
-    }
-
-    /**
-     * 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());
-    }
-
-    /**
-     * 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 docKey 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 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 = getIdentityKey(createNewKey(docKey));
-        synchronized (key) {
-
-            /*
-             * 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) {
-                Entry entry = documentCache.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;
-            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());
-                }
-            }
-
-            Map entryMeta = Entry.createMetaMap(record);
-            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.putDocumentEntry(this, key, value.getData(), entryMeta);
-                    }
-                } else {
-                    String documentChars = value.toString();
-                    if (log.isTraceEnabled()) {
-                        log.trace(localDebugHeader + "Pre parseDocument(): value=<" + documentChars + ">");
-                    }
-
-                    // FIXME There should be no reason here to re-compress the document & flush symbols table?
-                    document = parseDocument(key, documentChars);
-                    flushSymbolTable();
-
-                    if (documentCache != null) {
-                        documentCache.putDocumentEntry(this, key, documentChars, entryMeta);
-                    }
-                }
-
-                DBObserver.getInstance().loadDocument(this, record, document);
-                return new Entry(key, document, entryMeta);
-            } else {
-                if (log.isTraceEnabled()) {
-                    log.trace(localDebugHeader + "Binary document");
-                }
-
-                return new Entry(key, value.getData(), entryMeta);
-            }
-        }
-    }
-
-    /**
-     * 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 docKey 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 docKey) throws DBException {
-        if (docKey == null) {
-            return null;
-        }
-
-        checkFiler(FaultCodes.COL_NO_FILER);
-
-        Key key = getIdentityKey(createNewKey(docKey));
-        synchronized (key) {
-            /*
-             * If the key has a corresponding value in the cache, return it
-             * and save a disk access.
-             */
-            if (documentCache != null) {
-                Entry entry = documentCache.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);
-        }
-    }
-
-    /**
-     * 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);
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
      * 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.
@@ -1154,46 +990,40 @@
     }
 
     /**
-     * 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.
+     * Retrieve a binary database entry by key.
+     * This low-level method will not update non-inline metadata.
      *
-     * @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.
+     * @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 Key insertBinary(byte[] bytes) throws DBException {
-        return insertBinary(null, bytes);
-    }
+    public final byte[] getBinary(Object key) throws DBException {
+        if (log.isTraceEnabled()) {
+            log.trace(debugHeader() + "Get binary: " + key);
+        }
 
-    /**
-     * 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)");
+            throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
+                                  "Collection '" + getCanonicalName() +
+                                  "' has no binary resources (inline metadata is not enabled)");
         }
 
-        Key key = createNewKey(docKey);
-        if (log.isInfoEnabled()) {
-            log.info(debugHeader() + "Insert binary: " + key);
+        Entry entry = getEntry(key);
+        if (entry == null) {
+            return null;
         }
-        putBinary(key, bytes, ACTION_INSERT);
 
-        // update the meta information if necessary
-        updateCollectionMeta();
-        return key;
+        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();
     }
 
     /**
@@ -1263,129 +1093,112 @@
     }
 
     /**
-     * Returns whether or not meta data is enabled.
+     * 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.
      *
-     * @return boolean whether or not meta data is enabled.
+     * @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 boolean isMetaEnabled() {
-        return getDatabase().isMetaEnabled();
+    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;
     }
 
     /**
-     * listDocuments returns a list of all entry keys stored by this
-     * collection.
+     * setDocument inserts or updates an existing Document in a
+     * Xindice Collection.
      *
-     * @return the list of entry keys
+     * @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 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());
-            }
+    public final boolean setDocument(Object docKey, Document document) throws DBException {
+        if (log.isInfoEnabled()) {
+            log.info(debugHeader() + "Set document " + docKey);
+        }
 
-            return (String[]) temp.toArray(new String[temp.size()]);
+        boolean res = putDocument(createNewKey(docKey), document, ACTION_STORE);
+        if (res) {
+            updateCollectionMeta();
         }
-    }
 
-    /**
-     * 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();
+        return res;
     }
 
     /**
-     * Turns an XML string into a parsed document retrieved
-     * from the uncompressed collection.
+     * 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 when caching
-     * @param xml The string to parse
-     * @return A parsed DOM document or null if failure
+     * @param key The Key to use
+     * @param obj The Object to set
      * @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);
+    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);
     }
 
     /**
-     * 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>
+     * 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.
      */
-    private boolean putBinary(Key key, byte[] bytes, byte action) throws DBException {
-        synchronized (getIdentityKey(key)) {
-            Entry 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 cache or indexes
-                if (documentCache != null) {
-                    documentCache.removeEntry(this, key);
-                }
-                indexManager.removeDocument(key, (Document) entry.getValue());
-            }
+    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)");
+        }
 
-            InlineMetaMap map = inlineMetaService.getEmptyMap();
-            map.put("type", ResourceTypeReader.BINARY);
-            Value value = inlineMetaService.createValue(map, bytes, 0, bytes.length);
-            Record record = filer.writeRecord(key, value);
+        if (log.isInfoEnabled()) {
+            log.info(debugHeader() + "Set binary " + docKey);
+        }
 
-            // update the meta for this document
-            updateDocumentMeta(record);
-            return entry == null;
+        boolean res = putBinary(createNewKey(docKey), bytes, ACTION_STORE);
+        if (res) {
+            updateCollectionMeta();
         }
+
+        return res;
     }
 
     /**
@@ -1541,50 +1354,54 @@
     }
 
     /**
-     * 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
+     * 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>
      */
-    public final NodeSet queryCollection(String style, String query, NamespaceMap nsMap) throws DBException {
-        if (log.isDebugEnabled()) {
-            log.debug(debugHeader() + "Query collection, query " + query);
-        }
+    private boolean putBinary(Key key, byte[] bytes, byte action) throws DBException {
+        synchronized (getIdentityKey(key)) {
+            Entry 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");
+            }
 
-        // 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);
-    }
+            if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
+                // binary resources aren't stored in cache or indexes
+                if (documentCache != null) {
+                    documentCache.removeEntry(this, key);
+                }
+                indexManager.removeDocument(key, (Document) entry.getValue());
+            }
 
-    /**
-     * 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);
-        }
+            InlineMetaMap map = inlineMetaService.getEmptyMap();
+            map.put("type", ResourceTypeReader.BINARY);
+            Value value = inlineMetaService.createValue(map, bytes, 0, bytes.length);
+            Record record = filer.writeRecord(key, value);
 
-        checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
-        Key[] k;
-        if (key instanceof Key[]) {
-            k = (Key[]) key;
-        } else {
-            k = new Key[]{createNewKey(key)};
+            // update the meta for this document
+            updateDocumentMeta(record);
+            return entry == null;
         }
-        return getQueryEngine().query(this, style, query, nsMap, k);
     }
 
     /**
@@ -1626,7 +1443,47 @@
                 getMetaSystemCollection().dropDocumentMeta(this, objKey.toString());
             }
         }
-        DBObserver.getInstance().dropDocument(this, objKey);
+        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;
     }
 
     /**
@@ -1657,53 +1514,99 @@
     }
 
     /**
-     * setDocument inserts or updates an existing Document in a
-     * Xindice Collection.
+     * Retrieve a database entry metadata by key.
      *
-     * @param docKey The Document Key
-     * @param document The Document
-     * @return True if new document entry was created, false otherwise
-     * @throws DBException if operation failed
+     * If no matching entry is found, null is returned, otherwise this method
+     * return Entry that holds metadata only.
+     *
+     * @param docKey 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 boolean setDocument(Object docKey, Document document) throws DBException {
-        if (log.isInfoEnabled()) {
-            log.info(debugHeader() + "Set document " + docKey);
+    public final Entry getEntryMeta(Object docKey) throws DBException {
+        if (docKey == null) {
+            return null;
         }
 
-        boolean res = putDocument(createNewKey(docKey), document, ACTION_STORE);
-        if (res) {
-            updateCollectionMeta();
-        }
+        checkFiler(FaultCodes.COL_NO_FILER);
 
-        return res;
+        Key key = getIdentityKey(createNewKey(docKey));
+        synchronized (key) {
+            /*
+             * If the key has a corresponding value in the cache, return it
+             * and save a disk access.
+             */
+            if (documentCache != null) {
+                Entry entry = documentCache.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);
+        }
     }
 
     /**
-     * setBinary inserts or updates binary object into a Xindice Collection.
+     * Return the MetaData object for a document within this collection.
+     * If metadata is not enabled, the MetaData object returned will be null.
      *
-     * @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.
+     * @param id the document whose metadata you want
+     * @return meta data for requested resource
+     * @throws DBException if operation failed
      */
-    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)");
+    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 (log.isInfoEnabled()) {
-            log.info(debugHeader() + "Set binary " + docKey);
-        }
+        Key key = getIdentityKey(createNewKey(id));
+        synchronized (key) {
+            if (getEntry(id) == null) {
+                throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
+                                      "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
+            }
 
-        boolean res = putBinary(createNewKey(docKey), bytes, ACTION_STORE);
-        if (res) {
-            updateCollectionMeta();
-        }
+            MetaSystemCollection metacol = getMetaSystemCollection();
+            MetaData meta = metacol.getDocumentMeta(this, id);
 
-        return res;
+            /*
+            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;
+        }
     }
 
     /**
@@ -1746,22 +1649,6 @@
     }
 
     /**
-     * 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);
-    }
-
-    /**
      * update the modified time of this collection when appropriate
      */
     protected void updateCollectionMeta() {
@@ -1843,5 +1730,127 @@
         }
 
         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);
     }
 }