Package org.exist.fluent

Source Code of org.exist.fluent.Folder$Event

package org.exist.fluent;

import java.io.*;
import java.util.*;

import org.exist.EXistException;
import org.exist.collections.*;
import org.exist.collections.Collection;
import org.exist.collections.triggers.TriggerException;
import org.exist.dom.*;
import org.exist.security.PermissionDeniedException;
import org.exist.storage.DBBroker;
import org.exist.storage.lock.Lock;
import org.exist.util.LockException;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.XPathException;
import org.exist.xquery.value.*;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;


/**
* <p>A named collection of XML documents in the database.  Each document belongs
* to precisely one folder.  Folders can be nested, with queries carried out
* within either the scope of a single folder or of a whole subtree.</p>
*
* <p>Though the name <code>Collection</code> is commonly used for these
* constructs in the context of XML databases, the name <code>Folder</code> was
* chosen instead to avoid conflicting with the ubiquitous Java <code>java.util.Collection</code>
* interface.</p>
*
* <p>Instances of this class are not thread-safe.</p>
*  
* @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
* @version $Revision: 1.31 $ ($Date: 2006/09/04 06:09:05 $)
*/
public class Folder extends NamedResource implements Cloneable {
 
  /**
   * Listener for events affecting folders.  The three possible actions are folder
   * creation, renaming (update), and deletion.
   *
   * <em>WARNING:</em>  as of September 1, 2005, eXist does not implement
   * folder triggers so folder listeners are ineffective.  This warning will be removed
   * when the situation is known to have been corrected.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public interface Listener extends org.exist.fluent.Listener {
    /**
     * Respond to a folder event.
     *
     * @param ev the details of the event
     */
    void handle(Folder.Event ev);
  }
 
  /**
   * An event that concerns a folder.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public static class Event extends org.exist.fluent.Listener.Event {
    /**
     * The folder that's the subject of this event.
     * Note that for some timing/action combinations, this field might be <code>null</code>.
     */
    public final Folder folder;
   
    Event(Trigger trigger, String path, Folder folder) {
      super(trigger, path);
      this.folder = folder;
    }
   
    Event(ListenerManager.EventKey key, Folder folder) {
      super(key);
      this.folder = folder;
    }
   
    @Override
    public boolean equals(Object o) {
      return
        super.equals(o) &&
        (folder == null ? ((Event) o).folder == null : folder.equals(((Event) o).folder));
    }
   
    @Override
    public int hashCode() {
      return super.hashCode() * 37 + (folder == null ? 0 : folder.hashCode());
    }
   
    @Override
    public String toString() {
      StringBuilder buf = new StringBuilder(super.toString());
      buf.insert(3, "Folder.");
      buf.insert(buf.length()-1, ", " + folder);
      return buf.toString();
    }
  }
 
  /**
   * The children (subfolders) facet of a collection.  Permits operations on subfolders,
   * both immediate and indirect.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public class ChildrenFacet implements Iterable<Folder> {
   
    private ChildrenFacet() {}

    /**
     * Return an existing descendant this of this folder, inheriting this one's namespace mappings.
     *
     * @param descendantName the relative name of the child folder to get; must not start with '/'
     * @return the descendant folder with the given name
     */
    public Folder get(String descendantName) {
      staleMarker.check();
      if (descendantName.startsWith("/")) throw new IllegalArgumentException("descendant name starts with '/': " + descendantName);
      String parentPath = path();
      if (parentPath.equals("/")) parentPath = "";
      return new Folder(parentPath + "/" + descendantName, false, Folder.this);
    }

    /**
     * Get a descendant folder of this folder, or create it if it doesn't exist.  It inherits
     * this folder's namespace mappings.
     *
     * @param descendantName the relative name of the descendant folder to create; must not start with '/'
     * @return the child folder with the given name
     */
    public Folder create(String descendantName) {
      staleMarker.check();
      if (descendantName.startsWith("/")) throw new IllegalArgumentException("descendant name starts with '/': " + descendantName);
      String parentPath = path();
      if (parentPath.equals("/")) parentPath = "";
      return new Folder(parentPath + "/" + descendantName, true, Folder.this);
    }

    /**
     * Return the number of immediate child folders of this folder.
     *
     * @return the number of child subfolders
     */
    public int size() {
                        DBBroker _broker = null;
                        try {
                            _broker = db.acquireBroker();
                            return getQuickHandle().getChildCollectionCount(_broker);
                        } catch(PermissionDeniedException pde) {
                            throw new DatabaseException(pde.getMessage(), pde);
      } finally {
                            db.releaseBroker(_broker);
      }
     
    }

    /**
     * Return whether this folder has a descendant folder with the given name.
     *
     * @param descendantName the relative name of the descendant folder; must not start with '/'
     * @return <code>true</code> if this folder has a descendant wich the given name, <code>false</code> otherwise
     */
    public boolean contains(String descendantName) {
      staleMarker.check();
      if (descendantName.startsWith("/")) throw new IllegalArgumentException("child name starts with '/': " + descendantName);
      DBBroker _broker = null;
      try {
        _broker = db.acquireBroker();
        return _broker.getCollection(XmldbURI.create(path() + "/" + descendantName)) != null;
                        } catch(PermissionDeniedException pde) {
                            throw new DatabaseException(pde.getMessage(), pde);
      } finally {
        db.releaseBroker(_broker);
      }
    }
   
    /**
     * Export these children folders to the given directory.
     * If the directory does not exist it will be created.  A subdirectory will be created
     * for each child folder.
     *
     * @param destination the destination folder to export into
     * @throws IOException if the export failed due to an I/O error
     */
    public void export(File destination) throws IOException {
      if (!destination.exists()) destination.mkdirs();
      if (!destination.isDirectory()) throw new IOException("export destination not a directory: " + destination);
      for (Folder child : this) {
        child.export(new File(destination, child.name()));
      }
    }

                public class FolderIterator implements Iterator<Folder> {
                    private Folder last;
                   
                    private Iterator<XmldbURI> delegate = null;
                    private DBBroker broker = null;
                   
                    private Iterator<XmldbURI> getDelegate() throws IllegalStateException {
                        if(delegate == null) {
                            try {
                                broker = db.acquireBroker();
                                delegate = getQuickHandle().collectionIterator(broker);
                            } catch(PermissionDeniedException pde) {
                                throw new IllegalStateException(pde.getMessage(), pde);
                            }
                        }
                       
                        return delegate;
                    }
                   
                    @Override
                    public void remove() {
                            staleMarker.check();
                            if (last == null) {
                                throw new IllegalStateException("no collection to remove");
                            }
                            last.delete();
                            last = null;
                    }
                   
                    @Override
                    public boolean hasNext() {
                            staleMarker.check();
                            boolean result = getDelegate().hasNext();
                            if(!result) {
                                db.releaseBroker(broker);
                            }
                            return result;
                    }
                   
                    @Override
                    public Folder next() {
                            staleMarker.check();
                            last = get(getDelegate().next().getCollectionPath());
                            return last;
                    }
                }
               
    /**
     * Return an iterator over the immediate child subfolders.  You can use this iterator to
     * selectively delete subfolders as well.
     *
     * @return an iterator over the child subfolders
     */
                @Override
    public Iterator<Folder> iterator() {
                    staleMarker.check();
                    return new FolderIterator();
    }
   
  }
 
  /**
   * The immediate documents facet of a folder.  Gives access to the (conceptual) set of
   * documents contained directly in a folder.  All functions will <em>not</em> consider
   * documents contained in subfolders.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public class DocumentsFacet extends Resource implements Iterable<Document> {
   
    private ListenersFacet listeners;

    /**
     * The facet that gives control over listeners for documents contained directly within
     * a folder.
     *
     * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
     */
    public class ListenersFacet {
     
      /**
       * Add a listener for all documents directly in this folder.  Equivalent to <code>add(EnumSet.of(trigger), listener)</code>.
       *
       * @see #add(Set, Document.Listener)
       * @param trigger the kind of event the listener should be notified of
       * @param listener the listener to notify of events
       */
      public void add(Trigger trigger, Document.Listener listener) {
        add(EnumSet.of(trigger), listener);
      }
     
      /**
       * Add a listener for all documents directly in this folder.
       *
       * @param triggers the kinds of events the listener should be notified of; the set must not be empty
       * @param listener the listener to notify of events
       */
      public void add(Set<Trigger> triggers, Document.Listener listener) {
        staleMarker.check();
        ListenerManager.INSTANCE.add(path(), ListenerManager.Depth.ONE, triggers, listener, Folder.this);
      }
     
      /**
       * Remove a listener previously added through this facet.  This will remove the listener from
       * all combinations of timing and action for this folder, even if added via multiple invocations
       * of the <code>add</code> methods.  However, it will not remove the listener from combinations
       * added through other facets.
       *
       * @param listener the listener to remove
       */
      public void remove(Document.Listener listener) {
        ListenerManager.INSTANCE.remove(path(), ListenerManager.Depth.ONE, listener);
      }
    }

    private DocumentsFacet() {
      super(Folder.this.namespaceBindings, Folder.this.db);
    }
   
    @Override Sequence convertToSequence() {
      staleMarker.check();
      return getDocsSequence(false);
    }
   
    /**
     * Return the facet that allows control over listenersof the folder's immediate documents.
     *
     * @return the immediate documents' listener facet
     */
    public ListenersFacet listeners() {
      if (listeners == null) listeners = new ListenersFacet();
      return listeners;
    }
   
    /**
     * Build a new XML document in this collection, with the given relative path.  Remember to {@link ElementBuilder#commit commit}
     * commit the builder when done.  If the builder doesn't commit, no document is created.
     *
     * @param name the relative path to the new document
     * @return a builder to use to create the document
     */
    public ElementBuilder<XMLDocument> build(final Name name) {
      Folder target = name.stripPathPrefix(Folder.this);
      if (target != Folder.this) return target.documents().build(name);
     
      staleMarker.check();
      return new ElementBuilder<XMLDocument>(Folder.this.namespaceBindings(), false, new ElementBuilder.CompletedCallback<XMLDocument>() {
        public XMLDocument completed(Node[] nodes) {
          assert nodes.length == 1;
          Node node = nodes[0];
          transact(Lock.WRITE_LOCK);
          try {
            name.setContext(handle);
            IndexInfo info = handle.validateXMLResource(tx.tx, broker, XmldbURI.create(name.get()), node);
            changeLock(Lock.NO_LOCK);
            handle.store(tx.tx, broker, info, node, false);
            commit();
          } catch (EXistException e) {
            throw new DatabaseException(e);
          } catch (PermissionDeniedException e) {
            throw new DatabaseException(e);
          } catch (TriggerException e) {
            throw new DatabaseException(e);
          } catch (SAXException e) {
            throw new DatabaseException(e);
          } catch (LockException e) {
            throw new DatabaseException(e);
          } catch (IOException e) {
            throw new DatabaseException(e);
          } finally {
            release();
          }
          return get(name.get()).xml();
        }
      });
    }

    /**
     * Return whether this facet's folder contains a document with the given relative path.
     *
     * @param documentPath the relative path of the document to check for
     * @return <code>true</code> if the folder contains a document with the given path, <code>false</code> otherwise
     */
    public boolean contains(String documentPath) {
      checkIsRelativeDocPath(documentPath);
      if (documentPath.contains("/")) return database().contains(Folder.this.path() + "/" + documentPath);
                       
                        DBBroker _broker = null;
                        try {
                            _broker = db.acquireBroker();
                            return getQuickHandle().getDocument(_broker, XmldbURI.create(documentPath)) != null;
                        } catch(PermissionDeniedException pde) {
                            throw new DatabaseException(pde.getMessage(), pde);
                        } finally {
                            if(_broker != null) {
                                db.releaseBroker(_broker);
                            }
                        }
     
    }

    /**
     * Create an XML document with the given relative path, takings its contents from the given source.
     *
     * @param name the desired relative path of the document
     * @param source the source of XML data to read in the document contents from; the folder's namespace bindings are <em>not</em> applied
     * @return the newly created document
     * @throws DatabaseException if anything else goes wrong
     */
    public XMLDocument load(Name name, Source.XML source) {
      Folder target = name.stripPathPrefix(Folder.this);
      if (target != Folder.this) return target.documents().load(name, source);
     
      transact(Lock.WRITE_LOCK);
      try {
        source.applyOldName(name);
        name.setContext(handle);
        IndexInfo info = handle.validateXMLResource(tx.tx, broker, XmldbURI.create(name.get()), source.toInputSource());
        changeLock(Lock.NO_LOCK);
        handle.store(tx.tx, broker, info, source.toInputSource(), false);
        commit();
      } catch (EXistException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } catch (PermissionDeniedException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } catch (TriggerException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } catch (SAXException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } catch (LockException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } catch (IOException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } finally {
        release();
      }
      return get(name.get()).xml();
    }
   
    /**
     * Create a binary document with the given relative path, takings its contents from the given source.
     *
     * @param name the desired relative path of the document
     * @param source the source to read the document contents from
     * @return the newly created document
     * @throws DatabaseException if anything else goes wrong
     */
    public Document load(Name name, Source.Blob source) {
      Folder target = name.stripPathPrefix(Folder.this);
      if (target != Folder.this) return target.documents().load(name, source);
     
      transact(Lock.WRITE_LOCK);
      try {
        source.applyOldName(name);
        name.setContext(handle);
        InputStream inputStream = source.toInputStream();
        try {
          handle.addBinaryResource(tx.tx, broker, XmldbURI.create(name.get()), inputStream, null, source.getLength());
        } catch (PermissionDeniedException e) {
          throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
        } catch (LockException e) {
          throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
        } catch (TriggerException e) {
          throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
        } catch (EXistException e) {
                                        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
                                } finally {
          inputStream.close();
        }
        commit();
      } catch (IOException e) {
        throw new DatabaseException("failed to create document '" + name + "' from source " + source, e);
      } finally {
        release();
      }
      return get(name.get());
    }
   
    /**
     * Get the contained document with the given relative path.
     *
     * @param documentPath the relative path of the document to find
     * @return the document with the given path
     * @throws DatabaseException if unable to find or access the desired document
     */
    public Document get(String documentPath) {
      checkIsRelativeDocPath(documentPath);
      if (documentPath.contains("/")) return database().getDocument(Folder.this.path() + "/" + documentPath);
     
                        DBBroker _broker = null;
                        try {
                            _broker = db.acquireBroker();
                            DocumentImpl dimpl = getQuickHandle().getDocument(_broker, XmldbURI.create(documentPath));
                            if (dimpl == null) throw new DatabaseException("no such document: " + documentPath);
                            return Document.newInstance(dimpl, Folder.this);
                        } catch(PermissionDeniedException pde) {
                            throw new DatabaseException(pde.getMessage(), pde);
                        } finally {
                            if(_broker != null) {
                                db.releaseBroker(_broker);
                            }
                        }
    }
   
    private void checkIsRelativeDocPath(String docPath) {
      if (docPath.startsWith("/")) throw new IllegalArgumentException("relative path cannot start with '/': " + docPath);
      if (docPath.endsWith("/")) throw new IllegalArgumentException("relative path cannot end with '/': " + docPath);
    }

    /**
     * Return the number of documents immediately contained in the folder.
     *
     * @return the number of child documents
     */
    public int size() {
                    DBBroker _broker = null;
                    try {
                        _broker = db.acquireBroker();
      return getQuickHandle().getDocumentCount(_broker);
                    } catch (PermissionDeniedException pde) {
                        throw new DatabaseException(pde.getMessage(), pde);
                    } finally {
                        db.releaseBroker(_broker);
                    }
    }
   
    /**
     * Export the immediately contained documents to the given directory.
     * If the directory does not exist it will be created.  Documents in this
     * folder will appear directly in the given directory.
     *
     * @param destination the destination folder to export into
     * @throws IOException if the export failed due to an I/O error
     */
    public void export(File destination) throws IOException {
      if (!destination.exists()) destination.mkdirs();
      if (!destination.isDirectory()) throw new IOException("export destination not a directory: " + destination);
      for (Document doc : this) {
        doc.export(new File(destination, doc.name()));
      }
    }

    /**
     * Query over the documents immediately contained in the folder, ignoring any documents
     * in subfolders.
     *
     * @return a query service over the folder's immediate documents
     */
    @Override public QueryService query() {
      staleMarker.check();
      return super.query();
    }
   
    @Override QueryService createQueryService() {
      return new QueryService(Folder.this) {
        @Override
        protected void prepareContext(DBBroker broker_) {
          acquire(Lock.READ_LOCK, broker_);
          try {
            docs = handle.allDocs(broker_, new DefaultDocumentSet(), false);
            baseUri = new AnyURIValue(handle.getURI());
                                        }catch (PermissionDeniedException pde) {
                                            throw new DatabaseException(pde.getMessage(), pde);
          } finally {
            release();
          }
        }
      };
    }
   
    /**
     * Return an iterator over the folder's immediate documents.  This iterator can be used
     * to selectively delete documents as well.
     *
     * @return an iterator over the folder's immediate documents
     */
    public Iterator<Document> iterator() {
      return new Iterator<Document>() {
        private Iterator<DocumentImpl> delegate;
        private Document last;
        {
          acquire(Lock.READ_LOCK);
          try {
            delegate = handle.iterator(broker);
                                        } catch(PermissionDeniedException pde) {
                                            throw new DatabaseException(pde.getMessage(), pde);
          } finally {
            release();
          }
        }
        public void remove() {
          staleMarker.check();
          if (last == null) throw new IllegalStateException("no document to remove");
          last.delete();
          last = null;
        }
        public boolean hasNext() {
          staleMarker.check();
          return delegate.hasNext();
        }
        public Document next() {
          staleMarker.check();
          last = Document.newInstance(delegate.next(), Folder.this);
          return last;
        }
      };
    }
   
  }
 
  /**
   * The facet that allwos control over listeners to the folder, and all its descendant folders and
   * documents.
   *
   * @author <a href="mailto:piotr@ideanest.com">Piotr Kaminski</a>
   */
  public class ListenersFacet {
   
    /**
     * Add a listener for either folder or document events on this folder, its contents, and all
     * its descendants and their contents as well.  Equivalent to <code>add(EnumSet.of(trigger), listener)</code>.
     *
     * @see #add(Set, org.exist.fluent.Listener)
     * @param trigger the kind of event the listener should be notified of
     * @param listener the listener to notify of events
     */
    public void add(Trigger trigger, org.exist.fluent.Listener listener) {
      add(EnumSet.of(trigger), listener);
    }
   
    /**
     * Add a listener for either folder or document events on this folder, its contents, and all
     * its descendants and their contents as well.  The listener's type (either {@link Document.Listener}
     * or {@link Folder.Listener}) will determine the kinds of events it will receive.  Note that
     * if the listener implements both interfaces it will be notified of events that concern both
     * documents and folders (and satisfy the timing and action characteristics requested).
     *
     * @param triggers the kinds of events the listener should be notified of; the set must not be empty
     * @param listener the listener to notify of events
     */
    public void add(Set<Trigger> triggers, org.exist.fluent.Listener listener) {
      staleMarker.check();
      ListenerManager.INSTANCE.add(path(), ListenerManager.Depth.MANY, triggers, listener, Folder.this);
    }
   
    /**
     * Remove a listener previously added through this facet.  This will remove the listener from
     * all combinations of timing and action for this folder, even if added via multiple invocations
     * of the <code>add</code> methods.  However, it will not remove the listener from combinations
     * added through other facets, even this folder's documents facet or the facets of any descendants.
     *
     * @param listener the listener to remove
     */
    public void remove(org.exist.fluent.Listener listener) {
      ListenerManager.INSTANCE.remove(path(), ListenerManager.Depth.MANY, listener);
    }
  }
 

 
  private String path;
  private StaleMarker staleMarker;
  private ChildrenFacet children;
  private DocumentsFacet documents;
  private ListenersFacet listeners;
  private MetadataFacet metadata;
 
  // the following are only valid while we're holding a broker
  private DBBroker broker;
  private Collection handle;
  private Transaction tx;
  private int lockMode;
  private boolean ownBroker;
 
  /**
   * Create a wrapper around the given collection.
   *
   * @param path the absolute path to the desired collection, must start with '/'
   * @param createIfMissing what to do if the given collection doesn't exist; if <code>true</code>, create it, otherwise signal an error
   * @param origin the origin, indicating the database instance and namespace bindings to be extended
   * @throws DatabaseException if the collection cannot be found and is not supposed to be created
   */
  Folder(String path, boolean createIfMissing, Resource origin) {
    this(path, createIfMissing, origin.namespaceBindings().extend(), origin.database());
  }
 
  Folder(String path, boolean createIfMissing, NamespaceMap namespaceBindings, Database db) {
    super(namespaceBindings, db);
    if (path.length() == 0 || path.charAt(0) != '/') throw new IllegalArgumentException("path must start with /, got " + path);
   
    try {
      broker = db.acquireBroker();
      Collection collection;
      if (createIfMissing) {
        tx = Database.requireTransaction();
        try {
          collection = createInternal(path);
          tx.commit();
        } finally {
          tx.abortIfIncomplete();
        }
      } else {
        try {
                                    collection = broker.getCollection(XmldbURI.create(path));
                               
                                    if (collection == null) {
                                        throw new DatabaseException("collection not found '" + path + "'");
                                    }
                                } catch(PermissionDeniedException pde) {
                                    throw new DatabaseException(pde.getMessage(), pde);
                                }
      }
      // store the normalized path, minus the root prefix if possible
      changePath(collection.getURI().getCollectionPath());
    } finally {
      db.releaseBroker(broker);
      broker = null;
      tx = null;
    }
  }
 
  private void changePath(String newPath) {
    this.path = Database.normalizePath(newPath);
    staleMarker = new StaleMarker();
    staleMarker.track(path);
  }
 
  /**
   * Return this folder's listeners facet, giving control over listeners to events on this folder,
   * its contents, and all its descendants.
   *
   * @return this folder's listeners facet
   */
  public ListenersFacet listeners() {
    if (listeners == null) listeners = new ListenersFacet();
    return listeners;
  }

  @Override public MetadataFacet metadata() {
    if (metadata == null) metadata = new MetadataFacet(getQuickHandle().getPermissionsNoLock(), db) {
      @Override public Date creationDate() {
        return new Date(getQuickHandle().getCreationTime());
      }
    };
    return metadata;
  }

  /**
   * Create a duplicate handle that copies the original's path and namespace bindings.
   * No copy is created of the underlying folder.  The namespace bindings will copy the
   * original's immediate namespace map and namespace bindings inheritance chain.
   *
   * @return a duplicate of this collection wrapper
   */
  @Override
  public Folder clone() {
    staleMarker.check();
    return new Folder(path(), false, namespaceBindings.clone(), db);
  }
 
  public Folder cloneWithoutNamespaceBindings() {
    staleMarker.check();
    return new Folder(path(), false, new NamespaceMap(), db);
  }

  @Override
  public boolean equals(Object o) {
    if (o instanceof Folder) return path().equals(((Folder) o).path());
    return false;
  }
 
  @Override
  public int hashCode() {
    return path().hashCode();
  }
 
  @Override
  public String toString() {
    return "folder '" + path() + "'";
  }
 
  private Collection createInternal(String targetPath) {
    try {
      Collection collection = broker.getOrCreateCollection(tx.tx, XmldbURI.create(targetPath));
      broker.saveCollection(tx.tx, collection);
      broker.flush();
      return collection;
    } catch (PermissionDeniedException e) {
      throw new DatabaseException(e);
    } catch (IOException e) {
      throw new DatabaseException(e);
    } catch (TriggerException e) {
      throw new DatabaseException(e);
    }
  }
 
  void transact(int _lockMode) {
    if (tx != null) throw new IllegalStateException("transaction already in progress");
    tx = Database.requireTransaction();
    acquire(_lockMode);
  }
 
  void commit() {
    if (tx == null) throw new IllegalStateException("no transaction in progress");
    tx.commit();    // later aborts will do nothing
  }
 
  void acquire(int _lockMode) {
    DBBroker _broker = db.acquireBroker();
    ownBroker = true;
    try {
      acquire(_lockMode, _broker);
    } catch (RuntimeException e) {
      db.releaseBroker(_broker);
      ownBroker = false;
      throw e;
    }
  }
 
  void acquire(int _lockMode, DBBroker _broker) {
    staleMarker.check();
    if(broker != null || handle != null) throw new IllegalStateException("broker already acquired");
    broker = _broker;
    try {
                    handle = broker.openCollection(XmldbURI.create(path()), _lockMode);
                    if(handle == null) {
                        throw new DatabaseException("collection not found '" + path + "'");
                    }
                    this.lockMode = _lockMode;
    } catch (RuntimeException e) {
                    broker = null;
                    handle = null;
                    throw e;
    } catch(PermissionDeniedException pde) {
                    broker = null;
                    handle = null;
                    throw new DatabaseException(pde.getMessage(), pde);
                }
  }
 
  void release() {
    if (broker == null || handle == null) throw new IllegalStateException("broker not acquired");
    if (tx != null) tx.abortIfIncomplete();
    if (lockMode != Lock.NO_LOCK) handle.getLock().release(lockMode);
    if (ownBroker) db.releaseBroker(broker);
    ownBroker = false;
    broker = null;
    handle = null;
    tx = null;
  }
 
  void changeLock(int newLockMode) {
    if (broker == null || handle == null) throw new IllegalStateException("broker not acquired");
    if (lockMode == newLockMode) return;
    if (lockMode == Lock.NO_LOCK) {
      try {
        handle.getLock().acquire(newLockMode);
        lockMode = newLockMode;
      } catch (LockException e) {
        throw new DatabaseException(e);
      }
    } else {
      if (newLockMode != Lock.NO_LOCK) throw new IllegalStateException("cannot change between read and write lock modes");
      handle.getLock().release(lockMode);
      lockMode = newLockMode;
    }
  }
 
  private Collection getQuickHandle() {
    acquire(Lock.NO_LOCK);
    try {
      return handle;
    } finally {
      release();
    }
  }
 
  /**
   * Return whether this folder is empty, i.e. has no documents or subfolders in it.
   *
   * @return whether this folder is empty
   */
  public boolean isEmpty() {
    acquire(Lock.NO_LOCK);
    try {
      return handle.getDocumentCount(broker) == 0 && handle.getChildCollectionCount(broker) == 0;
                } catch (PermissionDeniedException pde) {
                    throw new DatabaseException(pde.getMessage(), pde);
                } finally {
      release();
    }
               
  }
 
  /**
   * Remove all resources and subfolders from this folder, but keep the folder itself.
   */
  public void clear() {
    transact(Lock.READ_LOCK);
    try {
      if (handle.getDocumentCount(broker) == 0 && handle.getChildCollectionCount(broker) == 0) return;
      broker.removeCollection(tx.tx, handle);
      createInternal(path);
      commit();
    } catch (PermissionDeniedException e) {
      throw new DatabaseException(e);
    } catch (IOException e) {
      throw new DatabaseException(e);
    } catch (TriggerException e) {
      throw new DatabaseException(e);
    } finally {
      release();
    }
    changePath(path)// reset stale marker
  }
 
  /**
   * Delete this folder, including all documents and descendants.  If invoked on the root folder,
   * deletes all documents and descendants but does not delete the root folder itself.
   */
  @Override public void delete() {
    transact(Lock.NO_LOCK);
    try {
      // TODO: temporary hack, remove once removing root collection works in eXist
      if (path.equals("/")) {
        for (Iterator<DocumentImpl> it = handle.iterator(broker); it.hasNext();) {
          DocumentImpl dimpl = it.next();
          if (dimpl instanceof BinaryDocument) {
            handle.removeBinaryResource(tx.tx, broker, dimpl);
          } else {
            handle.removeXMLResource(tx.tx, broker, dimpl.getFileURI());
          }
        }
      }
      // end hack
      broker.removeCollection(tx.tx, handle);
      commit();
    } catch (PermissionDeniedException e) {
      throw new RuntimeException(e);
    } catch (IOException e) {
      throw new DatabaseException(e);
    } catch (LockException e) {
      throw new DatabaseException(e);
    } catch (TriggerException e) {
      throw new DatabaseException(e);
    } finally {
      release();
    }
  }
 
  /**
   * Move this folder to another spot in the folder hierarchy, possibly changing its name in the process.
   * This folder will refer to the newly relocated folder when this method returns.  You can use this
   * method to move a folder without changing its name (<code>folder.move(destFolder, Name.keepCreate())</code>)
   * or to rename it without changing its location (<code>folder.move(folder.parent(), Name.create(newName))</code>).
   *
   * @param destination the new parent folder of this folder
   * @param name the new name for this folder
   */
  @Override public void move(Folder destination, Name name) {
    changePath(moveOrCopyThisFolder(destination, name, false));
  }
 
  /**
   * Copy this folder to another place in the folder hierarchy, returning the newly copied folder
   * with namespace bindings inherited from this one.
   *
   * @param destination the desired parent folder of the copy
   * @param name the desired name of the copy
   * @return a reference to the copied folder
   */
  @Override public Folder copy(Folder destination, Name name) {
    return new Folder(moveOrCopyThisFolder(destination, name, true), false, this);
  }
 
  private String moveOrCopyThisFolder(Folder destination, Name name, boolean copy) {
    db.checkSame(destination);
    transact(Lock.WRITE_LOCK);
    try {
      destination.acquire(Lock.WRITE_LOCK, broker);
      try {
        name.setOldName(name());
        name.setContext(handle);
        if (copy) {
          broker.copyCollection(tx.tx, handle, destination.handle, XmldbURI.create(name.get()));
        } else {
          broker.moveCollection(tx.tx, handle, destination.handle, XmldbURI.create(name.get()));
        }
        commit();
      } catch (PermissionDeniedException e) {
        throw new DatabaseException(e);
      } catch (LockException e) {
        throw new DatabaseException(e);
      } catch (IOException e) {
        throw new DatabaseException(e);
      } catch (TriggerException e) {
        throw new DatabaseException(e);
      } catch (EXistException e) {
        throw new DatabaseException(e);
      } finally {
        destination.release();
      }
    } finally {
      release();
    }
    return destination.path() + "/" + name.get();
  }
 
  /**
   * Provide access to the documents facet of this folder.  The documents facet is
   * the conceptual set of documents contained directly in this collection (and therefore
   * excludes documents contained in any subfolders).
   *
   * @return the documents facet
   */
  public DocumentsFacet documents() {
    if (documents == null) documents = new DocumentsFacet();
    return documents;
  }
 
  /**
   * Return whether this folder or one of its descendants contains the given named resource.
   *
   * @param res the resource to check for
   * @return <code>true</code> if the resource is contained (directly or indirectly) in this folder, <code>false</code> otherwise
   */
  public boolean contains(NamedResource res) {
    staleMarker.check();
    db.checkSame(res);
    return res.path().startsWith(path() + (path().equals("/") ? "" : "/"));
  }
 
  /**
   * Return the given path stripped of this folder's prefix path, i.e. relative to this folder.
   * The given path can be either to a document or to a subfolder.  If the given path is to
   * this folder itself, return the empty string.
   *
   * @param subPath the path to relativize
   * @return the given path relative to this folder
   */
  public String relativePath(String subPath) {
    if (subPath.equals(path())) return "";
    if (path().equals("/") && subPath.charAt(0) == '/') return subPath.substring(1);
    if (!subPath.startsWith(path()+"/")) throw new IllegalArgumentException("path '" + subPath + "' does not fall under this collection's path '" + path() + "'");
    return subPath.substring(path().length() + 1);
  }
 
  /**
   * Return the full path to the folder; always starts with '/'.
   *
   * @return the full path to the folder
   */
  @Override public String path() {
    return path;
  }
 
  /**
   * Return the local name of the folder; this is the last segment of its path.
   *
   * @return the local name of the folder
   */
  @Override public String name() {
    return path().substring(path().lastIndexOf('/')+1);
  }
 
  /**
   * Return the parent folder of this folder.  It will inherit this folder's namespace bindings.
   *
   * @return the parent folder that this folder is a child of
   * @throws DatabaseException if this is the root folder
   */
  public Folder parent() {
    String parentPath = getQuickHandle().getURI().removeLastSegment().getCollectionPath();
    if (parentPath == null || parentPath.length() == 0) throw new DatabaseException("this is the root collection");
    return new Folder(parentPath, false, this);
  }
 
  /**
   * Return the children facet of this folder that gives access to operations on its subfolders
   * and descendants.
   *
   * @return the children facet of this folder
   */
  public ChildrenFacet children() {
    if (children == null) children = new ChildrenFacet();
    return children;
  }
 
  /**
   * Export the contents of this folder (both documents and subfolders) to the given
   * directory.  If the directory does not exist it will be created.  Documents in this
   * folder will appear directly in the given directory, i.e. a subdirectory matching this
   * folder will <em>not</em> be created.
   *
   * @param destination the destination folder to export into
   * @throws IOException if the export failed due to an I/O error
   */
  public void export(File destination) throws IOException {
    documents().export(destination);
    children().export(destination);
  }
 
  @Override Sequence convertToSequence() {
    staleMarker.check();
    return getDocsSequence(true);
  }
 
  private Sequence getDocsSequence(boolean recursive) {
    try {
      DocumentSet docs;
      acquire(Lock.READ_LOCK);
      try {
        docs = handle.allDocs(broker, new DefaultDocumentSet(), recursive);
                        } catch(PermissionDeniedException pde) {
                            throw new DatabaseException(pde.getMessage(), pde);
                        } finally {
        release();
      }
      Sequence result = new ExtArrayNodeSet(docs.getDocumentCount(), 1);
      for (Iterator<?> i = docs.getDocumentIterator(); i.hasNext();) {
        result.add(new NodeProxy((DocumentImpl) i.next()));
      }
      return result;
    } catch (XPathException e) {
      throw new RuntimeException("unexpected exception converting document set to sequence", e);
    }
  }
 
  /**
   * Return a query service for executing queries over the contents of this folder and the contents
   * of all its descendants.  If you only want to query the documents contained directly in this folder,
   * see {@link #documents() the documents facet}.
   *
   * @return a query service over this folder's contents and all its descendants' contents
   */
  @Override public QueryService query() {
    staleMarker.check();
    return super.query();
  }
 
  @Override QueryService createQueryService() {
    return new QueryService(this) {
      @Override void prepareContext(DBBroker broker_) {
        acquire(Lock.READ_LOCK, broker_);
        try {
          docs = handle.allDocs(broker_, new DefaultDocumentSet(), true);
          baseUri = new AnyURIValue(handle.getURI());
                                } catch(PermissionDeniedException pde) {
                                    throw new DatabaseException(pde.getMessage(), pde);
        } finally {
          release();
        }
      }
    };
  }
 
  void removeDocument(DocumentImpl dimpl) {
    transact(Lock.WRITE_LOCK);
    try {
      if (dimpl instanceof BinaryDocument) {
        handle.removeBinaryResource(tx.tx, broker, dimpl.getFileURI());
      } else {
        handle.removeXMLResource(tx.tx, broker, dimpl.getFileURI());
      }
      commit();
    } catch (PermissionDeniedException e) {
      throw new DatabaseException(e);
    } catch (LockException e) {
      throw new DatabaseException(e);
    } catch (TriggerException e) {
      throw new DatabaseException(e);
    } finally {
      release();
    }
  }
 
  DocumentImpl moveOrCopyDocument(DocumentImpl doc, Name name, boolean copy) {
    XmldbURI uri;
    transact(Lock.WRITE_LOCK);
    try {
      name.setContext(handle);
      uri = XmldbURI.create(name.get());
      if (copy) {
        tx.lockRead(doc);
        broker.copyResource(tx.tx, doc, handle, uri);
      } else {
        tx.lockWrite(doc);
        broker.moveResource(tx.tx, doc, handle, uri);
      }
      commit();
    } catch (PermissionDeniedException pde) {
      throw new DatabaseException(pde.getMessage(), pde);
    } catch (LockException e) {
      throw new DatabaseException("lock denied", e);
    } catch (IOException e) {
      throw new DatabaseException(e);
    } catch (TriggerException e) {
      throw new DatabaseException(e);
    } catch (EXistException e) {
      throw new DatabaseException(e);
    } finally {
      release();
    }
               
                DBBroker _broker = null;
                Collection _handle = getQuickHandle();
                try {
                     _broker = db.acquireBroker();
                    return _handle.getDocument(_broker, uri);
                } catch(PermissionDeniedException pde) {
                    throw new DatabaseException(pde.getMessage(), pde);
                } finally {
                    if(_broker != null) {
                        db.releaseBroker(_broker);
                    }
                }
                       
                   
  }
}
TOP

Related Classes of org.exist.fluent.Folder$Event

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.