jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From resc...@apache.org
Subject svn commit: r1213890 - in /jackrabbit/trunk: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/ jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webd...
Date Tue, 13 Dec 2011 19:40:37 GMT
Author: reschke
Date: Tue Dec 13 19:40:36 2011
New Revision: 1213890

URL: http://svn.apache.org/viewvc?rev=1213890&view=rev
Log:
JCR-2541: spi2dav : EventJournal not implemented

Add Atom Feed based Event Journal implementation.

Added:
    jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java
    jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java
    jackrabbit/trunk/jackrabbit-jcr2dav/pom.xml
    jackrabbit/trunk/jackrabbit-spi2dav/pom.xml
    jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java
    jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
    jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ElementIterator.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/observation/ObservationManagerImpl.java Tue Dec 13 19:40:36 2011
@@ -26,6 +26,7 @@ import org.apache.jackrabbit.spi.Path;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.jcr.AccessDeniedException;
 import javax.jcr.RepositoryException;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.observation.EventJournal;
@@ -246,7 +247,7 @@ public class ObservationManagerImpl impl
         }
 
         if (!session.isAdmin()) {
-            throw new RepositoryException("Only administrator session may access EventJournal");
+            throw new AccessDeniedException("Only administrator session may access EventJournal");
         }
 
         EventFilter filter = createEventFilter(

Added: jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java?rev=1213890&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/commons/webdav/AtomFeedConstants.java Tue Dec 13 19:40:36 2011
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jackrabbit.commons.webdav;
+
+import javax.xml.namespace.QName;
+
+/**
+ * <code>AtomFeedConstants</code> provides string constants for Atom feed 
+ * (RFC 4287) resources.
+ */
+public interface AtomFeedConstants {
+
+    /**
+     * Namespace URI for RFC 4287 elements.
+     */
+    public static final String NS_URI = "http://www.w3.org/2005/Atom";
+    
+    public static final String MEDIATYPE = "application/atom+xml";
+
+    public static final String XML_AUTHOR = "author";
+    public static final String XML_CONTENT = "content";
+    public static final String XML_ENTRY = "entry";
+    public static final String XML_FEED = "feed";
+    public static final String XML_ID = "id";
+    public static final String XML_LINK = "link";
+    public static final String XML_NAME = "name";
+    public static final String XML_TITLE = "title";
+    public static final String XML_UPDATED = "updated";
+
+    public static final QName N_AUTHOR = new QName(NS_URI, XML_AUTHOR);
+    public static final QName N_CONTENT = new QName(NS_URI, XML_CONTENT);
+    public static final QName N_ENTRY = new QName(NS_URI, XML_ENTRY);
+    public static final QName N_FEED = new QName(NS_URI, XML_FEED);
+    public static final QName N_ID = new QName(NS_URI, XML_ID);
+    public static final QName N_LINK = new QName(NS_URI, XML_LINK);
+    public static final QName N_NAME = new QName(NS_URI, XML_NAME);
+    public static final QName N_TITLE = new QName(NS_URI, XML_TITLE);
+    public static final QName N_UPDATED = new QName(NS_URI, XML_UPDATED);
+}

Modified: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/DavResourceFactoryImpl.java Tue Dec 13 19:40:36 2011
@@ -16,6 +16,18 @@
  */
 package org.apache.jackrabbit.webdav.jcr;
 
+import javax.jcr.AccessDeniedException;
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.EventJournal;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.jackrabbit.JcrConstants;
+import org.apache.jackrabbit.util.Text;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavMethods;
 import org.apache.jackrabbit.webdav.DavResource;
@@ -33,17 +45,9 @@ import org.apache.jackrabbit.webdav.tran
 import org.apache.jackrabbit.webdav.transaction.TransactionResource;
 import org.apache.jackrabbit.webdav.version.DeltaVServletRequest;
 import org.apache.jackrabbit.webdav.version.VersionControlledResource;
-import org.apache.jackrabbit.JcrConstants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-
 /**
  * <code>DavResourceFactoryImpl</code>...
  */
@@ -88,9 +92,26 @@ public class DavResourceFactoryImpl impl
         JcrDavSession session = (JcrDavSession)request.getDavSession();
 
         DavResource resource;
+        String type = request.getParameter("type");
+
         if (locator.isRootLocation()) {
             // root
             resource = new RootCollection(locator, session, this);
+        } else if ("journal".equals(type) && locator.getResourcePath().equals(locator.getWorkspacePath())) {
+            // feed/event journal resource
+            try {
+                EventJournal ej = session.getRepositorySession().getWorkspace().getObservationManager()
+                        .getEventJournal();
+                if (ej == null) {
+                    throw new DavException(HttpServletResponse.SC_NOT_IMPLEMENTED, "event journal not supported");
+                }
+                resource = new EventJournalResourceImpl(ej, locator, session, request, this);
+            } catch (AccessDeniedException ex) {
+                // EventJournal only allowed for admin?
+                throw new DavException(HttpServletResponse.SC_UNAUTHORIZED, ex);
+            } catch (RepositoryException ex) {
+                throw new DavException(HttpServletResponse.SC_BAD_REQUEST, ex);
+            }
         } else if (locator.getResourcePath().equals(locator.getWorkspacePath())) {
             // workspace resource
             resource = new WorkspaceResourceImpl(locator, session, this);

Added: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java?rev=1213890&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/EventJournalResourceImpl.java Tue Dec 13 19:40:36 2011
@@ -0,0 +1,473 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.jackrabbit.webdav.jcr;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventJournal;
+import javax.servlet.http.HttpServletRequest;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.sax.SAXTransformerFactory;
+import javax.xml.transform.sax.TransformerHandler;
+import javax.xml.transform.stream.StreamResult;
+
+import org.apache.jackrabbit.commons.webdav.AtomFeedConstants;
+import org.apache.jackrabbit.commons.webdav.EventUtil;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.AdditionalEventInfo;
+import org.apache.jackrabbit.util.ISO8601;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavResource;
+import org.apache.jackrabbit.webdav.DavResourceFactory;
+import org.apache.jackrabbit.webdav.DavResourceIterator;
+import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
+import org.apache.jackrabbit.webdav.DavResourceLocator;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.io.InputContext;
+import org.apache.jackrabbit.webdav.io.OutputContext;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Implements a JCR {@link EventJournal} in terms of an RFC 4287 Atom feed.
+ * <p>
+ * Each feed entry represents either a single event, or, if the repository
+ * supports the {@link Event#PERSIST} event, an event bundle. The actual event
+ * data is sent in the Atom &lt;content&gt; element and uses the same XML
+ * serialization as the one used for subscriptions.
+ * <p>
+ * Skipping is implemented by specifying the desired time offset (represented
+ * as hexadecimal long in ms since the epoch) disguised as ETag in the HTTP "If-None-Match" 
+ * header field.
+ * <p>
+ * The generated feed may not be complete; the total number of events is limited in
+ * order not to overload the client.
+ * <p>
+ * Furthermore, the number of events is limited by going up to 2000 ms into the future
+ * (based on the request time). This is supposed to limit the wait time for the client).
+ */
+public class EventJournalResourceImpl extends AbstractResource {
+
+    public static final String RELURIFROMWORKSPACE = "?type=journal";
+    
+    public static final String EVENTMEDIATYPE = "application/vnd.apache.jackrabbit.event+xml";
+
+    private static Logger log = LoggerFactory.getLogger(EventJournalResourceImpl.class);
+
+    private final HttpServletRequest request;
+    private final EventJournal journal;
+    private final DavResourceLocator locator;
+
+    EventJournalResourceImpl(EventJournal journal, DavResourceLocator locator, JcrDavSession session,
+            HttpServletRequest request, DavResourceFactory factory) {
+        super(locator, session, factory);
+        this.journal = journal;
+        this.locator = locator;
+        this.request = request;
+    }
+
+    public String getSupportedMethods() {
+        return "GET, HEAD";
+    }
+
+    public boolean exists() {
+        try {
+            List<String> available = Arrays.asList(getRepositorySession().getWorkspace().getAccessibleWorkspaceNames());
+            return available.contains(getLocator().getWorkspaceName());
+        } catch (RepositoryException e) {
+            log.warn(e.getMessage());
+            return false;
+        }
+    }
+
+    public boolean isCollection() {
+        return false;
+    }
+
+    public String getDisplayName() {
+        return "event journal for " + getLocator().getWorkspaceName();
+    }
+
+    public long getModificationTime() {
+        return System.currentTimeMillis();
+    }
+
+    private static final String ATOMNS = AtomFeedConstants.NS_URI;
+    private static final String EVNS = ObservationConstants.NAMESPACE.getURI();
+
+    private static final String AUTHOR = AtomFeedConstants.XML_AUTHOR;
+    private static final String CONTENT = AtomFeedConstants.XML_CONTENT;
+    private static final String ENTRY = AtomFeedConstants.XML_ENTRY;
+    private static final String FEED = AtomFeedConstants.XML_FEED;
+    private static final String ID = AtomFeedConstants.XML_ID;
+    private static final String LINK = AtomFeedConstants.XML_LINK;
+    private static final String NAME = AtomFeedConstants.XML_NAME;
+    private static final String TITLE = AtomFeedConstants.XML_TITLE;
+    private static final String UPDATED = AtomFeedConstants.XML_UPDATED;
+
+    private static final String E_EVENT = ObservationConstants.XML_EVENT;
+    private static final String E_EVENTDATE = ObservationConstants.XML_EVENTDATE;
+    private static final String E_EVENTIDENTIFIER = ObservationConstants.XML_EVENTIDENTIFIER;
+    private static final String E_EVENTINFO = ObservationConstants.XML_EVENTINFO;
+    private static final String E_EVENTTYPE = ObservationConstants.XML_EVENTTYPE;
+    private static final String E_EVENTMIXINNODETYPE = ObservationConstants.XML_EVENTMIXINNODETYPE;
+    private static final String E_EVENTPRIMARNODETYPE = ObservationConstants.XML_EVENTPRIMARNODETYPE;
+    private static final String E_EVENTUSERDATA = ObservationConstants.XML_EVENTUSERDATA;
+
+    private static final int MAXWAIT = 2000; // maximal wait time
+    private static final int MAXEV = 10000; // maximal event number
+
+    private static final Attributes NOATTRS = new AttributesImpl();
+
+    public void spool(OutputContext outputContext) throws IOException {
+
+        Calendar cal = Calendar.getInstance(Locale.ENGLISH);
+
+        try {
+            outputContext.setContentType("application/atom+xml; charset=UTF-8");
+            outputContext.setProperty("Vary", "If-None-Match");
+            // TODO: Content-Encoding: gzip
+
+            // find out where to start
+            long prevts = -1;
+            String inm = request.getHeader("If-None-Match");
+            if (inm != null) {
+                // TODO: proper parsing when comma-delimited
+                inm = inm.trim();
+                if (inm.startsWith("\"") && inm.endsWith("\"")) {
+                    String tmp = inm.substring(1, inm.length() - 1);
+                    try {
+                        prevts = Long.parseLong(tmp, 16);
+                        journal.skipTo(prevts);
+                    } catch (NumberFormatException ex) {
+                        // broken etag
+                    }
+                }
+            }
+
+            boolean hasPersistEvents = false;
+
+            if (outputContext.hasStream()) {
+
+                long lastts = -1;
+                long now = System.currentTimeMillis();
+                boolean done = false;
+
+                // collect events
+                List<Event> events = new ArrayList<Event>(MAXEV);
+
+                while (!done && journal.hasNext()) {
+                    Event e = journal.nextEvent();
+
+                    hasPersistEvents |= e.getType() == Event.PERSIST;
+
+                    if (e.getDate() != lastts) {
+                        // consider stopping
+                        if (events.size() > MAXEV) {
+                            done = true;
+                        }
+                        if (e.getDate() > now + MAXWAIT) {
+                            done = true;
+                        }
+                    }
+
+                    if (!done && (prevts == -1 || e.getDate() >= prevts)) {
+                        events.add(e);
+                    }
+
+                    lastts = e.getDate();
+                }
+
+                if (lastts >= 0) {
+                    // construct ETag from newest event
+                    outputContext.setETag("\"" + Long.toHexString(lastts) + "\"");
+                }
+
+                OutputStream os = outputContext.getOutputStream();
+                StreamResult streamResult = new StreamResult(os);
+                SAXTransformerFactory tf = (SAXTransformerFactory) TransformerFactory.newInstance();
+                TransformerHandler th = tf.newTransformerHandler();
+                Transformer s = th.getTransformer();
+                s.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
+                s.setOutputProperty(OutputKeys.INDENT, "yes");
+                s.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
+
+                th.setResult(streamResult);
+
+                th.startDocument();
+
+                th.startElement(ATOMNS, FEED, FEED, NOATTRS);
+
+                writeAtomElement(th, TITLE, "EventJournal for " + getLocator().getWorkspaceName());
+
+                th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
+                writeAtomElement(th, NAME, "Jackrabbit Event Journal Feed Generator");
+                th.endElement(ATOMNS, AUTHOR, AUTHOR);
+
+                String id = getFullUri(request);
+                writeAtomElement(th, ID, id);
+
+                AttributesImpl linkattrs = new AttributesImpl();
+                linkattrs.addAttribute(null, "self", "self", "CDATA", id);
+                writeAtomElement(th, LINK, linkattrs, null);
+
+                cal.setTimeInMillis(lastts >= 0 ? lastts : now);
+                String upd = ISO8601.format(cal);
+                writeAtomElement(th, UPDATED, upd);
+
+                String lastDateString = "";
+                long lastTimeStamp = 0;
+                long index = 0;
+
+                AttributesImpl contentatt = new AttributesImpl();
+                contentatt.addAttribute(null, "type", "type", "CDATA", EVENTMEDIATYPE);
+
+                while (!events.isEmpty()) {
+
+                    List<Event> bundle = null;
+                    String path = null;
+                    String op;
+
+                    if (hasPersistEvents) {
+                        bundle = new ArrayList<Event>();
+                        Event e = null;
+                        op = "operations";
+
+                        do {
+                            e = events.remove(0);
+                            bundle.add(e);
+
+                            // compute common path
+                            if (path == null) {
+                                path = e.getPath();
+                            } else {
+                                if (e.getPath() != null && e.getPath().length() < path.length()) {
+                                    path = e.getPath();
+                                }
+                            }
+                        } while (e.getType() != Event.PERSIST && !events.isEmpty());
+                    } else {
+                        // no persist events
+                        Event e = events.remove(0);
+                        bundle = Collections.singletonList(e);
+                        path = e.getPath();
+                        op = EventUtil.getEventName(e.getType());
+                    }
+
+                    Event firstEvent = bundle.get(0);
+
+                    String entryupd = lastDateString;
+                    if (lastTimeStamp != firstEvent.getDate()) {
+                        cal.setTimeInMillis(firstEvent.getDate());
+                        entryupd = ISO8601.format(cal);
+                        index = 0;
+                    } else {
+                        index += 1;
+                    }
+
+                    th.startElement(ATOMNS, ENTRY, ENTRY, NOATTRS);
+
+                    String entrytitle = op + (path != null ? (": " + path) : "");
+                    writeAtomElement(th, TITLE, entrytitle);
+
+                    String entryid = id + "?type=journal&ts=" + Long.toHexString(firstEvent.getDate()) + "-" + index;
+                    writeAtomElement(th, ID, entryid);
+
+                    String author = firstEvent.getUserID() == null || firstEvent.getUserID().length() == 0 ? null
+                            : firstEvent.getUserID();
+                    if (author != null) {
+                        th.startElement(ATOMNS, AUTHOR, AUTHOR, NOATTRS);
+                        writeAtomElement(th, NAME, author);
+                        th.endElement(ATOMNS, AUTHOR, AUTHOR);
+                    }
+
+                    writeAtomElement(th, UPDATED, entryupd);
+
+                    th.startElement(ATOMNS, CONTENT, CONTENT, contentatt);
+
+                    for (Event e : bundle) {
+
+                        // serialize the event
+                        th.startElement(EVNS, E_EVENT, E_EVENT, NOATTRS);
+
+                        // DAV:href
+                        if (e.getPath() != null) {
+                            boolean isCollection = (e.getType() == Event.NODE_ADDED || e.getType() == Event.NODE_REMOVED);
+                            String href = locator
+                                    .getFactory()
+                                    .createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(),
+                                            e.getPath(), false).getHref(isCollection);
+                            th.startElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF,
+                                    DavConstants.XML_HREF, NOATTRS);
+                            th.characters(href.toCharArray(), 0, href.length());
+                            th.endElement(DavConstants.NAMESPACE.getURI(), DavConstants.XML_HREF, DavConstants.XML_HREF);
+                        }
+
+                        // event type
+                        String evname = EventUtil.getEventName(e.getType());
+                        th.startElement(EVNS, E_EVENTTYPE, E_EVENTTYPE, NOATTRS);
+                        th.startElement(EVNS, evname, evname, NOATTRS);
+                        th.endElement(EVNS, evname, evname);
+                        th.endElement(EVNS, E_EVENTTYPE, E_EVENTTYPE);
+
+                        // date
+                        writeObsElement(th, E_EVENTDATE, Long.toString(e.getDate()));
+
+                        // user data
+                        if (e.getUserData() != null && e.getUserData().length() > 0) {
+                            writeObsElement(th, E_EVENTUSERDATA, firstEvent.getUserData());
+                        }
+
+                        // user id: already sent as Atom author/name
+
+                        // try to compute nodetype information
+                        if (e instanceof AdditionalEventInfo) {
+                            try {
+                                Name pnt = ((AdditionalEventInfo) e).getPrimaryNodeTypeName();
+                                if (pnt != null) {
+                                    writeObsElement(th, E_EVENTPRIMARNODETYPE, pnt.toString());
+                                }
+
+                                Set<Name> mixins = ((AdditionalEventInfo) e).getMixinTypeNames();
+                                if (mixins != null) {
+                                    for (Name mixin : mixins) {
+                                        writeObsElement(th, E_EVENTMIXINNODETYPE, mixin.toString());
+                                    }
+                                }
+
+                            } catch (UnsupportedRepositoryOperationException ex) {
+                                // optional
+                            }
+                        }
+
+                        // identifier
+                        if (e.getIdentifier() != null) {
+                            writeObsElement(th, E_EVENTIDENTIFIER, e.getIdentifier());
+                        }
+
+                        // info
+                        if (!e.getInfo().isEmpty()) {
+                            th.startElement(EVNS, E_EVENTINFO, E_EVENTINFO, NOATTRS);
+                            Map<?, ?> m = e.getInfo();
+                            for (Map.Entry<?, ?> entry : m.entrySet()) {
+                                String key = entry.getKey().toString();
+                                Object value = entry.getValue();
+                                String t = value != null ? value.toString() : null;
+                                writeElement(th, null, key, NOATTRS, t);
+                            }
+                            th.endElement(EVNS, E_EVENTINFO, E_EVENTINFO);
+                        }
+
+                        th.endElement(EVNS, E_EVENT, E_EVENT);
+
+                        lastTimeStamp = e.getDate();
+                        lastDateString = entryupd;
+                    }
+
+                    th.endElement(ATOMNS, CONTENT, CONTENT);
+                    th.endElement(ATOMNS, ENTRY, ENTRY);
+                }
+
+                th.endElement(ATOMNS, FEED, FEED);
+
+                th.endDocument();
+
+                os.flush();
+            }
+        } catch (Exception ex) {
+            throw new IOException(ex);
+        }
+    }
+
+    public DavResource getCollection() {
+        return null;
+    }
+
+    public void addMember(DavResource resource, InputContext inputContext) throws DavException {
+        throw new DavException(DavServletResponse.SC_FORBIDDEN);
+    }
+
+    public DavResourceIterator getMembers() {
+        return DavResourceIteratorImpl.EMPTY;
+    }
+
+    public void removeMember(DavResource member) throws DavException {
+        throw new DavException(DavServletResponse.SC_FORBIDDEN);
+    }
+
+    @Override
+    protected void initLockSupport() {
+        // lock not allowed
+    }
+
+    @Override
+    protected String getWorkspaceHref() {
+        return getHref();
+    }
+
+    private void writeElement(TransformerHandler th, String ns, String name, Attributes attrs, String textContent)
+            throws SAXException {
+        th.startElement(ns, name, name, attrs);
+        if (textContent != null) {
+            th.characters(textContent.toCharArray(), 0, textContent.length());
+        }
+        th.endElement(ns, name, name);
+    }
+
+    private void writeAtomElement(TransformerHandler th, String name, Attributes attrs, String textContent)
+            throws SAXException {
+        writeElement(th, ATOMNS, name, attrs, textContent);
+    }
+
+    private void writeAtomElement(TransformerHandler th, String name, String textContent) throws SAXException {
+        writeAtomElement(th, name, NOATTRS, textContent);
+    }
+
+    private void writeObsElement(TransformerHandler th, String name, String textContent) throws SAXException {
+        writeElement(th, EVNS, name, NOATTRS, textContent);
+    }
+
+    private String getFullUri(HttpServletRequest req) {
+
+        String scheme = req.getScheme();
+        int port = req.getServerPort();
+        boolean isDefaultPort = (scheme.equals("http") && port == 80) || (scheme.equals("http") && port == 443);
+        String query = request.getQueryString() != null ? "?" + request.getQueryString() : "";
+
+        return String.format("%s://%s%s%s%s%s", scheme, req.getServerName(), isDefaultPort ? ":" : "",
+                isDefaultPort ? Integer.toString(port) : "", req.getRequestURI(), query);
+    }
+}

