You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by pi...@apache.org on 2004/11/08 01:35:39 UTC

svn commit: rev 56886 - in cocoon/whiteboard/kernel/sources/contracts/repository: . src src/org src/org/apache src/org/apache/cocoon src/org/apache/cocoon/components src/org/apache/cocoon/components/repository

Author: pier
Date: Sun Nov  7 16:35:38 2004
New Revision: 56886

Added:
   cocoon/whiteboard/kernel/sources/contracts/repository/
   cocoon/whiteboard/kernel/sources/contracts/repository/cocoon.xml
   cocoon/whiteboard/kernel/sources/contracts/repository/src/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/AbstractRepository.java
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/BufferedDocument.java
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Document.java
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Repository.java
   cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/RepositoryListener.java
Log:
Adding sample "repository" block

Added: cocoon/whiteboard/kernel/sources/contracts/repository/cocoon.xml
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/cocoon.xml	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<contract xmlns="http://apache.org/cocoon/kernel/descriptor/1.0"
+    id="http://cocoon.apache.org/kernel/contracts/repository/1.0">
+
+  <exposes interface="org.apache.cocoon.contracts.repository.Repository"/>
+
+</contract>
\ No newline at end of file

Added: cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/AbstractRepository.java
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/AbstractRepository.java	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,256 @@
+/* =============================================================================== *
+ * Copyright (C) 1999-2004, The Apache Software Foundation.   All rights reserved. *
+ *                                                                                 *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use *
+ * this file except in  compliance with the License.  You may obtain a copy of the *
+ * License at <http://www.apache.org/licenses/LICENSE-2.0>.                        *
+ *                                                                                 *
+ * Unless required by applicable law or agreed to in writing, software distributed *
+ * under the License  is distributed on an  "AS IS"  BASIS,  WITHOUT WARRANTIES OR *
+ * CONDITIONS  OF ANY KIND,  either express  or implied.  See the License  for the *
+ * specific language governing permissions and limitations under the License.      *
+ * =============================================================================== */
+package org.apache.cocoon.components.repository;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>A basic implementation of the {@link Repository} interface.</p> 
+ *
+ * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
+ * @author Copyright &copy; 2000-2004 <a href="http://www.apache.org/">The Apache
+ *         Software Foundation</a>. All rights reserved.
+ */
+public abstract class AbstractRepository implements Repository {
+
+    private Set listeners = null;
+    private Log logger = null;
+
+    /**
+     * <p>Create a new {@link AbstractRepository} instance.</p>
+     */
+    protected AbstractRepository() {
+        this.listeners = Collections.synchronizedSet(new HashSet());
+        this.logger = LogFactory.getLog(this.getClass());
+    }
+
+    /* =========================================================================== */
+    /* ABSTRACT METHODS                                                            */
+    /* =========================================================================== */
+
+    public abstract Document retrieve(String identifier)
+    throws IOException;
+
+    public abstract Iterator documents();
+
+    /* =========================================================================== */
+    /* OVERRIDABLE METHODS                                                         */
+    /* =========================================================================== */
+
+    /**
+     * <p>Store the specified {@link Document} in this {@link Repository}
+     * associating it with the specified unique identifier.</p>
+     * 
+     * <p>The default {@link AbstractRepository} implementation of this method
+     * always throws an {@link UnsupportedOperationException}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @param document the {@link Document} to store.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred retrieving the {@link Document}.
+     */
+    protected void doStore(String identifier, Document document)
+    throws UnsupportedOperationException, IOException {
+        throw new UnsupportedOperationException("Store operation not supported");
+    }
+
+    /**
+     * <p>Delete the specified {@link Document} from this {@link Repository}.</p>
+     * 
+     * <p>The default {@link AbstractRepository} implementation of this method
+     * always throws an {@link UnsupportedOperationException}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred removing the {@link Document}.
+     */
+    protected void doDelete(String identifier)
+    throws UnsupportedOperationException, IOException {
+        throw new UnsupportedOperationException("Delete operation not supported");
+    }
+
+    /* =========================================================================== */
+    /* PROTECTED METHODS                                                           */
+    /* =========================================================================== */
+
+    /**
+     * <p>Return the currently configured {@link Log} instance.</p> 
+     *
+     * @return a <b>non null</b> {@link Log} instance.
+     */
+    protected Log getLogger() {
+        return this.logger;
+    }
+
+    /**
+     * <p>Return an {@link Iterator} over all known {@link RepositoryListener}s.</p>
+     * 
+     * @return a <b>non null</b> {@link Iterator} instance.
+     */
+    protected Iterator getRepositoryListeners() {
+        return this.listeners.iterator();
+    }
+
+    /* =========================================================================== */
+    /* IMPLEMENTED METHODS                                                         */
+    /* =========================================================================== */
+
+    /**
+     * <p>Check if a {@link Document} is contained in this {@link Repository}.</p>
+     * 
+     * <p>The default {@link AbstractRepository} implementation of this method
+     * is equivalent to:</p>
+     * 
+     * <p><code>return&nbsp;(this.retrieve(identifier)&nbsp;!=&nbsp;null)</code></p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @return <b>true</b> if this {@link Repository} contains a {@link Document}
+     *         associated with the specified identifier.
+     */
+    public boolean contains(String identifier) {
+        if (identifier == null) return false;
+        try {
+            return (this.retrieve(identifier) != null);
+        } catch (IOException exception) {
+            return (false);
+        }
+    }
+
+    /**
+     * <p>Store the specified {@link Document} in this {@link Repository}
+     * associating it with the specified unique identifier.</p>
+     * 
+     * <p>If another {@link Document} existed with the same identifier, their
+     * contents will be compared and the {@link #doStore(String, Document)} method
+     * (and consequently all {@link RepositoryListener} notification) will be
+     * invoked <b>only</b> if the old and new contents differ.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @param document the {@link Document} to store.
+     * @return a copy of the previously stored {@link Document} or <b>null</b> if
+     *         the {@link Document} did not exist in this {@link Repository}.
+     * @throws NullPointerException if one of the parameters was <b>null</b>.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred retrieving the {@link Document}.
+     * @throws SAXException if a SAX error occurred processing the content.
+     */
+    public Document store(String identifier, Document document)
+    throws UnsupportedOperationException, IOException, SAXException {
+        if (identifier == null) throw new NullPointerException("Null identifier");
+        if (document == null) throw new NullPointerException("Null document");
+        Log log = this.getLogger();
+        
+        /* Create an in-memory representation of the old version for checks */
+        Document previous = this.retrieve(identifier);
+        if (previous != null) {
+            Date lastmodified = previous.getLastModified();
+            BufferedDocument buffered = new BufferedDocument(lastmodified);
+            previous.process(buffered.read());
+            previous = buffered;
+
+            /* If no modifications were introduced, well, forget about it */
+            if (buffered.compare(document)) {
+                log.debug("Not storing unmodified document \"" + identifier + "\"");
+                return document;
+            }
+        }
+
+        /* Store locally this document */
+        String message = (previous == null ? "new" : "modified");
+        log.debug("Storing " + message + " document \"" + identifier + "\"");
+        this.doStore(identifier, document);
+
+        /* Notify all listeners */
+        RepositoryListener list[] = new RepositoryListener[this.listeners.size()];
+        list = (RepositoryListener[]) this.listeners.toArray(list);
+        for (int x = 0; x < list.length; x ++) try {
+            list[x].documentModified(this, identifier, previous);
+        } catch (Throwable throwable) {
+            log.error("Exception thrown notifying listener", throwable);
+        }
+
+        /* All done */
+        return previous;
+    }
+
+    /**
+     * <p>Delete the specified {@link Document} from this {@link Repository}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @return a copy of the deleted {@link Document} or <b>null</b> if
+     *         the {@link Document} did not exist in this {@link Repository}.
+     * @throws NullPointerException if one of the parameters was <b>null</b>.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred removing the {@link Document}.
+     * @throws SAXException if a SAX error occurred processing the content.
+     */
+    public Document delete(String identifier)
+    throws UnsupportedOperationException, IOException, SAXException {
+        if (identifier == null) throw new NullPointerException("Null identifier");
+        Log log = this.getLogger();
+        
+        /* Create an in-memory representation of the old version */
+        Document previous = this.retrieve(identifier);
+        if (previous == null) return null;
+
+        Date lastmodified = previous.getLastModified();
+        BufferedDocument buffered = new BufferedDocument(lastmodified);
+        previous.process(buffered.read());
+        previous = buffered;
+
+        /* Delete locally the document */
+        log.debug("Deleting document \"" + identifier + "\"");
+        this.doDelete(identifier);
+
+        /* Notify all listeners */
+        RepositoryListener list[] = new RepositoryListener[this.listeners.size()];
+        list = (RepositoryListener[]) this.listeners.toArray(list);
+        for (int x = 0; x < list.length; x ++) try {
+            list[x].documentDeleted(this, identifier, previous);
+        } catch (Throwable throwable) {
+            log.error("Exception thrown notifying listener", throwable);
+        }
+
+        /* All done */
+        return previous;
+    }
+
+    /**
+     * <p>Register a new {@link RepositoryListener} listening to events generated
+     * by this {@link Repository}.</p>
+     * 
+     * @param listener the {@link RepositoryListener} to be added.
+     */
+    public synchronized void addRepositoryListener(RepositoryListener listener) {
+        this.listeners.add(listener);
+    }
+
+    /**
+     * <p>De-register a previously registered {@link RepositoryListener}.</p>
+     * 
+     * @param listener the {@link RepositoryListener} to be removed.
+     */
+    public synchronized void removeRepositoryListener(RepositoryListener listener) {
+        this.listeners.remove(listener);
+    }
+
+}

