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