
/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 Wolfgang M. Meier
 *  meier@ifs.tu-darmstadt.de
 *  http://exist.sourceforge.net
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: LocalCollection.java 4403 2006-09-27 12:27:37Z wolfgang_m $
 */
package org.exist.xmldb;

import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Properties;
import java.util.Random;

import javax.xml.transform.OutputKeys;

import org.apache.log4j.Logger;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.collections.IndexInfo;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.DocumentImpl;
import org.exist.dom.LockToken;
import org.exist.security.Permission;
import org.exist.security.PermissionDeniedException;
import org.exist.security.User;
import org.exist.security.xacml.AccessContext;
import org.exist.security.xacml.NullAccessContextException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.EXistOutputKeys;
import org.exist.storage.sync.Sync;
import org.exist.storage.txn.TransactionManager;
import org.exist.storage.txn.Txn;
import org.exist.util.LockException;
import org.exist.validation.service.LocalValidationService;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xmldb.api.base.ErrorCodes;
import org.xmldb.api.base.Resource;
import org.xmldb.api.base.Service;
import org.xmldb.api.base.XMLDBException;

/**
 *  A local implementation of the Collection interface. This
 * is used when the database is running in embedded mode.
 *
 * Extends Observable to allow status callbacks during indexing.
 * Methods storeResource notifies registered observers about the
 * progress of the indexer by passing an object of type ProgressIndicator
 * to the observer.
 *
 *@author     wolf
 */
public class LocalCollection extends Observable implements CollectionImpl {
    
    private static Logger LOG = Logger.getLogger(LocalCollection.class.getName());
    
    protected final static Properties defaultProperties = new Properties();
    static {
        defaultProperties.setProperty(OutputKeys.ENCODING, "UTF-8");
        defaultProperties.setProperty(OutputKeys.INDENT, "yes");
        defaultProperties.setProperty(EXistOutputKeys.EXPAND_XINCLUDES, "yes");
        defaultProperties.setProperty(EXistOutputKeys.PROCESS_XSL_PI, "no");
    }
    
    protected XmldbURI path = null;
    protected BrokerPool brokerPool = null;
    protected Properties properties = new Properties(defaultProperties);
    protected LocalCollection parent = null;
    protected User user = null;
    protected ArrayList observers = new ArrayList(1);
    protected boolean needsSync = false;
    
    private XMLReader userReader = null;
    
    private AccessContext accessCtx;
    
    private LocalCollection() {}
    /**
     * Create a collection with no parent (root collection).
     *
     * @param user
     * @param brokerPool
     * @param collection
     * @throws XMLDBException
     */
    public LocalCollection(User user, BrokerPool brokerPool, XmldbURI collection, AccessContext accessCtx)
    throws XMLDBException {
        this(user, brokerPool, null, collection, accessCtx);
    }
    
    /**
     * Create a collection identified by its name. Load the collection from the database.
     *
     * @param user
     * @param brokerPool
     * @param parent
     * @param name
     * @throws XMLDBException
     */
    public LocalCollection(
            User user,
            BrokerPool brokerPool,
            LocalCollection parent,
            XmldbURI name,
            AccessContext accessCtx)
            throws XMLDBException {
        if(accessCtx == null)
            throw new NullAccessContextException();
        this.accessCtx = accessCtx;
        if (user == null)
            user = new User("guest", "guest", "guest");
        this.user = user;
        this.parent = parent;
        this.brokerPool = brokerPool;
        this.path = name;
        if (path == null)
            path = XmldbURI.ROOT_COLLECTION_URI;
        path = path.toCollectionPathURI();
        getCollection();
    }
    
    public AccessContext getAccessContext() {
        return accessCtx;
    }
    