Added: cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/BufferedDocument.java
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/BufferedDocument.java	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,539 @@
+/* =============================================================================== *
+ * Copyright (C) 1999-2004, The Apache Software Foundation.   All rights reserved. *
+ *                                                                                 *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use *
+ * this file except in  compliance with the License.  You may obtain a copy of the *
+ * License at <http://www.apache.org/licenses/LICENSE-2.0>.                        *
+ *                                                                                 *
+ * Unless required by applicable law or agreed to in writing, software distributed *
+ * under the License  is distributed on an  "AS IS"  BASIS,  WITHOUT WARRANTIES OR *
+ * CONDITIONS  OF ANY KIND,  either express  or implied.  See the License  for the *
+ * specific language governing permissions and limitations under the License.      *
+ * =============================================================================== */
+package org.apache.cocoon.components.repository;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+import org.xml.sax.helpers.LocatorImpl;
+
+/**
+ * <p>A simple implementation of the {@link Document} interface backed by an
+ * in-memory SAX events buffer.</p> 
+ *
+ * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
+ * @author Copyright &copy; 2000-2004 <a href="http://www.apache.org/">The Apache
+ *         Software Foundation</a>. All rights reserved.
+ */
+public class BufferedDocument implements Document {
+
+    /** <p>The last modified date of this document (if known).</p> */
+    private Date lastmodified = null;
+    /** <p>The DOM node containing the full XML {@link Document}.</p> */
+    private Buffer buffer = null;
+
+    /**
+     * <p>Create a new {@link BufferedDocument} instance.</p>
+     */
+    public BufferedDocument(Date lastmodified) {
+        this.lastmodified = lastmodified;
+        this.buffer = new Buffer();
+    }
+
+    /**
+     * <p>Return the {@link Date} when this {@link Document} was last modified.</p>
+     * 
+     * @return a {@link Date} instance or <b>null</b> if the last modified date was
+     *         not known.
+     */
+    public Date getLastModified() {
+        return this.lastmodified;
+    }
+
+    /**
+     * <p>Process the contents of this {@link Document} by triggering SAX events
+     * into the specified {@link ContentHandler}.</p>
+     * 
+     * @throws IOException if an I/O error occurred processing the content.
+     * @throws SAXException if a SAX error occurred processing the content.
+     * @throws IllegalStateException if {@link #read()} was never called or its
+     *                               returned {@link ContentHandler} did not
+     *                               process the document entirely.
+     */
+    public void process(ContentHandler handler)
+    throws IOException, SAXException {
+        if (! this.buffer.isSealed()) {
+            throw new IllegalStateException("No content available");
+        }
+        if (handler == null) return;
+        this.buffer.replay(handler);
+    }
+
+    /**
+     * <p>Return a {@link ContentHandler} which can be used to build the XML
+     * content of this {@link BufferedDocument} instance.</p>
+     * 
+     * @throws IOException if an I/O error occurred processing the content.
+     * @throws SAXException if a SAX error occurred processing the content.
+     * @throws IllegalStateException if {@link #read()} was alread called and the
+     *                               returned {@link ContentHandler} already
+     *                               processed the document entirely.
+     */
+    public ContentHandler read()
+    throws SAXException {
+        if (this.buffer.isSealed()) {
+            throw new IllegalStateException("Content already read");
+        }
+        return this.buffer;
+    }
+
+    /**
+     * <p>Compare the contents of this {@link BufferedDocument} with the contents
+     * of another {@link Document} instance.</p>
+     * 
+     * @param document a {@link Document} instance whose content must be compared.
+     * @return <b>true</b> if the two {@link Document} instances have the same
+     *         content, <b>false</b> otherwise.
+     */
+    public boolean compare(Document document) {
+        if (document == this) return true;
+        if (document == null) return false;
+        
+        /* Check if we can already have access to the SAX buffer */
+        if (document instanceof BufferedDocument) {
+            return this.buffer.equals(((BufferedDocument)document).buffer);
+        }
+
+        /* Create a new buffer and compare */
+        try {
+            Buffer buffer = new Buffer();
+            document.process(buffer);
+            return this.buffer.equals(buffer);
+        } catch (Exception exception) {
+            return false;
+        }
+    }
+
+    /* =========================================================================== */
+    /* SAX BUFFER IMPLEMENTATION                                                   */
+    /* =========================================================================== */
+
+    private class Buffer implements ContentHandler {
+
+        private Locator locator = null;
+        private List tokens = null;
+        private boolean sealed = false;
+
+        /**
+         * <p>Create a new {@link Buffer} instance.</p>
+         */
+        public Buffer() {
+            this.tokens = new ArrayList();
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            if (! this.sealed) throw new IllegalStateException("Buffer not sealed");
+
+            TokenLocator locator = new TokenLocator();
+            handler.setDocumentLocator(locator);
+
+            Iterator iterator = this.tokens.iterator();
+            while (iterator.hasNext()) {
+                Token token = (Token) iterator.next();
+                locator.setToken(token);
+                token.replay(handler);
+            }
+        }
+        
+        public boolean isSealed() {
+            return this.sealed;
+        }
+
+        public boolean equals(Object object) {
+            if (! this.sealed) throw new IllegalStateException("Buffer not sealed");
+            if (object == this) return true;
+            if (object == null) return false;
+            if (!(object instanceof Buffer)) return false;
+
+            /* Check the buffer status */
+            Buffer buffer = (Buffer) object;
+            if (!buffer.isSealed()) return(false);
+
+            /* Create copies of the two tokens arrays */
+            ArrayList list1 = new ArrayList(this.tokens);
+            ArrayList list2 = new ArrayList(buffer.tokens);
+
+            /* Strip out ignorable whitespace (well, it's ignorable) */
+            Iterator iterator = list1.iterator();
+            while (iterator.hasNext()) {
+                Token token = (Token) iterator.next();
+                if (token instanceof IgnorableWhitespace) iterator.remove();
+            }
+            iterator = list2.iterator();
+            while (iterator.hasNext()) {
+                Token token = (Token) iterator.next();
+                if (token instanceof IgnorableWhitespace) iterator.remove();
+            }
+
+            /* Default list comparation */
+            return(list1.equals(list2));
+        }
+
+        public int hashCode() {
+            if (! this.sealed) throw new IllegalStateException("Buffer not sealed");
+            return (this.tokens.hashCode());
+        }
+
+        /* ======================================================================= */
+        /* CONTENTHANDLER METHODS                                                  */
+        /* ======================================================================= */
+
+        public void setDocumentLocator(Locator locator) {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.locator = locator;
+        }
+
+        public void startDocument()
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new StartDocument(this.locator));
+        }
+
+        public void endDocument()
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new EndDocument(this.locator));
+            this.sealed = true;
+        }
+
+        public void startPrefixMapping(String prefix, String uri)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new StartPrefixMapping(this.locator, prefix, uri));
+        }
+
+        public void endPrefixMapping(String prefix)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new EndPrefixMapping(this.locator, prefix));
+        }
+
+        public void startElement(String uri, String loc, String qlf, Attributes att)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new StartElement(this.locator, uri, loc, qlf, att));
+        }
+
+        public void endElement(String uri, String loc, String qlf)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new EndElement(this.locator, uri, loc, qlf));
+        }
+
+        public void characters(char buf[], int off, int len)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            String value = new String(buf, off, len);
+            int last = this.tokens.size() - 1;
+            Token token = (Token)this.tokens.get(last);
+            if (token instanceof Characters) {
+                value = token.values[0] + value;
+                this.tokens.remove(last);
+            }
+            this.tokens.add(new Characters(this.locator, value));
+        }
+
+        public void ignorableWhitespace(char buf[], int off, int len)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            
+            String value = new String(buf, off, len);
+            int last = this.tokens.size() - 1;
+            Token token = (Token)this.tokens.get(last);
+            if (token instanceof IgnorableWhitespace) {
+                value = token.values[0] + value; 
+                this.tokens.remove(last);
+            }
+            this.tokens.add(new IgnorableWhitespace(this.locator, value));
+        }
+
+        public void processingInstruction(String target, String data)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new ProcessingInstruction(this.locator, target, data));
+        }
+
+        public void skippedEntity(String name)
+        throws SAXException {
+            if (this.sealed) throw new IllegalStateException("Buffer sealed");
+            this.tokens.add(new SkippedEntity(this.locator, name));
+        }
+    }
+
+    /* =========================================================================== */
+    /* SAX TOKENS IMPLEMENTATION                                                   */
+    /* =========================================================================== */
+
+    private static abstract class Token extends LocatorImpl {
+        
+        protected String values[] = null;
+        private int hash = 0;
+
+        private Token(Locator locator, String values[]) {
+            if (locator != null) {
+                this.setPublicId(locator.getPublicId());
+                this.setSystemId(locator.getSystemId());
+                this.setLineNumber(locator.getLineNumber());
+                this.setColumnNumber(locator.getColumnNumber());
+            }
+
+            this.values = (values == null ? new String[0] : values);
+            this.hash = 31 + this.getClass().hashCode(); 
+            for (int x = 0; x < this.values.length; x ++) {
+                String val = this.values[x];
+                this.hash = 31 * this.hash + (val == null ? 0 : val.hashCode());
+            }
+        }
+
+        public int hashCode() {
+            return(this.hash);
+        }
+
+        public boolean equals(Object object) {
+            if (object == this) return true;
+            if (object == null) return false;
+            if (! this.getClass().equals(object.getClass())) return false;
+            
+            Token token = (Token) object;
+            if (this.values.length != token.values.length) return false;
+            for (int x = 0; x < this.values.length; x ++) {
+                String v1 = this.values[x];
+                String v2 = token.values[x];
+                if (v1 == null ? v2 == null : v1.equals(v2)) continue;
+                return(false);
+            }
+            return(true);
+        }
+
+        public abstract void replay(ContentHandler handler)
+        throws SAXException;
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class StartDocument extends Token {
+
+        private StartDocument(Locator locator) {
+            super(locator, null);
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.startDocument();
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class EndDocument extends Token {
+
+        private EndDocument(Locator locator) {
+            super(locator, null);
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.endDocument();
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class StartPrefixMapping extends Token {
+
+        private StartPrefixMapping(Locator locator, String prefix, String uri) {
+            super(locator, new String [] { prefix, uri });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.startPrefixMapping(this.values[0], this.values[1]);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class EndPrefixMapping extends Token {
+
+        private EndPrefixMapping(Locator locator, String prefix) {
+            super(locator, new String [] { prefix });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.endPrefixMapping(this.values[0]);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class StartElement extends Token {
+
+        private Attributes att = null;
+
+        private StartElement(Locator locator, String uri, String loc, String qlf, Attributes att) {
+            super(locator, new String [] { uri, loc, qlf });
+            this.att = (att == null ? new AttributesImpl() : new AttributesImpl(att));
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.startElement(this.values[0], this.values[1], this.values[2], this.att);
+        }
+
+        public boolean equals(Object object) {
+            if (! super.equals(object)) return false;
+
+            StartElement k = (StartElement) object;
+            Attributes a = k.att;
+            if (a.getLength() != this.att.getLength()) return false;
+            for (int x = 0; x < a.getLength(); x++) {
+                String u = a.getURI(x);
+                String l = a.getLocalName(x);
+                String q = a.getQName(x);
+                String t1 = a.getType(x);
+                String v1 = a.getValue(x);
+                int z = this.att.getIndex(q);
+                if (z < 0) z = this.att.getIndex(u, l);
+                if (z < 0) return (false);
+                String t2 = this.att.getType(z);
+                String v2 = this.att.getValue(z);
+                boolean t = (t1 == null ? t2 == null : t1.equals(t2));
+                boolean v = (v1 == null ? v2 == null : v1.equals(v2));
+                if (t && v) continue;
+                return(false);
+            }
+            return(true);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class EndElement extends Token {
+
+        private EndElement(Locator locator, String uri, String loc, String qlf) {
+            super(locator, new String [] { uri, loc, qlf } );
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.endElement(this.values[0], this.values[1], this.values[2]);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class Characters extends Token {
+
+        private Characters(Locator locator, String value) {
+            super(locator, new String [] { value });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            if (this.values[0] == null) return;
+            char data[] = this.values[0].toCharArray(); 
+            handler.characters(data, 0, data.length);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class IgnorableWhitespace extends Token {
+
+        private IgnorableWhitespace(Locator locator, String value) {
+            super(locator, new String [] { value });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            if (this.values[0] == null) return;
+            char data[] = this.values[0].toCharArray(); 
+            handler.ignorableWhitespace(data, 0, data.length);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class ProcessingInstruction extends Token {
+
+        private ProcessingInstruction(Locator locator, String target, String data) {
+            super(locator, new String [] { target, data });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.processingInstruction(this.values[0], this.values[1]);
+        }
+    }
+
+    /* --------------------------------------------------------------------------- */
+
+    private static final class SkippedEntity extends Token {
+
+        private SkippedEntity(Locator locator, String name) {
+            super(locator, new String [] { name });
+        }
+
+        public void replay(ContentHandler handler)
+        throws SAXException {
+            handler.skippedEntity(this.values[0]);
+        }
+    }
+
+    /* =========================================================================== */
+    /* LOCATOR IMPLEMENTATION                                                      */
+    /* =========================================================================== */
+
+    private static class TokenLocator implements Locator {
+        
+        private Token token = null;
+        
+        private TokenLocator() {
+            super();
+        }
+
+        public String getPublicId() {
+            if (this.token == null) return null;
+            return this.token.getPublicId();
+        }
+
+        public String getSystemId() {
+            if (this.token == null) return null;
+            return this.token.getSystemId();
+        }
+
+        public int getLineNumber() {
+            if (this.token == null) return -1;
+            return this.token.getLineNumber();
+        }
+
+        public int getColumnNumber() {
+            if (this.token == null) return -1;
+            return this.token.getColumnNumber();
+        }
+        
+        protected void setToken(Token token) {
+            this.token = token;
+        }
+    }
+}

Added: cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Document.java
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Document.java	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,49 @@
+/* =============================================================================== *
+ * Copyright (C) 1999-2004, The Apache Software Foundation.   All rights reserved. *
+ *                                                                                 *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use *
+ * this file except in  compliance with the License.  You may obtain a copy of the *
+ * License at <http://www.apache.org/licenses/LICENSE-2.0>.                        *
+ *                                                                                 *
+ * Unless required by applicable law or agreed to in writing, software distributed *
+ * under the License  is distributed on an  "AS IS"  BASIS,  WITHOUT WARRANTIES OR *
+ * CONDITIONS  OF ANY KIND,  either express  or implied.  See the License  for the *
+ * specific language governing permissions and limitations under the License.      *
+ * =============================================================================== */
+package org.apache.cocoon.components.repository;
+
+import java.io.IOException;
+import java.util.Date;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+/**
+ * <p>The {@link Document} interface abstracts the concept of a simple XML document
+ * contained in a {@link Repository}.</p> 
+ *
+ * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
+ * @author Copyright &copy; 2000-2004 <a href="http://www.apache.org/">The Apache
+ *         Software Foundation</a>. All rights reserved.
+ */
+public interface Document {
+    
+    /**
+     * <p>Return the {@link Date} when this {@link Document} was last modified.</p>
+     * 
+     * @return a {@link Date} instance or <b>null</b> if the last modified date was
+     *         not known.
+     */
+    public Date getLastModified();
+    
+    /**
+     * <p>Process the contents of this {@link Document} by triggering SAX events
+     * into the specified {@link ContentHandler}.</p>
+     * 
+     * @throws IOException if an I/O error occurred processing the content.
+     * @throws SAXException if a SAX error occurred processing the content.
+     */
+    public void process(ContentHandler handler)
+    throws IOException, SAXException;
+
+}

Added: cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Repository.java
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/Repository.java	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,99 @@
+/* =============================================================================== *
+ * Copyright (C) 1999-2004, The Apache Software Foundation.   All rights reserved. *
+ *                                                                                 *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use *
+ * this file except in  compliance with the License.  You may obtain a copy of the *
+ * License at <http://www.apache.org/licenses/LICENSE-2.0>.                        *
+ *                                                                                 *
+ * Unless required by applicable law or agreed to in writing, software distributed *
+ * under the License  is distributed on an  "AS IS"  BASIS,  WITHOUT WARRANTIES OR *
+ * CONDITIONS  OF ANY KIND,  either express  or implied.  See the License  for the *
+ * specific language governing permissions and limitations under the License.      *
+ * =============================================================================== */
+package org.apache.cocoon.components.repository;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+import org.xml.sax.SAXException;
+
+/**
+ * <p>The {@link Repository} interface describes a simple object providing access to
+ * a collection of {@link Document}s.</p> 
+ *
+ * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
+ * @author Copyright &copy; 2000-2004 <a href="http://www.apache.org/">The Apache
+ *         Software Foundation</a>. All rights reserved.
+ */
+public interface Repository {
+
+    /**
+     * <p>Check if a {@link Document} is contained in this {@link Repository}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @return <b>true</b> if this {@link Repository} contains a {@link Document}
+     *         associated with the specified identifier.
+     */
+    public boolean contains(String identifier);
+
+    /**
+     * <p>Retrieve the specified {@link Document}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @return a {@link Document} instance or <b>null</b> if not found.
+     * @throws IOException if an I/O error occurred retrieving the {@link Document}.
+     */
+    public Document retrieve(String identifier)
+    throws IOException;
+
+    /**
+     * <p>Store the specified {@link Document} in this {@link Repository}
+     * associating it with the specified unique identifier.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @param document the {@link Document} to store.
+     * @return a copy of the previously stored {@link Document} or <b>null</b> if
+     *         the {@link Document} did not exist in this {@link Repository}.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred retrieving the {@link Document}.
+     * @throws SAXException if a SAX error occurred processing the content.
+     */
+    public Document store(String identifier, Document document)
+    throws UnsupportedOperationException, IOException, SAXException;
+
+    /**
+     * <p>Delete the specified {@link Document} from this {@link Repository}.</p>
+     * 
+     * @param identifier the unique identifier of the document.
+     * @return a copy of the deleted {@link Document} or <b>null</b> if
+     *         the {@link Document} did not exist in this {@link Repository}.
+     * @throws UnsupportedOperationException if this {@link Repository} is immutable.
+     * @throws IOException if an I/O error occurred removing the {@link Document}.
+     * @throws SAXException if a SAX error occurred processing the content.
+     */
+    public Document delete(String identifier)
+    throws UnsupportedOperationException, IOException, SAXException;
+
+    /**
+     * <p>Retrieve an {@link Iterator} over all {@link Document} identifiers
+     * contained in this {@link Repository}.</p>
+     * 
+     * @return a <b>non-null</b> {@link Iterator} instance.
+     */
+    public Iterator documents();
+
+    /**
+     * <p>Register a new {@link RepositoryListener} listening to events generated
+     * by this {@link Repository}.</p>
+     * 
+     * @param listener the {@link RepositoryListener} to be added.
+     */
+    public void addRepositoryListener(RepositoryListener listener);
+
+    /**
+     * <p>De-register a previously registered {@link RepositoryListener}.</p>
+     * 
+     * @param listener the {@link RepositoryListener} to be removed.
+     */
+    public void removeRepositoryListener(RepositoryListener listener);
+}

Added: cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/RepositoryListener.java
==============================================================================
--- (empty file)
+++ cocoon/whiteboard/kernel/sources/contracts/repository/src/org/apache/cocoon/components/repository/RepositoryListener.java	Sun Nov  7 16:35:38 2004
@@ -0,0 +1,52 @@
+/* =============================================================================== *
+ * Copyright (C) 1999-2004, The Apache Software Foundation.   All rights reserved. *
+ *                                                                                 *
+ * Licensed under the Apache License, Version 2.0 (the "License"). You may not use *
+ * this file except in  compliance with the License.  You may obtain a copy of the *
+ * License at <http://www.apache.org/licenses/LICENSE-2.0>.                        *
+ *                                                                                 *
+ * Unless required by applicable law or agreed to in writing, software distributed *
+ * under the License  is distributed on an  "AS IS"  BASIS,  WITHOUT WARRANTIES OR *
+ * CONDITIONS  OF ANY KIND,  either express  or implied.  See the License  for the *
+ * specific language governing permissions and limitations under the License.      *
+ * =============================================================================== */
+package org.apache.cocoon.components.repository;
+
+import java.util.EventListener;
+
+/**
+ * <p>The {@link RepositoryListener} interface describes a simple object that,
+ * once registered in the {@link Repository}, will be notified of events.</p> 
+ *
+ * @author <a href="mailto:pier@apache.org">Pier Fumagalli</a>
+ * @author Copyright &copy; 2000-2004 <a href="http://www.apache.org/">The Apache
+ *         Software Foundation</a>. All rights reserved.
+ */
+public interface RepositoryListener extends EventListener {
+
+    /**
+     * <p>Receive notification of the addition or modification of a {@link Document}
+     * in a {@link Repository}.</p>
+     *
+     * <p>If a {@link Document} was added to the repository, then the third
+     * parameter <code>previous</code> will be set to <b>null</b> otherwise it
+     * will contain an in-memory copy of the previous (overwritten) document.</p>
+     *  
+     * @param repository the {@link Repository} instance of the {@link Document}.
+     * @param identifier the unique identifier of the modified {@link Document}.
+     * @param previous an in-memory copy of the previous {@link Document} version.
+     */
+    public void documentModified(Repository repository, String identifier,
+                                 Document previous);
+
+    /**
+     * <p>Receive notification of the deletion of a {@link Document} from a 
+     * {@link Repository}.</p>
+     * 
+     * @param repository the {@link Repository} instance of the {@link Document}.
+     * @param identifier the unique identifier of the deleted {@link Document}.
+     * @param previous an in-memory copy of the deleted {@link Document}.
+     */
+    public void documentDeleted(Repository repository, String identifier,
+                                Document previous);
+}