Clover coverage report -
Coverage timestamp: Sun Nov 1 2009 23:08:24 UTC
file stats: LOC: 1,903   Methods: 86
NCLOC: 1,057   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
Collection.java 58.3% 74.5% 73.3% 69.3%
coverage coverage
 1    /*
 2    * Licensed to the Apache Software Foundation (ASF) under one or more
 3    * contributor license agreements. See the NOTICE file distributed with
 4    * this work for additional information regarding copyright ownership.
 5    * The ASF licenses this file to You under the Apache License, Version 2.0
 6    * (the "License"); you may not use this file except in compliance with
 7    * the License. You may obtain a copy of the License at
 8    *
 9    * http://www.apache.org/licenses/LICENSE-2.0
 10    *
 11    * Unless required by applicable law or agreed to in writing, software
 12    * distributed under the License is distributed on an "AS IS" BASIS,
 13    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14    * See the License for the specific language governing permissions and
 15    * limitations under the License.
 16    *
 17    * $Id: Collection.java 712324 2008-11-08 00:32:01Z vgritsenko $
 18    */
 19   
 20    package org.apache.xindice.core;
 21   
 22    import org.apache.commons.logging.Log;
 23    import org.apache.commons.logging.LogFactory;
 24    import org.apache.xindice.core.cache.DocumentCache;
 25    import org.apache.xindice.core.data.DocumentSet;
 26    import org.apache.xindice.core.data.EmptyDocumentSet;
 27    import org.apache.xindice.core.data.EmptyNodeSet;
 28    import org.apache.xindice.core.data.Entry;
 29    import org.apache.xindice.core.data.Key;
 30    import org.apache.xindice.core.data.NodeSet;
 31    import org.apache.xindice.core.data.Record;
 32    import org.apache.xindice.core.data.RecordSet;
 33    import org.apache.xindice.core.data.Value;
 34    import org.apache.xindice.core.filer.Filer;
 35    import org.apache.xindice.core.indexer.IndexManager;
 36    import org.apache.xindice.core.indexer.Indexer;
 37    import org.apache.xindice.core.meta.MetaData;
 38    import org.apache.xindice.core.meta.inline.InlineMetaMap;
 39    import org.apache.xindice.core.meta.inline.InlineMetaService;
 40    import org.apache.xindice.core.meta.inline.ResourceTypeReader;
 41    import org.apache.xindice.core.query.QueryEngine;
 42    import org.apache.xindice.util.Configurable;
 43    import org.apache.xindice.util.Configuration;
 44    import org.apache.xindice.util.Named;
 45    import org.apache.xindice.util.XindiceException;
 46    import org.apache.xindice.xml.NamespaceMap;
 47    import org.apache.xindice.xml.NodeSource;
 48    import org.apache.xindice.xml.SymbolTable;
 49    import org.apache.xindice.xml.TextWriter;
 50    import org.apache.xindice.xml.XMLSerializable;
 51    import org.apache.xindice.xml.dom.DBDocument;
 52    import org.apache.xindice.xml.dom.DOMCompressor;
 53    import org.apache.xindice.xml.dom.DOMParser;
 54    import org.apache.xindice.xml.dom.DocumentImpl;
 55   
 56    import org.w3c.dom.Document;
 57    import org.w3c.dom.Element;
 58    import org.w3c.dom.Node;
 59    import org.w3c.dom.NodeList;
 60    import org.w3c.dom.ProcessingInstruction;
 61   
 62    import java.io.File;
 63    import java.io.UnsupportedEncodingException;
 64    import java.lang.ref.WeakReference;
 65    import java.net.InetAddress;
 66    import java.util.ArrayList;
 67    import java.util.Map;
 68    import java.util.WeakHashMap;
 69   
 70    /**
 71    * Collection represents a collection of Documents maintains links to
 72    * the Filer storage implementation, and the Indexes associated with
 73    * the Collection.
 74    *
 75    * @version $Revision: 712324 $, $Date: 2008-11-08 00:32:01 +0000 (Sat, 08 Nov 2008) $
 76    */
 77    public class Collection extends CollectionManager
 78    implements Named, DBObject, Configurable {
 79   
 80    private static final Log log = LogFactory.getLog(Collection.class);
 81   
 82    private static final String CACHE = "cache";
 83    private static final String CLASS = "class";
 84    private static final String CLASSNAME = "xindice-class";
 85    private static final String COMPRESSED = "compressed";
 86    private static final String FILER = "filer";
 87    private static final String INDEXES = "indexes";
 88    private static final String INLINE_METADATA = "inline-metadata";
 89    private static final String NAME = "name";
 90    private static final String SYMBOLS = "symbols";
 91   
 92    private static final byte ACTION_INSERT = 1;
 93    private static final byte ACTION_UPDATE = 2;
 94    private static final byte ACTION_STORE = 3;
 95   
 96    private static final DocumentSet EMPTY_DOCUMENTSET = new EmptyDocumentSet();
 97    private static final NodeSet EMPTY_NODESET = new EmptyNodeSet();
 98    private static final String[] EMPTY_STRING_ARRAY = {};
 99   
 100    private static int host_id;
 101    static {
 102  19 try {
 103  19 InetAddress a = InetAddress.getLocalHost();
 104  19 byte[] b = a.getAddress();
 105  19 host_id = 0;
 106  19 host_id += b[0];
 107  19 host_id += (b[1] << 8);
 108  19 host_id += (b[2] << 16);
 109  19 host_id += (b[3] << 24);
 110  19 host_id = Math.abs(host_id);
 111    } catch (Exception e) {
 112  0 if (log.isWarnEnabled()) {
 113  0 log.warn("ignored exception", e);
 114    }
 115    }
 116    }
 117   
 118    /**
 119    * ColContainer
 120    */
 121    private class ColContainer implements Container {
 122    private Document document;
 123    private Key key;
 124   
 125  0 public ColContainer(Key key, Document document) {
 126  0 this.key = key;
 127  0 this.document = document;
 128    }
 129   
 130  0 public void commit() throws DBException {
 131  0 putDocument(this.key, this.document, ACTION_STORE);
 132    }
 133   
 134  0 public void commit(Document doc) throws DBException {
 135  0 this.document = doc;
 136  0 commit();
 137    }
 138   
 139  0 public String getCanonicalName() throws DBException {
 140  0 return Collection.this.getCanonicalDocumentName(key);
 141    }
 142   
 143  0 public Collection getCollection() {
 144  0 return Collection.this;
 145    }
 146   
 147  0 public Document getDocument() {
 148  0 return this.document;
 149    }
 150   
 151  0 public Key getKey() {
 152  0 return this.key;
 153    }
 154   
 155  0 public void remove() throws DBException {
 156  0 Collection.this.remove(key);
 157    }
 158   
 159  0 public Document rollback() throws DBException {
 160  0 this.document = Collection.this.getDocument(key);
 161  0 return this.document;
 162    }
 163    }
 164   
 165    /**
 166    * ColDocumentSet
 167    */
 168    private class ColDocumentSet implements DocumentSet {
 169    private RecordSet set;
 170   
 171  0 public ColDocumentSet(RecordSet set) {
 172  0 this.set = set;
 173    }
 174   
 175  0 public Container getNextContainer() throws DBException {
 176  0 if (set.hasMoreRecords()) {
 177  0 Record rec = set.getNextRecord();
 178  0 Key key = rec.getKey();
 179  0 Value val = rec.getValue();
 180  0 if (val.getLength() > 0) {
 181  0 try {
 182  0 if (compressed) {
 183  0 Document doc = new DocumentImpl(val.getData(), symbols, new NodeSource(Collection.this, key));
 184  0 return new ColContainer(key, doc);
 185    } else {
 186  0 return new ColContainer(key, DOMParser.toDocument(val));
 187    }
 188    } catch (Exception e) {
 189  0 if (log.isWarnEnabled()) {
 190  0 log.warn("ignored exception", e);
 191    }
 192    }
 193    }
 194    }
 195  0 return null;
 196    }
 197   
 198  0 public Document getNextDocument() throws DBException {
 199  0 Container c = getNextContainer();
 200  0 if (c != null) {
 201  0 return c.getDocument();
 202    } else {
 203  0 return null;
 204    }
 205    }
 206   
 207  0 public boolean hasMoreDocuments() throws DBException {
 208  0 return set.hasMoreRecords();
 209    }
 210    }
 211   
 212   
 213    private String name;
 214    private String canonicalName;
 215    private Collection parent;
 216   
 217    // Object ID Stuff
 218    private final Object oidMutex = new Object();
 219    private String oidTemplate;
 220    private long documentId;
 221   
 222    private SymbolTable symbols;
 223   
 224    private File collectionRoot;
 225    private boolean compressed;
 226    private Filer filer;
 227    private InlineMetaService inlineMetaService;
 228    private DocumentCache cache;
 229    private IndexManager indexManager;
 230   
 231    // document keys identity map
 232    private final Map identityMap = new WeakHashMap();
 233   
 234   
 235  123 protected Collection() {
 236  123 documentId = System.currentTimeMillis();
 237    }
 238   
 239    /**
 240    * @param parent parent collection
 241    */
 242  1661 public Collection(Collection parent) {
 243  1661 this.parent = parent;
 244    }
 245   
 246  18087 public boolean isCompressed() {
 247  18087 return compressed;
 248    }
 249   
 250    // -- Internal Implementation Methods -----------------------------------
 251   
 252  76356 private void checkFiler(int faultCode) throws DBException {
 253  76356 if (filer == null) {
 254  0 throw new DBException(faultCode,
 255    "Collection '" + name + "' cannot store resources (no filer)");
 256    }
 257  76356 if (!filer.isOpened()) {
 258  0 throw new DBException(FaultCodes.COL_COLLECTION_CLOSED,
 259    "Collection '" + name + "' is closed.");
 260    }
 261    }
 262   
 263  74621 private Key getIdentityKey(Key key) {
 264  74621 synchronized (identityMap) {
 265  74621 Key id = null;
 266  74621 WeakReference ref = (WeakReference) identityMap.get(key);
 267  74621 if (ref != null) {
 268  69512 id = (Key) ref.get();
 269    }
 270  74621 if (id == null) {
 271  5109 id = key;
 272  5109 identityMap.put(id, new WeakReference(id));
 273    }
 274   
 275  74621 return id;
 276    }
 277    }
 278   
 279  14978 private String debugHeader() {
 280  14978 return "["
 281    + Thread.currentThread().getName()
 282    + "] '"
 283  14978 + (parent != null ? parent.getCanonicalName() : "")
 284    + "/"
 285    + name
 286    + "' ";
 287    }
 288   
 289    /**
 290    * @throws DBException if operation failed
 291    */
 292  45437 private void flushSymbolTable() throws DBException {
 293  45437 if (symbols.isDirty()) {
 294  941 getSystemCollection().saveSymbols(this, symbols);
 295    }
 296    }
 297   
 298    /**
 299    * @param name collection name
 300    */
 301  0 protected void setName(String name) {
 302  0 this.name = name;
 303    }
 304   
 305  1771 protected final void setCanonicalName(String canonicalName) {
 306  1771 this.canonicalName = canonicalName;
 307   
 308    // Calculate The OID Template
 309  1771 StringBuffer sb = new StringBuffer("00000000000000000000000000000000");
 310  1771 String host = Integer.toString(host_id, 16);
 311  1771 sb.insert(8 - host.length(), host);
 312   
 313  1771 String collection = Integer.toString(Math.abs(canonicalName.hashCode()), 16);
 314  1771 sb.insert(16 - collection.length(), collection);
 315   
 316  1771 sb.setLength(32);
 317  1771 oidTemplate = sb.toString();
 318    }
 319   
 320  1771 protected final void setCollectionRoot(File collectionRoot) {
 321  1771 this.collectionRoot = collectionRoot;
 322  1771 if (!collectionRoot.exists()) {
 323  1059 if (log.isTraceEnabled()) {
 324  0 log.trace("Creating directories: " + collectionRoot);
 325    }
 326  1059 collectionRoot.mkdirs();
 327    }
 328    }
 329   
 330    /**
 331    * createNewKey allocates a new key to be used as a key in the
 332    * collection. Passed in <code>key</code> parameter string value
 333    * used for the key. If passed key parameter is null, new OID is generated.
 334    *
 335    * @param key The Key hint, can be null
 336    * @return The newly generated Key
 337    */
 338  74627 protected final Key createNewKey(Object key) {
 339  74627 if (key == null) {
 340  1 return createNewOID();
 341  74626 } else if (key instanceof Key) {
 342  55197 return (Key) key;
 343    } else {
 344  19429 return new Key(key.toString());
 345    }
 346    }
 347   
 348    /**
 349    * Turns an XML string into a parsed document retrieved
 350    * from the uncompressed collection.
 351    *
 352    * @param key The key to use when caching
 353    * @param xml The string to parse
 354    * @return A parsed DOM document or null if failure
 355    * @throws DBException if operation failed
 356    */
 357  16773 private Document parseDocument(Key key, String xml) throws DBException {
 358  16773 try {
 359  16773 Document doc = DOMParser.toDocument(xml);
 360  16773 ((DBDocument) doc).setSource(new NodeSource(this, key));
 361   
 362    // Have to compress to update collection's SymbolTable,
 363    // which is used even for uncompressed collections
 364  16773 DOMCompressor.compress(doc, symbols);
 365   
 366  16773 return doc;
 367    } catch (Exception e) {
 368  0 throw new DBException(FaultCodes.COL_DOCUMENT_MALFORMED,
 369    "Unable to parse document '" + key + "' in '" + getCanonicalName() + "'", e);
 370    }
 371    }
 372   
 373   
 374    // -- Database Object Methods -------------------------------------------
 375   
 376  0 public boolean isOpened() {
 377    // Collection without filer is always open ... for now.
 378    //noinspection SimplifiableIfStatement
 379  0 if (filer == null) {
 380  0 return true;
 381    }
 382   
 383  0 return filer.isOpened();
 384    }
 385   
 386    /**
 387    * @see org.apache.xindice.core.DBObject#exists()
 388    */
 389  0 public boolean exists() throws DBException {
 390  0 return true;
 391    }
 392   
 393    /**
 394    * @see org.apache.xindice.core.DBObject#create()
 395    */
 396  1033 public boolean create() throws DBException {
 397    // update the meta information if necessary
 398  1033 updateCollectionMeta();
 399   
 400  1033 DBObserver.getInstance().createCollection(this);
 401  1033 return true;
 402    }
 403   
 404  0 public final boolean open() throws DBException {
 405  0 return true;
 406    }
 407   
 408    /**
 409    * @see org.apache.xindice.core.DBObject#drop()
 410    */
 411  1036 public boolean drop() throws DBException {
 412  1036 DBObserver.getInstance().dropCollection(this);
 413   
 414    // Drop the meta if necessary
 415  1036 if (isMetaEnabled()) {
 416  1035 getMetaSystemCollection().dropCollectionMeta(this);
 417    }
 418   
 419    // Drop Child Collections
 420  1036 String[] cols = listCollections();
 421  1036 for (int i = 0; i < cols.length; i++) {
 422  42 dropCollection(getCollection(cols[i]));
 423    }
 424   
 425  1036 if (filer != null) {
 426    // Drop Indexers and Filer
 427  1036 indexManager.drop();
 428  1036 filer.drop();
 429    }
 430   
 431  1036 getCollectionRoot().delete();
 432   
 433    // Drop symbols
 434  1036 if (!symbols.isReadOnly()) {
 435  1036 getSystemCollection().dropSymbols(this);
 436    }
 437   
 438  1036 getDatabase().flushConfig();
 439  1036 return true;
 440    }
 441   
 442    /**
 443    * @see org.apache.xindice.core.DBObject#close()
 444    */
 445  736 public boolean close() throws DBException {
 446    // Close children collections first
 447  736 super.close();
 448   
 449    // Close its own filer
 450  736 if (filer != null) {
 451  246 indexManager.close();
 452  246 filer.close();
 453    }
 454   
 455  736 return true;
 456    }
 457   
 458   
 459    // -- CollectionManager methods -----------------------------------------
 460   
 461  1894 public void setConfig(Configuration config) throws XindiceException {
 462  1894 name = config.getAttribute(NAME);
 463  1894 compressed = config.getBooleanAttribute(COMPRESSED, true);
 464   
 465    /*
 466    * If inline metadata is desired, get an InlineMetaService object.
 467    */
 468  1894 if (config.getBooleanAttribute(INLINE_METADATA, false)) {
 469  513 inlineMetaService = new InlineMetaService();
 470    }
 471   
 472    /*
 473    * Wait to set up the local debug header until everything needed
 474    * by debugHeader() is complete!
 475    */
 476  1894 final String localDebugHeader = debugHeader() + "setConfig: ";
 477   
 478    // Set parent
 479  1894 if (parent != null) {
 480  1648 setCanonicalName(parent.getCanonicalName() + '/' + name);
 481  1648 setCollectionRoot(new File(parent.getCollectionRoot(), name));
 482  1648 if (log.isDebugEnabled()) {
 483  0 log.debug(localDebugHeader + "Root=<" + getCollectionRoot() + ">");
 484    }
 485    }
 486   
 487  1894 if (log.isDebugEnabled()) {
 488  0 log.debug(localDebugHeader
 489  0 + (compressed ? "Compressed" : "NOT Compressed")
 490    + ", "
 491  0 + (inlineMetaService == null ? "Inline metadata DISABLED" : "Inline metadata ENABLED")
 492    );
 493    }
 494   
 495  1894 if (config.getBooleanAttribute(CACHE, true)) {
 496  1894 cache = getDatabase().getDocumentCache();
 497    }
 498   
 499    // If no Filer is defined, skip Symbols and Indexes
 500  1894 Configuration filerConfig = config.getChild(FILER);
 501  1894 if (filerConfig != null) {
 502  1281 if (log.isTraceEnabled()) {
 503  0 log.trace(localDebugHeader + "Have filer config...");
 504    }
 505   
 506    // Symbol Table Setup
 507  1281 Configuration symConfig = config.getChild(SYMBOLS);
 508  1281 if (symConfig != null) {
 509  123 if (log.isTraceEnabled()) {
 510  0 log.trace(localDebugHeader +
 511    "Internal symbols=<" + TextWriter.toString(symConfig.getElement()) + ">");
 512    }
 513   
 514  123 try {
 515  123 symbols = new SymbolTable(symConfig.getElement(), true);
 516    } catch (Exception e) {
 517  0 if (log.isWarnEnabled()) {
 518  0 log.warn(localDebugHeader + "Error building symbol table from internal symbols", e);
 519    }
 520    }
 521    } else {
 522  1158 if (log.isTraceEnabled()) {
 523  0 log.trace(localDebugHeader + "No internal symbols...");
 524    }
 525   
 526  1158 try {
 527  1158 symbols = getSystemCollection().loadSymbols(this);
 528  1158 if (log.isDebugEnabled()) {
 529  0 log.debug(localDebugHeader + "Loaded symbols=<" +
 530    TextWriter.toString(symbols.streamToXML(new DocumentImpl())) + ">");
 531    }
 532    } catch (Exception e) {
 533  0 if (log.isWarnEnabled()) {
 534  0 log.warn(localDebugHeader + "Error loading symbol table from system collection", e);
 535    }
 536    }
 537    }
 538   
 539  1281 String className = filerConfig.getAttribute(CLASS);
 540  1281 if (log.isDebugEnabled()) {
 541  0 log.debug(localDebugHeader + "Filer class=<" + className + ">");
 542    }
 543  1281 try {
 544  1281 filer = (Filer) Class.forName(className).newInstance();
 545  1281 filer.setLocation(getCollectionRoot(), getName());
 546  1281 filer.setConfig(filerConfig);
 547  1281 if (!filer.exists()) {
 548  1043 filer.create();
 549    }
 550  1281 filer.open();
 551    } catch (Exception e) {
 552  0 if (log.isWarnEnabled()) {
 553  0 log.warn("Filer '" + className + "' is not available", e);
 554    }
 555    }
 556   
 557    // Index Manager
 558  1281 try {
 559  1281 indexManager = new IndexManager(this, getDatabase().getTimer());
 560  1281 Configuration idxConfig = config.getChild(INDEXES, true);
 561  1281 indexManager.setConfig(idxConfig);
 562    } catch (Exception e) {
 563  0 if (log.isWarnEnabled()) {
 564  0 log.warn("Failed to initialize indexer", e);
 565    }
 566    }
 567    }
 568   
 569    // Last thing to do is to init child collections
 570  1894 super.setConfig(config);
 571   
 572    // observer
 573  1894 DBObserver.getInstance().setCollectionConfig(this, config);
 574    }
 575   
 576    /**
 577    * @see CollectionManager#createCollection(String, Configuration)
 578    */
 579  1062 public final Collection createCollection(String path, Configuration config) throws DBException {
 580  1062 Collection col = super.createCollection(path, config);
 581  1036 getDatabase().flushConfig();
 582  1036 return col;
 583    }
 584   
 585    /**
 586    * @see CollectionManager#dropCollection(Collection)
 587    */
 588  1558 public final boolean dropCollection(Collection collection) throws DBException {
 589  1558 boolean success = super.dropCollection(collection);
 590  1533 getDatabase().flushConfig();
 591  1533 return success;
 592    }
 593   
 594    // -- Core Collection API Public Methods --------------------------------
 595   
 596    /**
 597    * getDatabase returns the Database owner for this Collection.
 598    *
 599    * @return The Database
 600    */
 601  119863 public Database getDatabase() {
 602  119863 return parent.getDatabase();
 603    }
 604   
 605    /**
 606    * getParentCollection returns the parent Collection of this
 607    * Collection.
 608    *
 609    * @return The parent Collection (or null)
 610    * @throws DBException if operation failed
 611    */
 612  1533 public final Collection getParentCollection() throws DBException {
 613  1533 return parent;
 614    }
 615   
 616   
 617    /**
 618    * getSystemCollection returns the System Collection.
 619    *
 620    * @return The System Collection
 621    */
 622  3135 public SystemCollection getSystemCollection() {
 623  3135 return getDatabase().getSystemCollection();
 624    }
 625   
 626    /**
 627    * Return the MetaSystemCollection for the database containing this
 628    * collection.
 629    *
 630    * @return MetaSystemCollection
 631    */
 632  17768 private MetaSystemCollection getMetaSystemCollection() {
 633  17768 return getDatabase().getMetaSystemCollection();
 634    }
 635   
 636  6293 public final String getName() {
 637  6294 return name;
 638    }
 639   
 640    /**
 641    * getCanonicalName returns the canonical name of this Collection.
 642    * <br>
 643    * ex: /local/test/ocs
 644    *
 645    * @return The canonical name of the Collection
 646    */
 647  92771 public final String getCanonicalName() {
 648  92771 return canonicalName;
 649    }
 650   
 651    /**
 652    * getCanonicalDocumentName returns the canonical name for the specified
 653    * Key in relation to this Collection.
 654    * <br>
 655    * ex: /local/test/ocs/ytd
 656    *
 657    * @param key The Key
 658    * @return The canonical name
 659    */
 660  109797 public final String getCanonicalDocumentName(Key key) {
 661  109797 return getCanonicalDocumentName(key.toString());
 662    }
 663   
 664    /**
 665    * From the document key and this collection canonical name,
 666    * composes canonical document name.
 667    *
 668    * @param key document key
 669    * @return The canonical document name
 670    */
 671  120031 public final String getCanonicalDocumentName(String key) {
 672  120031 return canonicalName + '/' + key;
 673    }
 674   
 675    /**
 676    * @return The collection root
 677    */
 678  4335 public final File getCollectionRoot() {
 679  4335 return collectionRoot;
 680    }
 681   
 682    /**
 683    * getSymbols returns the SymbolTable in use by this
 684    * Collection.
 685    *
 686    * @return The Symbol Table
 687    */
 688  17015 public final SymbolTable getSymbols() {
 689  17015 return symbols;
 690    }
 691   
 692    /**
 693    * getFiler returns the low-level Filer instance underlying the
 694    * Collection instance.
 695    *
 696    * @return The requested Filer
 697    */
 698  910 public final Filer getFiler() {
 699  910 return filer;
 700    }
 701   
 702    /**
 703    * getQueryEngine returns the Database's Query Engine
 704    *
 705    * @return The Query Engine
 706    * @throws DBException if operation failed
 707    */
 708  372 public QueryEngine getQueryEngine() throws DBException {
 709  372 return getDatabase().getQueryEngine();
 710    }
 711   
 712   
 713    // -- Core Collection API Public Methods: Index Management --------------
 714   
 715    /**
 716    * return the IndexManager being used by this Collection.
 717    *
 718    * @return The IndexManager
 719    * @throws DBException if operation failed
 720    */
 721  1497 public final IndexManager getIndexManager() throws DBException {
 722  1497 checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
 723  1497 return indexManager;
 724    }
 725   
 726    /**
 727    * getIndexer retrieves an Indexer by name.
 728    *
 729    * @param name The Indexer name
 730    * @return The Indexer (or null)
 731    * @throws DBException if operation failed
 732    */
 733  66 public final Indexer getIndexer(String name) throws DBException {
 734  66 checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
 735  66 return indexManager.get(name);
 736    }
 737   
 738    /**
 739    * listIndexers returns a list of the currently registered Indexers
 740    * as an array of String.
 741    *
 742    * @return The Indexer list
 743    * @throws DBException if operation failed
 744    */
 745  15 public final String[] listIndexers() throws DBException {
 746  15 checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
 747  15 return indexManager.list();
 748    }
 749   
 750    /**
 751    * createIndexer creates a new Indexer object and any associated
 752    * system resources that the Indexer will need.
 753    *
 754    * @param config The Indexer's configuration
 755    * @return The newly created Indexer
 756    * @throws DBException if operation failed
 757    */
 758  154 public final Indexer createIndexer(Configuration config) throws DBException {
 759  154 checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
 760  154 Indexer idx = indexManager.create(config);
 761  144 getDatabase().flushConfig();
 762  144 return idx;
 763    }
 764   
 765    /**
 766    * dropIndexer physically removes the specified Indexer and any
 767    * associated system resources that the Indexer uses.
 768    *
 769    * @param index The Indexer to drop
 770    * @return Whether or not the Indexer was dropped
 771    * @throws DBException if operation failed
 772    */
 773  66 public final boolean dropIndexer(Indexer index) throws DBException {
 774  66 checkFiler(FaultCodes.COL_NO_INDEXMANAGER);
 775   
 776  66 if (index == null) {
 777  8 throw new DBException(FaultCodes.IDX_INDEX_NOT_FOUND,
 778    "Index value is null");
 779    }
 780   
 781  58 boolean success = indexManager.drop(index.getName());
 782  58 getDatabase().flushConfig();
 783  58 return success;
 784    }
 785   
 786   
 787    // -- Core Collection API Public Methods: Data Management ---------------
 788   
 789  10216 private Record writeRecord(Key key, boolean isDocument, byte[] bytes) throws DBException {
 790    // Construct the Value object that is stored in the BTree.
 791  10216 Value value;
 792  10216 if (inlineMetaService == null) {
 793  8233 value = new Value(bytes);
 794    } else {
 795  1983 InlineMetaMap map = inlineMetaService.getEmptyMap();
 796  1983 if (isDocument) {
 797  1963 map.put("type", ResourceTypeReader.XML);
 798    } else {
 799  20 map.put("type", ResourceTypeReader.BINARY);
 800    }
 801  1983 value = inlineMetaService.createValue(map, bytes, 0, bytes.length);
 802    }
 803   
 804  10216 return filer.writeRecord(key, value);
 805    }
 806   
 807    /**
 808    * createNewOID allocates a new Object ID to be used as a Key in the
 809    * Collection.
 810    *
 811    * @return The newly generated key
 812    */
 813  13 public final Key createNewOID() {
 814  13 long ct = System.currentTimeMillis();
 815  13 synchronized (oidMutex) {
 816  13 if (ct <= documentId) {
 817  0 ct = documentId + 1;
 818    }
 819  13 documentId = ct;
 820    }
 821   
 822  13 StringBuffer sb = new StringBuffer(oidTemplate);
 823  13 String document = Long.toString(documentId, 16);
 824  13 sb.insert(32 - document.length(), document);
 825  13 sb.setLength(32);
 826  13 return new Key(sb.toString());
 827    }
 828   
 829    /**
 830    * Retrieve a database entry by key.
 831    *
 832    * If no matching entry is found, null is returned, otherwise this
 833    * method returns Entry that identifies resource type and holds its
 834    * value and metadata.
 835    *
 836    * @param id identifying the desired database entry
 837    * @return Entry containing the database entry and its metadata, or null
 838    * if no matching entry is found
 839    * @throws DBException in case of backing store error,
 840    * and in case of header corruption
 841    */
 842  62344 public final Entry getEntry(Object id) throws DBException {
 843    // Test requires null result for null key.
 844    // Read on filer-less collection should result in null too.
 845  62344 if (id == null || filer == null) {
 846  4 return null;
 847    }
 848   
 849  62340 String localDebugHeader = null;
 850  62340 if (log.isTraceEnabled()) {
 851  0 localDebugHeader = debugHeader() + "getEntry: id=<" + id + ">: ";
 852  0 log.trace(localDebugHeader);
 853    }
 854   
 855  62340 checkFiler(FaultCodes.COL_NO_FILER);
 856   
 857  62340 Key key = getIdentityKey(createNewKey(id));
 858  62340 synchronized (key) {
 859    /*
 860    * If the key has a corresponding value in the cache, return it
 861    * and save a disk access.
 862    */
 863  62340 if (cache != null) {
 864  62340 Entry entry = cache.getEntry(this, key);
 865  62340 if (entry != null) {
 866  18109 if (log.isTraceEnabled()) {
 867  0 log.trace(localDebugHeader + "Returning cached: " + entry.getValue());
 868    }
 869   
 870  18109 return entry;
 871    }
 872    }
 873   
 874  44231 Record record = filer.readRecord(key);
 875  44231 if (record == null) {
 876  8998 return null;
 877    }
 878   
 879  35233 Value value;
 880  35233 boolean isDocument;
 881  35233 if (inlineMetaService == null) {
 882  781 value = record.getValue();
 883  781 isDocument = true;
 884   
 885  781 if (log.isTraceEnabled()) {
 886  0 log.trace(localDebugHeader + "Type is not available, Length=" + value.getLength());
 887    }
 888    } else {
 889  34452 InlineMetaService.DatabaseEntry databaseEntry = inlineMetaService.readDatabaseEntry(record.getValue());
 890  34452 Object type = databaseEntry.map.get("type");
 891  34452 value = databaseEntry.value;
 892  34452 isDocument = type.equals(ResourceTypeReader.XML);
 893   
 894  34452 if (log.isTraceEnabled()) {
 895  0 log.trace(localDebugHeader + "Type=" + type + ", Length=" + value.getLength());
 896    }
 897    }
 898   
 899  35233 Map entryMeta = Entry.createMetaMap(record);
 900  35233 if (isDocument) {
 901  35233 Document document;
 902  35233 if (compressed) {
 903  21240 document = new DocumentImpl(value.getData(), symbols, new NodeSource(this, key));
 904  21240 if (log.isTraceEnabled()) {
 905  0 log.trace(localDebugHeader +
 906    "Compressed XML document=<" + TextWriter.toString(document) + ">");
 907    }
 908    } else {
 909    // FIXME There should be no reason here to re-compress the document & flush symbols table?
 910  13993 document = parseDocument(key, value.toString());
 911  13993 if (log.isTraceEnabled()) {
 912  0 log.trace(localDebugHeader +
 913    "Uncompressed XML document=<" + TextWriter.toString(document) + ">");
 914    }
 915    }
 916   
 917    // Symbol table could have been updated above, flush it to the disk.
 918  35233 flushSymbolTable();
 919   
 920  35233 if (cache != null) {
 921  35233 cache.putEntry(this, key, Entry.DOCUMENT, value, entryMeta);
 922    }
 923   
 924  35233 DBObserver.getInstance().loadDocument(this, record, document);
 925  35233 return new Entry(key, document, entryMeta);
 926    } else {
 927  0 if (log.isTraceEnabled()) {
 928  0 log.trace(localDebugHeader + "Binary document");
 929    }
 930   
 931  0 if (cache != null) {
 932  0 cache.putEntry(this, key, Entry.BINARY, value, entryMeta);
 933    }
 934   
 935  0 return new Entry(key, value.getData(), entryMeta);
 936    }
 937    }
 938    }
 939   
 940    /**
 941    * getDocument retrieves a Document by Key.
 942    *
 943    * @param key The Document Key
 944    * @return The Document
 945    * @throws DBException if operation failed
 946    */
 947  6429 public final Document getDocument(Object key) throws DBException {
 948  6429 if (log.isDebugEnabled()) {
 949  0 log.debug(debugHeader() + "Get document: " + key);
 950    }
 951   
 952  6429 Entry entry = getEntry(key);
 953  6429 if (entry == null) {
 954  3515 return null;
 955    }
 956   
 957  2914 if (entry.getEntryType() != Entry.DOCUMENT) {
 958  0 throw new DBException(FaultCodes.COL_INVALID_RESULT,
 959    "Resource '" + key + "' in collection '" +
 960    getCanonicalName() + "' is not a document");
 961    }
 962   
 963  2914 return (Document) entry.getValue();
 964    }
 965   
 966    /**
 967    * getObject instantiates and returns an XMLSerializable object based on the
 968    * provided Key. Xindice takes care of instantiating the correct class, but
 969    * only if a class was registered with the Document in the first place.
 970    *
 971    * @param key The Document Key
 972    * @return an Castable XMLSerializable Instance
 973    * @throws DBException if operation failed
 974    */
 975  6162 public final XMLSerializable getObject(Object key) throws DBException {
 976  6162 if (log.isDebugEnabled()) {
 977  0 log.debug(debugHeader() + "Get object: " + key);
 978    }
 979   
 980  6162 Document doc = getDocument(key);
 981  6162 if (doc != null) {
 982  2658 String className = null;
 983  2658 NodeList childNodes = doc.getChildNodes();
 984  2658 int size = childNodes.getLength();
 985  2658 for (int i = 0; i < size; i++) {
 986  2658 Node n = childNodes.item(i);
 987  2658 if (n.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE && n.getNodeName().equals(CLASSNAME)) {
 988  2658 className = n.getNodeValue().trim();
 989  2658 break;
 990    }
 991    }
 992   
 993  2658 if (className != null) {
 994  2658 try {
 995  2658 XMLSerializable obj = (XMLSerializable) Class.forName(className).newInstance();
 996  2658 obj.streamFromXML(doc.getDocumentElement());
 997  2658 return obj;
 998    } catch (Exception e) {
 999  0 if (log.isWarnEnabled()) {
 1000  0 log.warn("ignored exception", e);
 1001    }
 1002    }
 1003    }
 1004    }
 1005   
 1006  3504 return null;
 1007    }
 1008   
 1009    /**
 1010    * Retrieve a binary database entry by key.
 1011    * This low-level method will not update non-inline metadata.
 1012    *
 1013    * @param key identifying the desired database entry
 1014    * @return byte[] containing the binary database entry
 1015    * @throws DBException if inline-metadata is not enabled
 1016    * (binary resource cannot be stored in a collection
 1017    * which does not have inline-metadata enabled),
 1018    * in case of backing store error, and in case of
 1019    * header corruption
 1020    */
 1021  4 public final byte[] getBinary(Object key) throws DBException {
 1022  4 if (log.isTraceEnabled()) {
 1023  0 log.trace(debugHeader() + "Get binary: " + key);
 1024    }
 1025   
 1026  4 if (inlineMetaService == null) {
 1027  0 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
 1028    "Collection '" + getCanonicalName() +
 1029    "' has no binary resources (inline metadata is not enabled)");
 1030    }
 1031   
 1032  4 Entry entry = getEntry(key);
 1033  4 if (entry == null) {
 1034  1 return null;
 1035    }
 1036   
 1037  3 if (entry.getEntryType() != Entry.BINARY) {
 1038  0 throw new DBException(FaultCodes.COL_INVALID_RESULT,
 1039    "Resource '" + key + "' in collection '" +
 1040    getCanonicalName() + "' is not a binary resource");
 1041    }
 1042   
 1043  3 return (byte[]) entry.getValue();
 1044    }
 1045   
 1046    /**
 1047    * insertDocument inserts a new Document into a Xindice Collection.
 1048    *
 1049    * @param document The Document
 1050    * @return The new Object Identifier
 1051    * @throws DBException if operation failed
 1052    */
 1053  1 public final Key insertDocument(Document document) throws DBException {
 1054  1 return insertDocument(null, document);
 1055    }
 1056   
 1057    /**
 1058    * insertDocument inserts a new Document into a Xindice Collection.
 1059    *
 1060    * @param docKey The document Key
 1061    * @param document The document to insert
 1062    * @return key for inserted document
 1063    * @throws DBException if operation failed
 1064    */
 1065  309 public final Key insertDocument(Object docKey, Document document) throws DBException {
 1066  309 Key key = createNewKey(docKey);
 1067  309 if (log.isInfoEnabled()) {
 1068  2 log.info(debugHeader() + "Insert document: " + key);
 1069    }
 1070  309 putDocument(key, document, ACTION_INSERT);
 1071   
 1072    // update the meta information if necessary
 1073  304 updateCollectionMeta();
 1074  304 return key;
 1075    }
 1076   
 1077    /**
 1078    * insertObject inserts an XMLSerializable object into the Collection and
 1079    * returns a newly generated Key. Xindice takes care of associating the
 1080    * implementation class with the XMLSerializable object.
 1081    *
 1082    * @param obj The Object to insert
 1083    * @return The newly generated Key
 1084    * @throws DBException if operation failed
 1085    */
 1086  0 public final Key insertObject(XMLSerializable obj) throws DBException {
 1087  0 return insertObject(null, obj);
 1088    }
 1089   
 1090    /**
 1091    * insertObject inserts an XMLSerializable object into the Collection based
 1092    * on the specified Key. Xindice takes care of associating the
 1093    * implementation class with the XMLSerializable object.
 1094    *
 1095    * @param objKey The Key to use
 1096    * @param obj The Object to insert
 1097    * @return key for the inserted object
 1098    * @throws DBException if operation failed
 1099    */
 1100  0 public final Key insertObject(String objKey, XMLSerializable obj) throws DBException {
 1101  0 Key key = createNewKey(objKey);
 1102  0 if (log.isInfoEnabled()) {
 1103  0 log.info(debugHeader() + "Insert object: " + key);
 1104    }
 1105  0 putObject(key, obj, ACTION_INSERT);
 1106   
 1107    // update the meta information if necessary
 1108  0 updateCollectionMeta();
 1109  0 return key;
 1110    }
 1111   
 1112    /**
 1113    * Insert a binary object into a Xindice Collection. A unique key
 1114    * is automatically generated. by which the binary object can be
 1115    * retrieved in the future. Note: because the key is automatically
 1116    * unique, this insert method will never cause a collision with an
 1117    * object already in the database.
 1118    *
 1119    * @param bytes The bytes making up the binary object to insert
 1120    * @return Key automatically generated for the binary object
 1121    * @throws DBException if inline-metadata is not enabled, or an
 1122    * error occurs while saving.
 1123    */
 1124  0 public Key insertBinary(byte[] bytes) throws DBException {
 1125  0 return insertBinary(null, bytes);
 1126    }
 1127   
 1128    /**
 1129    * insertBinary inserts a new binary object into a Xindice Collection.
 1130    *
 1131    * @param docKey The resource Key
 1132    * @param bytes The resource to insert
 1133    * @return key for the inserted binary
 1134    * @throws DBException if inline-metadata is not enabled, the key is
 1135    * already in the database, or an error occurs while saving.
 1136    */
 1137  4 public Key insertBinary(Object docKey, byte[] bytes) throws DBException {
 1138  4 if (inlineMetaService == null) {
 1139  0 throw new DBException(FaultCodes.COL_CANNOT_STORE,
 1140    "Cannot insert a binary resource in '" + getCanonicalName() +
 1141    "' (inline-metadata is not enabled)");
 1142    }
 1143   
 1144  4 Key key = createNewKey(docKey);
 1145  4 if (log.isInfoEnabled()) {
 1146  0 log.info(debugHeader() + "Insert binary: " + key);
 1147    }
 1148  4 putBinary(key, bytes, ACTION_INSERT);
 1149   
 1150    // update the meta information if necessary
 1151  4 updateCollectionMeta();
 1152  4 return key;
 1153    }
 1154   
 1155    /**
 1156    * setDocument inserts or updates an existing Document in a
 1157    * Xindice Collection.
 1158    *
 1159    * @param docKey The Document Key
 1160    * @param document The Document
 1161    * @return True if new document entry was created, false otherwise
 1162    * @throws DBException if operation failed
 1163    */
 1164  4020 public final boolean setDocument(Object docKey, Document document) throws DBException {
 1165  4020 if (log.isInfoEnabled()) {
 1166  971 log.info(debugHeader() + "Set document " + docKey);
 1167    }
 1168   
 1169  4020 boolean res = putDocument(createNewKey(docKey), document, ACTION_STORE);
 1170  4020 if (res) {
 1171  1636 updateCollectionMeta();
 1172    }
 1173   
 1174  4020 return res;
 1175    }
 1176   
 1177    /**
 1178    * setObject sets an XMLSerializable object in the Collection based on the
 1179    * provided Key. Xindice takes care of associating the implementation class
 1180    * with the XMLSerializable object.
 1181    *
 1182    * @param key The Key to use
 1183    * @param obj The Object to set
 1184    * @throws DBException if operation failed
 1185    */
 1186  5869 public final void setObject(Object key, XMLSerializable obj) throws DBException {
 1187  5869 if (log.isInfoEnabled()) {
 1188  1339 log.info(debugHeader() + "Set object " + key);
 1189    }
 1190  5869 putObject(createNewKey(key), obj, ACTION_STORE);
 1191    }
 1192   
 1193    /**
 1194    * setBinary inserts or updates binary object into a Xindice Collection.
 1195    *
 1196    * @param docKey The resource Key
 1197    * @param bytes The resource to store
 1198    * @return true if new binary was created, false otherwise
 1199    * @throws DBException if inline-metadata is not enabled, the key is
 1200    * already in the database, or an error occurs while saving.
 1201    */
 1202  16 public boolean setBinary(Object docKey, byte[] bytes) throws DBException {
 1203  16 if (inlineMetaService == null) {
 1204  0 throw new DBException(FaultCodes.COL_CANNOT_STORE,
 1205    "Cannot insert a binary resource in '" + getCanonicalName() +
 1206    "' (inline-metadata is not enabled)");
 1207    }
 1208   
 1209  16 if (log.isInfoEnabled()) {
 1210  4 log.info(debugHeader() + "Set binary " + docKey);
 1211    }
 1212   
 1213  16 boolean res = putBinary(createNewKey(docKey), bytes, ACTION_STORE);
 1214  16 if (res) {
 1215  13 updateCollectionMeta();
 1216    }
 1217   
 1218  16 return res;
 1219    }
 1220   
 1221    /**
 1222    * updateDocument updates existing Document in a Xindice Collection.
 1223    *
 1224    * @param docKey The document Key
 1225    * @param document The document to update
 1226    * @throws DBException if the key does not exist in the database, or an
 1227    * error occurs while saving.
 1228    */
 1229  6 public void updateDocument(Object docKey, Document document) throws DBException {
 1230  6 if (log.isInfoEnabled()) {
 1231  2 log.info(debugHeader() + "Update document: " + docKey);
 1232    }
 1233  6 putDocument(createNewKey(docKey), document, ACTION_UPDATE);
 1234   
 1235    // update the meta information if necessary
 1236  3 updateCollectionMeta();
 1237    }
 1238   
 1239    /**
 1240    * updateDocument updates existing binary object in a Xindice Collection.
 1241    *
 1242    * @param docKey The resource Key
 1243    * @param bytes The resource to update
 1244    * @throws DBException if inline-metadata is not enabled, the key does not
 1245    * exist in the database, or an error occurs while saving.
 1246    */
 1247  0 public void updateBinary(Object docKey, byte[] bytes) throws DBException {
 1248  0 if (inlineMetaService == null) {
 1249  0 throw new DBException(FaultCodes.COL_CANNOT_STORE,
 1250    "Cannot insert a binary resource in '" + getCanonicalName() +
 1251    "' (inline-metadata is not enabled)");
 1252    }
 1253   
 1254  0 if (log.isInfoEnabled()) {
 1255  0 log.info(debugHeader() + "Set binary " + docKey);
 1256    }
 1257   
 1258  0 putBinary(createNewKey(docKey), bytes, ACTION_UPDATE);
 1259  0 updateCollectionMeta();
 1260    }
 1261   
 1262    /**
 1263    * This is the lowest-level method for storing a record into the backing store.
 1264    * It now does update non-inline metadata if the user has configured it.
 1265    *
 1266    * <p>putDocument attempts to perform requested action, and success depends on
 1267    * action and presense of the key in the collection.
 1268    *
 1269    * @param key Entry key
 1270    * @param document Document to store
 1271    * @param action It can be either ACTION_INSERT, ACTION_UPDATE or ACTION_STORE
 1272    * @return True if new document entry was created, false otherwise
 1273    * @throws DBException<ul>
 1274    * <li>FaultCodes.COL_DUPLICATE_RESOURCE If entry with that key already present in
 1275    * collection and action is ACTION_INSERT</li>
 1276    * <li>FaultCodes.COL_DOCUMENT_NOT_FOUND If entry with that key is not present in
 1277    * collection and action is ACTION_UPDATE
 1278    * </ul>
 1279    */
 1280  10204 private boolean putDocument(Key key, Document document, byte action) throws DBException {
 1281  10204 final String localDebugHeader = debugHeader() + "putDocument: docKey=<" + key + ">: ";
 1282  10204 if (log.isTraceEnabled()) {
 1283  0 log.trace(localDebugHeader + "document=<" + TextWriter.toString(document) + ">");
 1284    }
 1285   
 1286  10204 checkFiler(FaultCodes.COL_NO_FILER);
 1287   
 1288  10204 if (document instanceof DBDocument) {
 1289    // FIXME: This is a shitty shitty hack... Kill immediately
 1290  9286 DBDocument dbDoc = (DBDocument) document;
 1291  9286 if (dbDoc.getSource() == null) {
 1292  7621 dbDoc.setSource(new NodeSource(this, key));
 1293    }
 1294    }
 1295   
 1296    /*
 1297    * The possibilities are restricted because only XML
 1298    * is handled by this method. There are only a few
 1299    * pieces of information that need to be constructed:
 1300    * 1) the xindice DOM document is needed for all XML objects, as
 1301    * it is handed to the IndexManager and the DBObserver.
 1302    * 2) the packed document, if this is a compressed XML object,
 1303    * is needed for the cache and the BTree (via the Value object).
 1304    * 3) the string-converted-to-utf-8 bytes, if this is a non-compressed
 1305    * XML object, is needed for the BTree (via the Value object).
 1306    * 4) A Value object, with a header if headers are enabled, and
 1307    * otherwise without headers, for the BTree.
 1308    */
 1309   
 1310  10204 byte[] documentBytes;
 1311   
 1312  10204 if (compressed) {
 1313    // Create compressed document bytes to be stored in the filer
 1314  7424 documentBytes = DOMCompressor.compress(document, symbols);
 1315  7424 if (log.isTraceEnabled()) {
 1316  0 log.trace(localDebugHeader + "length=" + documentBytes.length);
 1317    }
 1318   
 1319    // Create xindice document with just compressed bytes.
 1320    // Passed in document might not necessarily be xindice document,
 1321    // but we should be passing only our documents to index manager.
 1322  7424 document = new DocumentImpl(documentBytes, symbols, new NodeSource(this, key));
 1323   
 1324  7424 if (log.isTraceEnabled()) {
 1325  0 log.trace(localDebugHeader + "packedDocument: length=" + documentBytes.length +
 1326    " document=<" + TextWriter.toString(document) + ">");
 1327    }
 1328    } else {
 1329    // Create uncompressed document bytes to be stored in the filer
 1330  2780 String documentChars = TextWriter.toString(document);
 1331  2780 try {
 1332  2780 documentBytes = documentChars.getBytes("utf-8");
 1333    } catch (UnsupportedEncodingException e) {
 1334    // Should never happen
 1335  0 throw new DBException(FaultCodes.GEN_FATAL_ERROR,
 1336    "utf-8 encoding not supported", e);
 1337    }
 1338   
 1339    // Create xindice document from the string.
 1340    // In addition to converting passed document to xindice document
 1341    // instance, parseDocument() also updates the symbol table,
 1342    // if necessary.
 1343  2780 document = parseDocument(key, documentChars);
 1344   
 1345  2780 if (log.isTraceEnabled()) {
 1346  0 log.trace(localDebugHeader + "utf8Document: length=" + documentBytes.length +
 1347    " document=<" + documentChars + ">");
 1348    }
 1349    }
 1350   
 1351    // Symbol table could have been updated above, flush it to the disk.
 1352  10204 flushSymbolTable();
 1353   
 1354  10204 key = getIdentityKey(key);
 1355  10204 Entry entry;
 1356  10204 synchronized (key) {
 1357    // Temporary until insert and update are separate
 1358  10204 entry = getEntry(key);
 1359   
 1360  10204 if (action == ACTION_INSERT && entry != null) {
 1361  5 throw new DBException(FaultCodes.COL_DUPLICATE_RESOURCE);
 1362  10199 } else if (action == ACTION_UPDATE && entry == null) {
 1363  3 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND);
 1364    }
 1365   
 1366  10196 if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
 1367  4980 indexManager.removeDocument(key, (Document) entry.getValue());
 1368    }
 1369  10196 indexManager.addDocument(key, document);
 1370   
 1371    // Construct the Value object and store in the filer.
 1372  10196 Record record = writeRecord(key, true, documentBytes);
 1373   
 1374    // Cache Stuff
 1375  10196 if (cache != null) {
 1376  10196 cache.putEntry(this, key, Entry.DOCUMENT,
 1377    new Value(documentBytes), Entry.createMetaMap(record));
 1378    }
 1379   
 1380    // Update the meta for this document
 1381  10196 updateDocumentMeta(record);
 1382    }
 1383   
 1384  10196 DBObserver.getInstance().putDocument(this, key, document, entry == null);
 1385  10196 return entry == null;
 1386    }
 1387   
 1388  5869 private boolean putObject(Key key, XMLSerializable obj, byte action) throws DBException {
 1389  5869 if (log.isTraceEnabled()) {
 1390  0 log.trace(debugHeader() + "putObject: key=<" + key + "> class=<" + obj.getClass().getName() + ">");
 1391    }
 1392   
 1393  5869 Document doc = new DocumentImpl();
 1394  5869 ProcessingInstruction pi = doc.createProcessingInstruction(CLASSNAME, obj.getClass().getName());
 1395  5869 doc.appendChild(pi);
 1396  5869 Element elem = obj.streamToXML(doc);
 1397  5869 doc.appendChild(elem);
 1398  5869 return putDocument(key, doc, action);
 1399    }
 1400   
 1401    /**
 1402    * Lowest-level method for saving a binary entry into the database. At this moment,
 1403    * presence of inline metadata is known.
 1404    * It now does update non-inline metadata if the user has configured it.
 1405    * <br/><br/>
 1406    * putBinary attempts to perform requested action, and success depends on action
 1407    * and presense of the key in the collection.
 1408    * <br/><br/>
 1409    * @param key Entry key
 1410    * @param bytes Value
 1411    * @param action It can be either ACTION_INSERT, ACTION_UPDATE or ACTION_STORE
 1412    * @return True if new binary entry was created, false otherwise
 1413    * @throws DBException<ul>
 1414    * <li>FaultCodes.COL_DUPLICATE_RESOURCE If entry with that key already present in
 1415    * collection and action is ACTION_INSERT</li>
 1416    * <li>FaultCodes.COL_DOCUMENT_NOT_FOUND If entry with that key is not present in
 1417    * collection and action is ACTION_UPDATE
 1418    * </ul>
 1419    */
 1420  20 private boolean putBinary(Key key, byte[] bytes, byte action) throws DBException {
 1421  20 Entry entry;
 1422   
 1423  20 synchronized (getIdentityKey(key)) {
 1424  20 entry = getEntry(key);
 1425  20 if (action == ACTION_INSERT && entry != null) {
 1426  0 throw new DBException(FaultCodes.COL_DUPLICATE_RESOURCE,
 1427    "Error inserting binary resource '" + key + "' in '" + getCanonicalName() +
 1428    "': key is already in database");
 1429  20 } else if (action == ACTION_UPDATE && entry == null) {
 1430  0 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
 1431    "Error updating binary resource '" + key + "' in '" + getCanonicalName() +
 1432    "': key does not exist in database");
 1433    }
 1434   
 1435  20 if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
 1436    // binary resources aren't stored in indexes
 1437  2 indexManager.removeDocument(key, (Document) entry.getValue());
 1438    }
 1439   
 1440    // Construct the Value object and store in the filer.
 1441  20 Record record = writeRecord(key, false, bytes);
 1442   
 1443  20 if (cache != null) {
 1444  20 cache.putEntry(this, key, Entry.BINARY, new Value(bytes), Entry.createMetaMap(record));
 1445    }
 1446   
 1447    // update the meta for this document
 1448  20 updateDocumentMeta(record);
 1449    }
 1450   
 1451  20 return entry == null;
 1452    }
 1453   
 1454    /**
 1455    * remove removes an object from the Collection based on its Key,
 1456    * regardless of it's type.
 1457    *
 1458    * @param id The Object's Key
 1459    * @throws DBException if operation failed
 1460    */
 1461  1959 public final void remove(Object id) throws DBException {
 1462  1959 if (log.isInfoEnabled()) {
 1463  556 log.info(debugHeader() + "Remove " + id);
 1464    }
 1465   
 1466  1959 checkFiler(FaultCodes.COL_NO_FILER);
 1467   
 1468  1959 Key objKey = getIdentityKey(createNewKey(id));
 1469  1959 synchronized (objKey) {
 1470  1959 Entry entry = getEntry(objKey);
 1471  1959 if (entry != null && entry.getEntryType() == Entry.DOCUMENT) {
 1472  1718 indexManager.removeDocument(objKey, (Document) entry.getValue());
 1473    }
 1474   
 1475  1959 if (cache != null) {
 1476  1959 cache.removeEntry(this, objKey);
 1477    }
 1478   
 1479  1959 if (!filer.deleteRecord(objKey)) {
 1480  234 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
 1481    "Resource '" + objKey + "' does not exist in '" + getCanonicalName() + "'");
 1482    }
 1483   
 1484    // remove the document meta
 1485  1725 if (isMetaEnabled()) {
 1486  1724 getMetaSystemCollection().dropDocumentMeta(this, objKey.toString());
 1487    }
 1488    }
 1489   
 1490    // update the meta for this collection if necessary
 1491  1725 updateCollectionMeta();
 1492   
 1493  1725 DBObserver.getInstance().dropDocument(this, objKey);
 1494    }
 1495   
 1496   
 1497    // -- Core Collection API Public Methods: Meta Data Management ----------
 1498   
 1499    /**
 1500    * Returns whether or not meta data is enabled.
 1501    *
 1502    * @return boolean whether or not meta data is enabled.
 1503    */
 1504  17813 public boolean isMetaEnabled() {
 1505  17813 return getDatabase().isMetaEnabled();
 1506    }
 1507   
 1508    /**
 1509    * Return the MetaData for this collection.
 1510    *
 1511    * If metadata is not enabled in the configuration, the MetaData object
 1512    * returned will be null.
 1513    *
 1514    * @return MetaData this collection's metadata.
 1515    * @throws DBException if operation failed
 1516    */
 1517  39 public MetaData getCollectionMeta() throws DBException {
 1518  39 if (!isMetaEnabled()) {
 1519  0 if (log.isWarnEnabled()) {
 1520  0 log.warn("Meta information requested but not enabled in config!");
 1521    }
 1522  0 return null;
 1523    }
 1524   
 1525  39 MetaSystemCollection metacol = getMetaSystemCollection();
 1526  39 MetaData meta = metacol.getCollectionMeta(this);
 1527  39 if (null == meta) {
 1528  0 long now = System.currentTimeMillis();
 1529  0 meta = new MetaData(MetaData.COLLECTION, getCanonicalName(), now, now);
 1530  0 metacol.setCollectionMeta(this, meta);
 1531    }
 1532   
 1533  39 return meta;
 1534    }
 1535   
 1536    /**
 1537    * Reset the metadata object for this collection.
 1538    *
 1539    * @param meta the Metadata to use
 1540    * @throws DBException if operation failed
 1541    */
 1542  6 public void setCollectionMeta(MetaData meta) throws DBException {
 1543  6 if (!isMetaEnabled()) {
 1544  0 if (log.isWarnEnabled()) {
 1545  0 log.warn("Meta information requested but not enabled in config!");
 1546    }
 1547  0 return;
 1548    }
 1549   
 1550  6 if (null != meta) {
 1551  6 if (meta.getType() != MetaData.COLLECTION) {
 1552  0 throw new DBException(FaultCodes.GEN_UNKNOWN,
 1553    "Mismatch type of meta data for collection " + getCanonicalName());
 1554    }
 1555   
 1556  6 MetaSystemCollection metacol = getMetaSystemCollection();
 1557  6 MetaData current = metacol.getCollectionMeta(this);
 1558  6 current.copyDataFrom(meta);
 1559  6 metacol.setCollectionMeta(this, current);
 1560    }
 1561    }
 1562   
 1563    /**
 1564    * Retrieve a database entry metadata by key.
 1565    *
 1566    * If no matching entry is found, null is returned, otherwise this method
 1567    * return Entry that holds metadata only.
 1568    *
 1569    * @param id identifying the desired database entry
 1570    * @return Entry containing the metadata of the database entry, or null if no
 1571    * matching entry is found
 1572    * @throws DBException in case of backing store error,
 1573    * and in case of header corruption
 1574    */
 1575  49 public final Entry getEntryMeta(Object id) throws DBException {
 1576  49 if (id == null) {
 1577  0 return null;
 1578    }
 1579   
 1580  49 checkFiler(FaultCodes.COL_NO_FILER);
 1581   
 1582  49 Key key = getIdentityKey(createNewKey(id));
 1583  49 synchronized (key) {
 1584    /*
 1585    * If the key has a corresponding value in the cache, return it
 1586    * and save a disk access.
 1587    */
 1588  49 if (cache != null) {
 1589  49 Entry entry = cache.getEntryMeta(this, key);
 1590  49 if (entry != null) {
 1591  49 return entry;
 1592    }
 1593    }
 1594   
 1595  0 Record record = filer.readRecord(key, true);
 1596  0 if (record == null) {
 1597  0 return null;
 1598    }
 1599   
 1600  0 Map entryMeta = Entry.createMetaMap(record);
 1601  0 return new Entry(key, entryMeta);
 1602    }
 1603    }
 1604   
 1605    /**
 1606    * Return the MetaData object for a document within this collection.
 1607    * If metadata is not enabled, the MetaData object returned will be null.
 1608    *
 1609    * @param id the document whose metadata you want
 1610    * @return meta data for requested resource
 1611    * @throws DBException if operation failed
 1612    */
 1613  37 public MetaData getDocumentMeta(String id) throws DBException {
 1614  37 if (!isMetaEnabled()) {
 1615  0 if (log.isWarnEnabled()) {
 1616  0 log.warn("Meta information requested but not enabled in config!");
 1617    }
 1618  0 return null;
 1619    }
 1620   
 1621  37 Key key = getIdentityKey(createNewKey(id));
 1622  37 synchronized (key) {
 1623  37 if (getEntryMeta(id) == null) {
 1624  0 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
 1625    "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
 1626    }
 1627   
 1628  37 MetaSystemCollection metacol = getMetaSystemCollection();
 1629  37 MetaData meta = metacol.getDocumentMeta(this, id);
 1630   
 1631    /*
 1632    FIXME It is more efficient to store (and retrieve) created/modified timestamps
 1633    from the Record itself instead of storing them in the separate MetaData
 1634    object. Storing in the Record avoids writing two documents on each update
 1635    (Document itself and its MetaData).
 1636    Retrieval of the timestamps from Record can be implemented via TimeRecord.
 1637   
 1638    TimeRecord rec = null;
 1639    if( null == meta || !meta.hasContext() )
 1640    rec = getDatabase().getTime(path);
 1641   
 1642    long created = (null != rec) ? rec.getCreatedTime() : System.currentTimeMillis();
 1643    long modified = (null != rec) ? rec.getModifiedTime() : System.currentTimeMillis();
 1644    */
 1645   
 1646    // this is wrong.. but it should work for now...
 1647  37 long now = System.currentTimeMillis();
 1648  37 if (meta == null) {
 1649  0 meta = new MetaData(MetaData.DOCUMENT, getCanonicalDocumentName(id), now, now);
 1650  0 metacol.setDocumentMeta(this, id, meta);
 1651  37 } else if (!meta.hasContext()) {
 1652  0 meta.setContext(now, now);
 1653    }
 1654   
 1655  37 return meta;
 1656    }
 1657    }
 1658   
 1659    /**
 1660    * Set the metadata associated with a document within this collection.
 1661    *
 1662    * @param id the document name
 1663    * @param meta the metadata object to be used.
 1664    * @throws DBException if operation failed
 1665    */
 1666  12 public void setDocumentMeta(String id, MetaData meta) throws DBException {
 1667  12 if (!isMetaEnabled()) {
 1668  0 if (log.isWarnEnabled()) {
 1669  0 log.warn("Meta information requested but not enabled in config!");
 1670    }
 1671  0 return;
 1672    }
 1673   
 1674  12 Key key = getIdentityKey(createNewKey(id));
 1675  12 synchronized (key) {
 1676  12 if (getEntryMeta(id) == null) {
 1677  0 throw new DBException(FaultCodes.COL_DOCUMENT_NOT_FOUND,
 1678    "Resource '" + id + "' does not exist in '" + getCanonicalName() + "'");
 1679    }
 1680   
 1681  12 if (meta != null) {
 1682  12 if (meta.getType() == MetaData.UNKNOWN || meta.getType() == MetaData.COLLECTION) {
 1683  0 throw new DBException(FaultCodes.GEN_UNKNOWN,
 1684    "Mismatch type of meta data for document " + getCanonicalDocumentName(id));
 1685    }
 1686   
 1687  12 if (log.isInfoEnabled()) {
 1688  4 log.info(debugHeader() + "Set document meta " + id);
 1689    }
 1690  12 MetaSystemCollection metacol = getMetaSystemCollection();
 1691  12 MetaData current = metacol.getDocumentMeta(this, id);
 1692  12 current.copyDataFrom(meta);
 1693  12 metacol.setDocumentMeta(this, id, current);
 1694    }
 1695    }
 1696    }
 1697   
 1698    /**
 1699    * update the modified time of this collection when appropriate
 1700    */
 1701  4718 protected void updateCollectionMeta() {
 1702    // update the meta data if necessary
 1703  4718 if (isMetaEnabled()) {
 1704  4711 MetaSystemCollection metacol = getMetaSystemCollection();
 1705  4711 MetaData meta;
 1706  4711 try {
 1707  4711 meta = metacol.getCollectionMeta(this);
 1708    } catch (DBException e) {
 1709    // something strange has happened.. can't get the
 1710    // meta data for this collection
 1711  0 if (log.isWarnEnabled()) {
 1712  0 log.warn("Error fetching meta for collection '" + getCanonicalName() + "'", e);
 1713    }
 1714  0 return;
 1715    }
 1716   
 1717  4711 if (log.isTraceEnabled()) {
 1718  0 log.trace(debugHeader() + "Updating modified time for collection");
 1719    }
 1720  4711 long now = System.currentTimeMillis();
 1721  4711 if (null == meta) {
 1722  1784 meta = new MetaData(MetaData.COLLECTION, getCanonicalName(), now, now);
 1723  2927 } else if (!meta.hasContext()) {
 1724    // Newly created meta. Update its created and modified time.
 1725  517 meta.setContext(now, now);
 1726    } else {
 1727    // This collection already has a meta. So update its modified time.
 1728  2410 meta.setContext(0, now);
 1729    }
 1730   
 1731  4711 try {
 1732  4711 metacol.setCollectionMeta(this, meta);
 1733    } catch (DBException e) {
 1734  0 if (log.isWarnEnabled()) {
 1735  0 log.warn("Error setting meta for collection '" + getCanonicalName() + "'", e);
 1736    }
 1737    }
 1738    }
 1739    }
 1740   
 1741    /**
 1742    * update the modified time of this document when appropriate
 1743    *
 1744    * @param record database record for which metadata should be updated
 1745    * @throws DBException if operation failed
 1746    */
 1747  10216 protected void updateDocumentMeta(Record record) throws DBException {
 1748    // update the meta data if necessary
 1749  10216 if (!isMetaEnabled()) {
 1750  12 return;
 1751    }
 1752   
 1753  10204 MetaSystemCollection metacol = getMetaSystemCollection();
 1754  10204 String id = record.getKey().toString();
 1755  10204 MetaData meta = metacol.getDocumentMeta(this, id);
 1756  10204 String path = getCanonicalDocumentName(id);
 1757   
 1758  10204 if (log.isTraceEnabled()) {
 1759  0 log.trace(debugHeader() + "Updating modified time for document '" + id + "'");
 1760    }
 1761   
 1762  10204 long now = System.currentTimeMillis();
 1763    // Creation and modification time are not necessary will be available.
 1764    // Some filers may not have that information, in which case current time is used
 1765  10204 Long time = (Long) record.getMetaData(Record.CREATED);
 1766  10204 long created = time != null ? time.longValue() : now;
 1767   
 1768  10204 time = (Long) record.getMetaData(Record.MODIFIED);
 1769  10204 long modified = time != null ? time.longValue() : now;
 1770   
 1771  10204 if (null == meta) {
 1772  8221 meta = new MetaData(MetaData.DOCUMENT, path, created, modified);
 1773  1983 } else if (!meta.hasContext()) {
 1774  1948 meta.setContext(created, modified);
 1775    } else {
 1776  35 meta.setContext(0, modified);
 1777    }
 1778   
 1779  10204 metacol.setDocumentMeta(this, id, meta);
 1780    }
 1781   
 1782   
 1783    // ----------------------------------------------------------------------
 1784   
 1785    /**
 1786    * getContainer retrieves a Container from the Collection. The Container
 1787    * encapsulates all information needed in dealing with a Document outside
 1788    * of the context of a Collection (ex: DocumentContext).
 1789    *
 1790    * @param docKey The Document Key
 1791    * @return The Container
 1792    * @throws DBException if operation failed
 1793    */
 1794  0 public final Container getContainer(Object docKey) throws DBException {
 1795  0 Key key = createNewKey(docKey);
 1796  0 Document doc = getDocument(key);
 1797  0 return doc != null ? new ColContainer(key, doc) : null;
 1798    }
 1799   
 1800    /**
 1801    * getDocumentCount returns the count of Documents being maintained
 1802    * by this Collection.
 1803    *
 1804    * @return The Document count
 1805    * @throws DBException if operation failed
 1806    */
 1807  68 public final long getDocumentCount() throws DBException {
 1808    // a collection in which you are unable to file documents will have no filer
 1809    // (for example the root collection). Rather than throwing an exception return
 1810    // a constant result (nothing)
 1811  68 return null == filer ? 0 : filer.getRecordCount();
 1812    }
 1813   
 1814    /**
 1815    * getDocumentSet returns the set of Documents being maintained
 1816    * by this Collection.
 1817    *
 1818    * @return The DocumentSet
 1819    * @throws DBException if operation failed
 1820    */
 1821  0 public final DocumentSet getDocumentSet() throws DBException {
 1822    // a collection in which you are unable to file documents will have no filer
 1823    // (for example the root collection). Rather than throwing an exception return
 1824    // a constant result (nothing)
 1825  0 return null == filer ? EMPTY_DOCUMENTSET : new ColDocumentSet(filer.getRecordSet());
 1826    }
 1827   
 1828    /**
 1829    * listDocuments returns a list of all entry keys stored by this
 1830    * collection.
 1831    *
 1832    * @return the list of entry keys
 1833    * @throws DBException if operation failed
 1834    */
 1835  30 public final String[] listDocuments() throws DBException {
 1836    // a collection in which you are unable to file documents will have no filer
 1837    // (for example the root collection). Rather than throwing an exception return
 1838    // a constant result (nothing)
 1839  30 if (null == filer) {
 1840  0 return EMPTY_STRING_ARRAY;
 1841    } else {
 1842    // TODO: ArrayList length is limited to the int, while filer record count is long
 1843   
 1844    // give a hint to the size of the record set, saves on arraylist array copies.
 1845  30 ArrayList temp = new ArrayList((int) filer.getRecordCount());
 1846   
 1847  30 RecordSet set = filer.getRecordSet();
 1848  30 while (set.hasMoreRecords()) {
 1849  220 Key key = set.getNextKey();
 1850  220 temp.add(key.toString());
 1851    }
 1852   
 1853  30 return (String[]) temp.toArray(new String[temp.size()]);
 1854    }
 1855    }
 1856   
 1857    /**
 1858    * queryCollection performs a query against the current collection
 1859    * using the specified style and query String.
 1860    *
 1861    * @param style The query style to use (ex: XPath)
 1862    * @param query The query to execute
 1863    * @param nsMap The namespace Map (if any)
 1864    * @return The resulting NodeSet
 1865    * @throws DBException if operation failed
 1866    */
 1867  366 public final NodeSet queryCollection(String style, String query, NamespaceMap nsMap) throws DBException {
 1868  366 if (log.isDebugEnabled()) {
 1869  0 log.debug(debugHeader() + "Query collection, query " + query);
 1870    }
 1871   
 1872    // A collection in which you are unable to file documents will have no filer
 1873    // (for example the root collection). Rather than throwing an exception return
 1874    // a constant result (nothing)
 1875  366 return null == filer ? EMPTY_NODESET : getQueryEngine().query(this, style, query, nsMap, null);
 1876    }
 1877   
 1878    /**
 1879    * queryDocument performs a query against a single Document using
 1880    * the specified style, query string, and Document ID.
 1881    *
 1882    * @param style The query style to use (ex: XPath)
 1883    * @param query The query to execute
 1884    * @param nsMap The namespace Map (if any)
 1885    * @param key The Document to query
 1886    * @return The resulting NodeSet
 1887    * @throws DBException if operation failed
 1888    */
 1889  6 public final NodeSet queryDocument(String style, String query, NamespaceMap nsMap, Object key) throws DBException {
 1890  6 if (log.isInfoEnabled()) {
 1891  2 log.info(debugHeader() + "Query document " + key + ", query: " + query);
 1892    }
 1893   
 1894  6 checkFiler(FaultCodes.QRY_STYLE_NOT_FOUND);
 1895  6 Key[] k;
 1896  6 if (key instanceof Key[]) {
 1897  0 k = (Key[]) key;
 1898    } else {
 1899  6 k = new Key[]{createNewKey(key)};
 1900    }
 1901  6 return getQueryEngine().query(this, style, query, nsMap, k);
 1902    }
 1903    }