Modified: jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/jcr/WorkspaceResourceImpl.java Tue Dec 13 19:40:36 2011
@@ -21,6 +21,7 @@ import org.apache.jackrabbit.commons.cnd
 import org.apache.jackrabbit.commons.cnd.DefinitionBuilderFactory;
 import org.apache.jackrabbit.commons.cnd.ParseException;
 import org.apache.jackrabbit.commons.cnd.TemplateBuilderFactory;
+import org.apache.jackrabbit.commons.webdav.AtomFeedConstants;
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavMethods;
 import org.apache.jackrabbit.webdav.DavResource;
@@ -198,12 +199,14 @@ public class WorkspaceResourceImpl exten
     }
 
     /**
-     * Sets content lengths to '0' and retrieves the modification time.
-     *
      * @param outputContext
      * @throws IOException
      */
     public void spool(OutputContext outputContext) throws IOException {
+
+        outputContext.setProperty("Link", "<?" + EventJournalResourceImpl.RELURIFROMWORKSPACE
+                + ">; title=\"Event Journal\"; rel=alternate; type=\"" + AtomFeedConstants.MEDIATYPE + "\"");
+
         if (outputContext.hasStream()) {
             Session session = getRepositorySession();
             Repository rep = session.getRepository();
@@ -215,7 +218,10 @@ public class WorkspaceResourceImpl exten
             StringBuilder sb = new StringBuilder();
             sb.append("<html><head><title>");
             sb.append(repostr);
-            sb.append("</title></head>");
+            sb.append("</title>");
+            sb.append("<link rel=alternate type=\"" + AtomFeedConstants.MEDIATYPE
+                    + "\" title=\"Event Journal\" href=\"?" + EventJournalResourceImpl.RELURIFROMWORKSPACE + "\">");
+            sb.append("</head>");
             sb.append("<body><h2>").append(repostr).append("</h2><ul>");
             sb.append("<li><a href=\"..\">..</a></li>");
             DavResourceIterator it = getMembers();

Modified: jackrabbit/trunk/jackrabbit-jcr2dav/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-jcr2dav/pom.xml?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-jcr2dav/pom.xml (original)
+++ jackrabbit/trunk/jackrabbit-jcr2dav/pom.xml Tue Dec 13 19:40:36 2011
@@ -80,8 +80,6 @@
                       org.apache.jackrabbit.test.api.query.CreateQueryTest#testUnknownQueryLanguage
                       <!-- JCR-2543 : query offset -->
                       org.apache.jackrabbit.test.api.query.SetOffsetTest#testSetOffset
-                      <!-- JCR-2541 : event journal -->
-                      org.apache.jackrabbit.test.api.observation.EventJournalTest
                       <!-- JCR-2533 : missing impl of checkQueryStatement -->
                       org.apache.jackrabbit.test.api.query.qom.BindVariableValueTest
                       <!-- JCR-2535 : Row.getPath() called with multiple selectors (server-side) -->

Modified: jackrabbit/trunk/jackrabbit-spi2dav/pom.xml
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi2dav/pom.xml?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi2dav/pom.xml (original)
+++ jackrabbit/trunk/jackrabbit-spi2dav/pom.xml Tue Dec 13 19:40:36 2011
@@ -73,8 +73,6 @@
                   org.apache.jackrabbit.test.api.lock.LockManagerTest#testAddInvalidLockToken
                   org.apache.jackrabbit.test.api.lock.LockManagerTest#testAddLockTokenToAnotherSession
                   org.apache.jackrabbit.test.api.lock.LockManagerTest#testLockTransfer2
-                  <!-- JCR-2541 : event journal -->
-                  org.apache.jackrabbit.test.api.observation.EventJournalTest
                   <!-- JCR-2533 : missing impl of checkQueryStatement -->
                   org.apache.jackrabbit.test.api.query.CreateQueryTest#testUnknownQueryLanguage
                   <!-- JCR-2543 : query offset -->

Modified: jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/EventImpl.java Tue Dec 13 19:40:36 2011
@@ -57,22 +57,15 @@ public class EventImpl
 
     private static final NameFactory N_FACTORY = NameFactoryImpl.getInstance();
 
-    public EventImpl(ItemId eventId, Path eventPath, NodeId parentId,
-            int eventType, Element eventElement, NamePathResolver resolver,
-            QValueFactory qvFactory) throws NamespaceException,
+    public EventImpl(ItemId eventId, Path eventPath, NodeId parentId, int eventType, String userId,
+            Element eventElement, NamePathResolver resolver, QValueFactory qvFactory) throws NamespaceException,
             IllegalNameException {
-        super(getSpiEventType(eventType), eventPath, eventId, parentId,
-                resolver.getQName(DomUtil.getChildTextTrim(eventElement,
-                        XML_EVENTPRIMARNODETYPE, NAMESPACE)), getNames(
-                        DomUtil.getChildren(eventElement,
-                                XML_EVENTMIXINNODETYPE, NAMESPACE), resolver),
-                DomUtil.getChildTextTrim(eventElement, XML_EVENTUSERID,
-                        NAMESPACE), DomUtil.getChildTextTrim(eventElement,
-                        XML_EVENTUSERDATA, NAMESPACE), Long.parseLong(DomUtil
-                        .getChildTextTrim(eventElement, XML_EVENTDATE,
-                                NAMESPACE)), getEventInfo(
-                        DomUtil.getChildElement(eventElement, XML_EVENTINFO,
-                                NAMESPACE), resolver, qvFactory));
+        super(getSpiEventType(eventType), eventPath, eventId, parentId, getNameSafe(
+                DomUtil.getChildTextTrim(eventElement, XML_EVENTPRIMARNODETYPE, NAMESPACE), resolver), getNames(
+                DomUtil.getChildren(eventElement, XML_EVENTMIXINNODETYPE, NAMESPACE), resolver), userId, DomUtil
+                .getChildTextTrim(eventElement, XML_EVENTUSERDATA, NAMESPACE), Long.parseLong(DomUtil.getChildTextTrim(
+                eventElement, XML_EVENTDATE, NAMESPACE)), getEventInfo(
+                DomUtil.getChildElement(eventElement, XML_EVENTINFO, NAMESPACE), resolver, qvFactory));
     }
 
     //--------------------------------------------------------------------------
@@ -128,6 +121,15 @@ public class EventImpl
         return info;
     }
 
+    private static Name getNameSafe(String name, NamePathResolver resolver) throws IllegalNameException, NamespaceException {
+        if (name == null) {
+            return null;
+        }
+        else {
+            return resolver.getQName(name);
+        }
+    }
+
     private static Name[] getNames(ElementIterator elements,
             NamePathResolver resolver) {
 

Modified: jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-spi2dav/src/main/java/org/apache/jackrabbit/spi2dav/RepositoryServiceImpl.java Tue Dec 13 19:40:36 2011
@@ -75,6 +75,7 @@ import org.apache.commons.httpclient.met
 import org.apache.commons.httpclient.methods.RequestEntity;
 import org.apache.commons.httpclient.methods.StringRequestEntity;
 import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
+import org.apache.jackrabbit.commons.webdav.AtomFeedConstants;
 import org.apache.jackrabbit.commons.webdav.EventUtil;
 import org.apache.jackrabbit.commons.webdav.JcrRemotingConstants;
 import org.apache.jackrabbit.commons.webdav.JcrValueType;
@@ -171,7 +172,6 @@ import org.apache.jackrabbit.webdav.obse
 import org.apache.jackrabbit.webdav.observation.EventDiscovery;
 import org.apache.jackrabbit.webdav.observation.EventType;
 import org.apache.jackrabbit.webdav.observation.ObservationConstants;
-import org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery;
 import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
 import org.apache.jackrabbit.webdav.ordering.OrderingConstants;
 import org.apache.jackrabbit.webdav.property.DavProperty;
@@ -2023,11 +2023,64 @@ public class RepositoryServiceImpl imple
     /**
      * @see RepositoryService#getEvents(SessionInfo, EventFilter, long)
      */
-    public EventBundle getEvents(SessionInfo sessionInfo, EventFilter filter,
-                                   long after) throws
-            RepositoryException, UnsupportedRepositoryOperationException {
-        // TODO
-        throw new UnsupportedRepositoryOperationException("Not implemented -> JCR-2541");
+    public EventBundle getEvents(SessionInfo sessionInfo, EventFilter filter, long after) throws RepositoryException,
+            UnsupportedRepositoryOperationException {
+        // TODO: use filters remotely (JCR-3179)
+
+        GetMethod method = null;
+        String rootUri = uriResolver.getWorkspaceUri(sessionInfo.getWorkspaceName());
+        rootUri += "?type=journal"; // TODO should have a way to discover URI template
+
+        try {
+            method = new GetMethod(rootUri);
+            method.addRequestHeader("If-None-Match", "\"" + Long.toHexString(after) + "\""); // TODO
+            initMethod(method, sessionInfo);
+
+            getClient(sessionInfo).executeMethod(method);
+            assert method.getStatusCode() == 200;
+
+            InputStream in = method.getResponseBodyAsStream();
+            Document doc = null;
+            if (in != null) {
+                // read response and try to build a xml document
+                try {
+                    doc = DomUtil.parseDocument(in);
+                } catch (ParserConfigurationException e) {
+                    IOException exception = new IOException("XML parser configuration error");
+                    exception.initCause(e);
+                    throw exception;
+                } catch (SAXException e) {
+                    IOException exception = new IOException("XML parsing error");
+                    exception.initCause(e);
+                    throw exception;
+                } finally {
+                    in.close();
+                }
+            }
+
+            List<Event> events = new ArrayList<Event>();
+
+            ElementIterator entries = DomUtil.getChildren(doc.getDocumentElement(), AtomFeedConstants.N_ENTRY);
+            while (entries.hasNext()) {
+                Element entryElem = entries.next();
+
+                Element contentElem = DomUtil.getChildElement(entryElem, AtomFeedConstants.N_CONTENT);
+                if (contentElem != null
+                        && "application/vnd.apache.jackrabbit.event+xml".equals(contentElem.getAttribute("type"))) {
+                    List<Event> el = buildEventList(contentElem, (SessionInfoImpl) sessionInfo);
+                    for (Event e : el) {
+                        if (e.getDate() > after && (filter == null || filter.accept(e, false))) {
+                            events.add(e);
+                        }
+                    }
+                }
+            }
+
+            return new EventBundleImpl(events, false);
+        } catch (Exception ex) {
+            log.error("extracting events from journal feed", ex);
+            throw new RepositoryException(ex);
+        }
     }
 
     /**
@@ -2209,6 +2262,18 @@ public class RepositoryServiceImpl imple
     private List<Event> buildEventList(Element bundleElement, SessionInfoImpl sessionInfo) throws IllegalNameException, NamespaceException {
         List<Event> events = new ArrayList<Event>();
         ElementIterator eventElementIterator = DomUtil.getChildren(bundleElement, ObservationConstants.XML_EVENT, ObservationConstants.NAMESPACE);
+
+        String userId = null;
+
+        // get user id from enclosing Atom entry element in case this was a feed
+        if (DomUtil.matches(bundleElement, AtomFeedConstants.N_ENTRY)) {
+            Element authorEl = DomUtil.getChildElement(bundleElement, AtomFeedConstants.N_AUTHOR);
+            Element nameEl = authorEl != null ? DomUtil.getChildElement(authorEl, AtomFeedConstants.N_NAME) : null;
+            if (nameEl != null) {
+                userId = DomUtil.getTextTrim(nameEl);
+            }
+        }
+
         while (eventElementIterator.hasNext()) {
             Element evElem = eventElementIterator.nextElement();
             Element typeEl = DomUtil.getChildElement(evElem, ObservationConstants.XML_EVENTTYPE, ObservationConstants.NAMESPACE);
@@ -2222,51 +2287,61 @@ public class RepositoryServiceImpl imple
             String href = DomUtil.getChildTextTrim(evElem, XML_HREF, NAMESPACE);
 
             int type = EventUtil.getJcrEventType(et[0].getName());
-            Path eventPath;
-            try {
-                eventPath = uriResolver.getQPath(href, sessionInfo);
-            } catch (RepositoryException e) {
-                // should not occur
-                log.error("Internal error while building Event", e.getMessage());
-                continue;
-            }
-
-            boolean isForNode = (type == Event.NODE_ADDED
-                    || type == Event.NODE_REMOVED || type == Event.NODE_MOVED);
-            
+            Path eventPath = null;
             ItemId eventId = null;
-            try {
-                if (isForNode) {
-                    eventId = uriResolver.getNodeIdAfterEvent(href,
-                            sessionInfo, type == Event.NODE_REMOVED);
-                } else {
-                    eventId = uriResolver.getPropertyId(href, sessionInfo);
+            NodeId parentId = null;
+
+            if (href != null) {
+                try {
+                    eventPath = uriResolver.getQPath(href, sessionInfo);
+                } catch (RepositoryException e) {
+                    // should not occur
+                    log.error("Internal error while building Event", e.getMessage());
+                    continue;
                 }
-            } catch (RepositoryException e) {
-                if (isForNode) {
-                    eventId = idFactory.createNodeId((String) null, eventPath);
-                } else {
-                    try {
-                        eventId = idFactory.createPropertyId(
-                                idFactory.createNodeId((String) null,
-                                        eventPath.getAncestor(1)),
-                                eventPath.getName());
-                    } catch (RepositoryException e1) {
-                        log.warn("Unable to build event itemId: ",
-                                e.getMessage());
+
+                boolean isForNode = (type == Event.NODE_ADDED
+                        || type == Event.NODE_REMOVED || type == Event.NODE_MOVED);
+                
+                try {
+                    if (isForNode) {
+                        eventId = uriResolver.getNodeIdAfterEvent(href,
+                                sessionInfo, type == Event.NODE_REMOVED);
+                    } else {
+                        eventId = uriResolver.getPropertyId(href, sessionInfo);
+                    }
+                } catch (RepositoryException e) {
+                    if (isForNode) {
+                        eventId = idFactory.createNodeId((String) null, eventPath);
+                    } else {
+                        try {
+                            eventId = idFactory.createPropertyId(
+                                    idFactory.createNodeId((String) null,
+                                            eventPath.getAncestor(1)),
+                                    eventPath.getName());
+                        } catch (RepositoryException e1) {
+                            log.warn("Unable to build event itemId: ",
+                                    e.getMessage());
+                        }
                     }
                 }
-            }
-            String parentHref = Text.getRelativeParent(href, 1, true);
-            NodeId parentId = null;
-            try {
-                parentId = uriResolver.getNodeId(parentHref, sessionInfo);
-            } catch (RepositoryException e) {
-                log.warn("Unable to build event parentId: ", e.getMessage());
+
+                String parentHref = Text.getRelativeParent(href, 1, true);
+                try {
+                    parentId = uriResolver.getNodeId(parentHref, sessionInfo);
+                } catch (RepositoryException e) {
+                    log.warn("Unable to build event parentId: ", e.getMessage());
+                }
+                
             }
 
+            if (userId == null) {
+                // user id not retrieved from container
+                userId = DomUtil.getChildTextTrim(evElem, ObservationConstants.XML_EVENTUSERID, ObservationConstants.NAMESPACE);
+            }
 
-            events.add(new EventImpl(eventId, eventPath, parentId, type, evElem, getNamePathResolver(sessionInfo), getQValueFactory()));
+            events.add(new EventImpl(eventId, eventPath, parentId, type, userId, evElem,
+                    getNamePathResolver(sessionInfo), getQValueFactory()));
         }
 
         return events;

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/DomUtil.java Tue Dec 13 19:40:36 2011
@@ -31,6 +31,7 @@ import org.xml.sax.SAXException;
 import org.xml.sax.helpers.DefaultHandler;
 
 import javax.xml.XMLConstants;
+import javax.xml.namespace.QName;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -289,6 +290,28 @@ public class DomUtil {
     }
 
     /**
+     * Returns the first child element that matches the given {@link QName}.
+     * If no child element is present or no child element matches,
+     * <code>null</code> is returned.
+     *
+     * @param parent
+     * @param childName
+     * @return first child element matching the specified name or <code>null</code>.
+     */
+    public static Element getChildElement(Node parent, QName childName) {
+        if (parent != null) {
+            NodeList children = parent.getChildNodes();
+            for (int i = 0; i < children.getLength(); i++) {
+                Node child = children.item(i);
+                if (isElement(child) && matches(child, childName)) {
+                    return (Element)child;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Returns a <code>ElementIterator</code> containing all child elements of
      * the given parent node that match the given local name and namespace.
      * If the namespace is <code>null</code> only the localName is compared.
@@ -304,6 +327,20 @@ public class DomUtil {
     }
 
     /**
+     * Returns a <code>ElementIterator</code> containing all child elements of
+     * the given parent node that match the given {@link QName}.
+     * 
+     * @param parent
+     *            the node the children elements should be retrieved from
+     * @param childName
+     * @return an <code>ElementIterator</code> giving access to all child
+     *         elements that match the specified name.
+     */
+    public static ElementIterator getChildren(Element parent, QName childName) {
+        return new ElementIterator(parent, childName);
+    }
+
+    /**
      * Return an <code>ElementIterator</code> over all child elements.
      *
      * @param parent
@@ -409,6 +446,24 @@ public class DomUtil {
     }
 
     /**
+     * Returns true if the specified node matches the required {@link QName}.
+     *
+     * @param node
+     * @param requiredName
+     * @return true if local name and namespace match the corresponding properties
+     * of the given DOM node.
+     */
+    public static boolean matches(Node node, QName requiredName) {
+        if (node == null) {
+            return false;
+        } else {
+            String nodens = node.getNamespaceURI() != null ? node.getNamespaceURI() : "";
+            return nodens.equals(requiredName.getNamespaceURI())
+                    && node.getLocalName().equals(requiredName.getLocalPart());
+        }
+    }
+
+    /**
      * @param node
      * @param requiredNamespace
      * @return true if the required namespace is <code>null</code> or matches

Modified: jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ElementIterator.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ElementIterator.java?rev=1213890&r1=1213889&r2=1213890&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ElementIterator.java (original)
+++ jackrabbit/trunk/jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/xml/ElementIterator.java Tue Dec 13 19:40:36 2011
@@ -25,6 +25,8 @@ import org.w3c.dom.NodeList;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import javax.xml.namespace.QName;
+
 /**
  * <code>ElementIterator</code>...
  */
@@ -34,6 +36,7 @@ public class ElementIterator implements 
 
     private final Namespace namespace;
     private final String localName;
+    private final QName qName;
 
     private Element next;
 
@@ -49,6 +52,22 @@ public class ElementIterator implements 
     public ElementIterator(Element parent, String localName, Namespace namespace) {
         this.localName = localName;
         this.namespace = namespace;
+        this.qName = null;
+        seek(parent);
+    }
+
+    /**
+     * Create a new instance of <code>ElementIterator</code> with the given
+     * parent element. Only child elements that match the given {@link QName}
+     * will be respected by {@link #hasNext()} and {@link #nextElement()}.
+     *
+     * @param parent
+     * @param qname name to match (exactly)
+     */
+    public ElementIterator(Element parent, QName qname) {
+        this.localName = null;
+        this.namespace = null;
+        this.qName = qname;
         seek(parent);
     }
 
@@ -111,7 +130,7 @@ public class ElementIterator implements 
         NodeList nodeList = parent.getChildNodes();
         for (int i = 0; i < nodeList.getLength(); i++) {
             Node n = nodeList.item(i);
-            if (DomUtil.isElement(n) && DomUtil.matches(n, localName, namespace)) {
+            if (matchesName(n)) {
                 next = (Element)n;
                 return;
             }
@@ -124,7 +143,7 @@ public class ElementIterator implements 
     private void seek() {
         Node n = next.getNextSibling();
         while (n != null) {
-            if (DomUtil.isElement(n) && DomUtil.matches(n, localName, namespace)) {
+            if (matchesName(n)) {
                 next = (Element)n;
                 return;
             } else {
@@ -134,4 +153,18 @@ public class ElementIterator implements 
         // no next element found -> set to null in order to leave the loop.
         next = null;
     }
+
+    /**
+     * Matches the node name according to either {@link #qName} or the pair
+     * of {@link #localName) and {@link #namespace}.
+     */
+    private boolean matchesName(Node n) {
+        if (!DomUtil.isElement(n)) {
+            return false;
+        } else if (qName != null) {
+            return DomUtil.matches(n, qName);
+        } else {
+            return DomUtil.matches(n, localName, namespace);
+        }
+    }
 }



Mime
View raw message