    protected Collection getCollectionWithLock(int lockMode) throws XMLDBException {
        DBBroker broker = null;
        Collection collection = null;
        try {
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, lockMode);
            if(collection == null)
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            collection.setReader(userReader);
        } catch (EXistException e) {
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e.getMessage(), e);
        } finally {
            brokerPool.release(broker);
        }
        return collection;
    }
    
    protected void saveCollection() throws XMLDBException {
        DBBroker broker = null;
        Collection collection = null;
        TransactionManager transact = brokerPool.getTransactionManager();
        Txn transaction = transact.beginTransaction();
        try {
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, Lock.WRITE_LOCK);
            if(collection == null)
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            broker.saveCollection(transaction, collection);
            transact.commit(transaction);
        } catch (EXistException e) {
            transact.abort(transaction);
            throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e.getMessage(), e);
        } catch (PermissionDeniedException e) {
            transact.abort(transaction);
            throw new XMLDBException(ErrorCodes.PERMISSION_DENIED, e.getMessage(), e);
        } finally {
            if(collection != null)
                collection.release();
            brokerPool.release(broker);
        }
    }
    
    protected Collection getCollection() throws XMLDBException {
        DBBroker broker = null;
        try {
            broker = brokerPool.get(user);
            org.exist.collections.Collection collection = broker.getCollection(path);
            if (collection == null)
                throw new XMLDBException(
                        ErrorCodes.NO_SUCH_COLLECTION,
                        "Collection " + path + " not found");
            
            collection.setReader(userReader);
            
            return collection;
        } catch (EXistException e) {
            throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
        } finally {
            brokerPool.release(broker);
        }
    }
    
    protected boolean checkOwner(Collection collection, User user) throws XMLDBException {
        return user.getName().equals(collection.getPermissions().getOwner());
    }
    
    protected boolean checkPermissions(Collection collection, int perm) throws XMLDBException {
        return collection.getPermissions().validate(user, perm);
    }
    
    /**
     * Close the current collection. Calling this method will flush all
     * open buffers to disk.
     */
    public void close() throws XMLDBException {
        if (needsSync) {
            DBBroker broker = null;
            try {
                broker = brokerPool.get(user);
                broker.sync(Sync.MAJOR_SYNC);
            } catch (EXistException e) {
                throw new XMLDBException(ErrorCodes.UNKNOWN_ERROR, e.getMessage(), e);
            } finally {
                brokerPool.release(broker);
            }
        }
    }
    
    //TODO: api change to XmldbURI?
    public String createId() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            XmldbURI id;
            Random rand = new Random();
            boolean ok;
            do {
                ok = true;
                id = XmldbURI.create(Integer.toHexString(rand.nextInt()) + ".xml");
                // check if this id does already exist
                if (collection.hasDocument(id))
                    ok = false;
                
                if (collection.hasSubcollection(id))
                    ok = false;
                
            } while (!ok);
            return id.toString();
        } finally {
            collection.getLock().release(Lock.READ_LOCK);
        }
    }
    
    //TODO: api change to XmldbURI?
    public Resource createResource(String id, String type) throws XMLDBException {
        if (id == null)
            id = createId();
        
        XmldbURI idURI;
        try {
            idURI = XmldbURI.xmldbUriFor(id);
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        Resource r = null;
        if (type.equals("XMLResource"))
            r = new LocalXMLResource(user, brokerPool, this, idURI);
        else if (type.equals("BinaryResource"))
            r = new LocalBinaryResource(user, brokerPool, this, idURI);
        else
            throw new XMLDBException(
                    ErrorCodes.INVALID_RESOURCE,
                    "unknown resource type: " + type);
        ((AbstractEXistResource)r).isNewResource = true;
        return r;
    }
    
    //TODO: api change to XmldbURI?
    public org.xmldb.api.base.Collection getChildCollection(String name) throws XMLDBException {
        XmldbURI childName = null;
        XmldbURI childURI;
        try {
            childURI = XmldbURI.xmldbUriFor(name);
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            if (!checkPermissions(collection, Permission.READ))
                throw new XMLDBException(
                        ErrorCodes.PERMISSION_DENIED,
                        "You are not allowed to access this collection");
            if(collection.hasChildCollection(childURI))
                childName = getPathURI().append(childURI);
        } finally {
            collection.release();
        }
        if(childName != null)
            return new LocalCollection(user, brokerPool, this, childName, accessCtx);
        else
            return null;
    }
    
    public int getChildCollectionCount() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            if (checkPermissions(collection, Permission.READ))
                return collection.getChildCollectionCount();
            else
                return 0;
        } finally {
            collection.getLock().release(Lock.READ_LOCK);
        }
    }
    
    //TODO: api change to XmldbURI?
    public String getName() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            return collection.getURI().toString();
        } finally {
            collection.release();
        }
    }
    
    public org.xmldb.api.base.Collection getParentCollection() throws XMLDBException {
        if (getName().equals(DBBroker.ROOT_COLLECTION))
            return null;
        if (parent == null) {
            // load the collection to check if it is valid
            DBBroker broker = null;
            Collection collection = null;
            try {
                broker = brokerPool.get(user);
                collection = broker.openCollection(path, Lock.READ_LOCK);
                if(collection == null)
                    throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
                parent = new LocalCollection(user, brokerPool, null, collection.getParentURI(), accessCtx);
            } catch (EXistException e) {
                throw new XMLDBException(
                        ErrorCodes.UNKNOWN_ERROR,
                        "error while retrieving parent collection: " + e.getMessage(),
                        e);
            } finally {
                if(collection != null)
                    collection.getLock().release(Lock.READ_LOCK);
                brokerPool.release(broker);
            }
        }
        return parent;
    }
    
    public String getPath() throws XMLDBException {
        return path.toString();
    }
    
    public XmldbURI getPathURI() {
        return path;
    }
    
    public String getProperty(String property) throws XMLDBException {
        return properties.getProperty(property);
    }
    
    public Resource getResource(String id) throws XMLDBException {
        Collection collection = null;
        DBBroker broker = null;
        XmldbURI idURI;
        try {
            idURI = XmldbURI.xmldbUriFor(id);
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        try {
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, Lock.READ_LOCK);
            if(collection == null)
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            if (!checkPermissions(collection, Permission.READ))
                throw new XMLDBException(
                        ErrorCodes.PERMISSION_DENIED,
                        "not allowed to read collection");
            DocumentImpl document = collection.getDocument(broker, idURI);
            if (document == null) {
                LOG.warn("Resource " + idURI + " not found");
                return null;
            }
            Resource r;
            if (document.getResourceType() == DocumentImpl.XML_FILE)
                r = new LocalXMLResource(user, brokerPool, this, idURI);
            else if (document.getResourceType() == DocumentImpl.BINARY_FILE)
                r = new LocalBinaryResource(user, brokerPool, this, idURI);
            else
                throw new XMLDBException(
                        ErrorCodes.INVALID_RESOURCE,
                        "unknown resource type");
            return r;
        } catch (EXistException e) {
            throw new XMLDBException(
                    ErrorCodes.UNKNOWN_ERROR,
                    "error while retrieving resource: " + e.getMessage(),
                    e);
        } finally {
            if(collection != null)
                collection.release();
            brokerPool.release(broker);
        }
    }
    
    public int getResourceCount() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            if (!checkPermissions(collection, Permission.READ))
                return 0;
            else
                return collection.getDocumentCount();
        } finally {
            collection.getLock().release(Lock.READ_LOCK);
        }
    }
    
    /** Possible services: XPathQueryService, XQueryService,
     * CollectionManagementService (CollectionManager), UserManagementService,
     * DatabaseInstanceManager, XUpdateQueryService,  IndexQueryService,
     * ValidationService. */
    public Service getService(String name, String version) throws XMLDBException {
        if (name.equals("XPathQueryService"))
            return new LocalXPathQueryService(user, brokerPool, this, accessCtx);
        
        if (name.equals("XQueryService"))
            return new LocalXPathQueryService(user, brokerPool, this, accessCtx);
        
        if (name.equals("CollectionManagementService")
        || name.equals("CollectionManager"))
            return new LocalCollectionManagementService(user, brokerPool, this, accessCtx);
        
        if (name.equals("UserManagementService"))
            return new LocalUserManagementService(user, brokerPool, this);
        
        if (name.equals("DatabaseInstanceManager"))
            return new LocalDatabaseInstanceManager(user, brokerPool);
        
        if (name.equals("XUpdateQueryService"))
            return new LocalXUpdateQueryService(user, brokerPool, this);
        
        if (name.equals("IndexQueryService"))
            return new LocalIndexQueryService(user, brokerPool, this);
        
        if (name.equals("ValidationService"))
            return new LocalValidationService(user, brokerPool, this);
        
        throw new XMLDBException(ErrorCodes.NO_SUCH_SERVICE);
    }
    
    public Service[] getServices() throws XMLDBException {
        Service[] services = new Service[7];
        services[0] = new LocalXPathQueryService(user, brokerPool, this, accessCtx);
        services[1] = new LocalCollectionManagementService(user, brokerPool, this, accessCtx);
        services[2] = new LocalUserManagementService(user, brokerPool, this);
        services[3] = new LocalDatabaseInstanceManager(user, brokerPool);
        services[4] = new LocalXUpdateQueryService(user, brokerPool, this);
        services[5] = new LocalIndexQueryService(user, brokerPool, this);
        services[6] = new LocalValidationService(user, brokerPool, this);
        return services; // jmv null;
    }
    
    public boolean isOpen() throws XMLDBException {
        return true;
    }
    
    //TODO: api change to XmldbURI?
    public String[] listChildCollections() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            if (!checkPermissions(collection, Permission.READ))
                return new String[0];
            String[] collections = new String[collection.getChildCollectionCount()];
            int j = 0;
            for (Iterator i = collection.collectionIterator(); i.hasNext(); j++)
                collections[j] = ((XmldbURI) i.next()).toString();
            return collections;
        } finally {
            collection.release();
        }
    }
    
    public String[] getChildCollections() throws XMLDBException {
        return listChildCollections();
    }
    
    public String[] listResources() throws XMLDBException {
        Collection collection = null;
        DBBroker broker = null;
        try {
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, Lock.READ_LOCK);
            if(collection == null)
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            if (!checkPermissions(collection, Permission.READ))
                return new String[0];
            
            List allresources = new ArrayList();
            DocumentImpl doc;
            for (Iterator i = collection.iterator(broker); i.hasNext(); ) {
                doc = (DocumentImpl) i.next();
                
                // Include only when (1) locktoken is present or (2)
                // locktoken indicates that it is not a null resource
                LockToken lock = doc.getMetadata().getLockToken();
                if(lock==null || (!lock.isNullResource()) ){
                    allresources.add( doc.getFileURI() );
                }
                
            }
            
            // Copy content of list into String array.
            int j=0;
            String[] resources = new String[allresources.size()];
            for(Iterator i = allresources.iterator(); i.hasNext(); j++){
                resources[j]= ((XmldbURI) i.next()).toString();
            }
            
            return resources;
        } catch (EXistException e) {
            throw new XMLDBException(
                    ErrorCodes.UNKNOWN_ERROR,
                    "error while retrieving resource: " + e.getMessage(),
                    e);
        } finally {
            if(collection != null)
                collection.release();
            brokerPool.release(broker);
        }
    }
    
    public String[] getResources() throws XMLDBException {
        return listResources();
    }
    
    public void registerService(Service serv) throws XMLDBException {
        throw new XMLDBException(ErrorCodes.NOT_IMPLEMENTED);
    }
    
    public void removeResource(Resource res) throws XMLDBException {
        if (res == null)
            return;
        XmldbURI resURI;
        try {
            resURI = XmldbURI.xmldbUriFor(res.getId());
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        Collection collection = null;
        DBBroker broker = null;
        TransactionManager transact = brokerPool.getTransactionManager();
        Txn txn = transact.beginTransaction();
        try {
            LOG.debug("removing " + resURI);
            
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, Lock.READ_LOCK);
            if(collection == null) {
                transact.abort(txn);
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            }
            DocumentImpl doc = collection.getDocument(broker, resURI);
            if (doc == null) {
                transact.abort(txn);
                throw new XMLDBException(
                        ErrorCodes.INVALID_RESOURCE,
                        "resource " + resURI + " not found");
            }
            if (res.getResourceType().equals("XMLResource"))
                collection.removeXMLResource(txn, broker, resURI);
            else
                collection.removeBinaryResource(txn, broker, resURI);
            transact.commit(txn);
        } catch (EXistException e) {
            transact.abort(txn);
            throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
        } catch (PermissionDeniedException e) {
            transact.abort(txn);
            throw new XMLDBException(ErrorCodes.PERMISSION_DENIED, e.getMessage(), e);
        } catch (TriggerException e) {
            transact.abort(txn);
            throw new XMLDBException(ErrorCodes.INVALID_RESOURCE, e.getMessage(), e);
        } catch (LockException e) {
            transact.abort(txn);
            throw new XMLDBException(ErrorCodes.VENDOR_ERROR,
                    "Failed to acquire lock on collections.dbx", e);
        } finally {
            if(collection != null)
                collection.getLock().release();
            brokerPool.release(broker);
        }
        needsSync = true;
    }
    
    public void setProperty(String property, String value) throws XMLDBException {
        properties.setProperty(property, value);
    }
    
    public void storeResource(Resource resource) throws XMLDBException {
        storeResource(resource, null, null);
    }
    
    public void storeResource(Resource resource, Date a, Date b) throws XMLDBException {
        if (resource.getResourceType().equals("XMLResource")) {
            LOG.debug("storing document " + resource.getId());
            ((LocalXMLResource)resource).datecreated =a;
            ((LocalXMLResource)resource).datemodified =b;
            storeXMLResource((LocalXMLResource) resource);
        } else if (resource.getResourceType().equals("BinaryResource")) {
            LOG.debug("storing binary resource " + resource.getId());
            ((LocalBinaryResource)resource).datecreated =a;
            ((LocalBinaryResource)resource).datemodified =b;
            storeBinaryResource((LocalBinaryResource) resource);
        } else
            throw new XMLDBException(
                    ErrorCodes.UNKNOWN_RESOURCE_TYPE,
                    "unknown resource type: " + resource.getResourceType());
        ((AbstractEXistResource)resource).isNewResource = false;
        needsSync = true;
    }
    
    private void storeBinaryResource(LocalBinaryResource res) throws XMLDBException {
        XmldbURI resURI;
        try {
            resURI = XmldbURI.xmldbUriFor(res.getId());
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        Collection collection = null;
        DBBroker broker = null;
        TransactionManager transact = brokerPool.getTransactionManager();
        Txn txn = transact.beginTransaction();
        try {
            broker = brokerPool.get(user);
            collection = broker.openCollection(path, Lock.WRITE_LOCK);
            if(collection == null) {
                transact.abort(txn);
                throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
            }
            collection.addBinaryResource(txn,
                    broker,
                    resURI,
                    (byte[]) res.getContent(),
                    res.getMimeType(), res.datecreated, res.datemodified  );
            transact.commit(txn);
        } catch (Exception e) {
            transact.abort(txn);
            throw new XMLDBException(
                    ErrorCodes.VENDOR_ERROR,
                    "Exception while storing binary resource: " + e.getMessage(),
                    e);
        } finally {
            if(collection != null)
                collection.getLock().release();
            brokerPool.release(broker);
        }
    }
    
    private void storeXMLResource(LocalXMLResource res) throws XMLDBException {
        XmldbURI resURI;
        try {
            resURI = XmldbURI.xmldbUriFor(res.getId());
        } catch(URISyntaxException e) {
            throw new XMLDBException(ErrorCodes.INVALID_URI,e);
        }
        DBBroker broker = null;
        TransactionManager transact = brokerPool.getTransactionManager();
        Txn txn = transact.beginTransaction();
        try {
            broker = brokerPool.get(user);
            String uri = null;
            if(res.file != null) uri = res.file.toURI().toASCIIString();
            IndexInfo info = null;
            Collection collection = broker.openCollection(path, Lock.WRITE_LOCK);
            try {
                if(collection == null) {
                    transact.abort(txn);
                    throw new XMLDBException(ErrorCodes.INVALID_COLLECTION, "Collection " + path + " not found");
                }
                Observer observer;
                for (Iterator i = observers.iterator(); i.hasNext();) {
                    observer = (Observer) i.next();
                    collection.addObserver(observer);
                }
                if (uri != null) {
                    setupParser(collection, res);
                    info = collection.validateXMLResource(txn, broker, resURI, new InputSource(uri));
                } else if (res.root != null)
                    info = collection.validateXMLResource(txn, broker, resURI, res.root);
                else
                    info = collection.validateXMLResource(txn, broker, resURI, res.content);
                info.getDocument().getMetadata().setMimeType(res.getMimeType());
                
                if (res.datecreated  != null)
                    info.getDocument().getMetadata().setCreated( res.datecreated.getTime());
                
                if (res.datemodified != null)
                    info.getDocument().getMetadata().setLastModified( res.datemodified.getTime());
            } finally {
                collection.release();
            }
            if (uri != null) {
                collection.store(txn, broker, info, new InputSource(uri), false);
            } else if (res.root != null) {
                collection.store(txn, broker, info, res.root, false);
            } else {
                collection.store(txn, broker, info, res.content, false);
            }
            transact.commit(txn);
            collection.deleteObservers();
        } catch (Exception e) {
            transact.abort(txn);
            LOG.debug(e);
            
            throw new XMLDBException(ErrorCodes.VENDOR_ERROR, e.getMessage(), e);
        } finally {
            brokerPool.release(broker);
        }
    }
    
    private void setupParser(Collection collection, LocalXMLResource res) throws XMLDBException {
        if ( res.getMimeType().equals("text/html") ) {
            try {
                Class clazz = Class.forName( "org.cyberneko.html.parsers.SAXParser" );
                XMLReader htmlReader = (XMLReader) clazz.newInstance();
                collection.setReader( htmlReader );
            } catch ( Exception e) {
                LOG.info("nekohtml parser not found. If you want to parse non-wellformed HTML files, put nekohtml.jar into " +
                        "directory lib/optional/");
            }
        }
    }
    
    public Date getCreationTime() throws XMLDBException {
        Collection collection = getCollectionWithLock(Lock.READ_LOCK);
        try {
            return new Date(collection.getCreationTime());
        } finally {
            collection.getLock().release();
        }
    }
    
    /**
     * Add a new observer to the list. Observers are just passed
     * on to the indexer to be notified about the indexing progress.
     */
    public void addObserver(Observer o) {
        if (!observers.contains(o))
            observers.add(o);
    }
    
    /* (non-Javadoc)
     * @see org.exist.xmldb.CollectionImpl#isRemoteCollection()
     */
    public boolean isRemoteCollection() throws XMLDBException {
        return false;
    }
    
    /** set user-defined Reader
     * @param reader
     */
    public void setReader(XMLReader reader){
        userReader = reader;
    }
    
    //You probably will have to call this methed from this cast :
    //((org.exist.xmldb.CollectionImpl)collection).getURI()
    public XmldbURI getURI() {
        StringBuffer accessor = new StringBuffer(XmldbURI.XMLDB_URI_PREFIX);
        //TODO : get the name from client
        accessor.append("exist");
        accessor.append("://");
        //No host ;-)
        accessor.append("");
        //No port ;-)
        if (-1 != -1)
            accessor.append(":").append(-1);
        //No context ;-)
        //accessor.append(getContext());
        try {
            //TODO : cache it when constructed
            return XmldbURI.create(accessor.toString(), getPath());
        } catch (XMLDBException e) {
            //TODO : should never happen
            return null;
        }
    }
}
