You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by an...@apache.org on 2006/07/12 11:38:26 UTC

svn commit: r421206 - in /jackrabbit/trunk/jcr-server: client/src/java/org/apache/jackrabbit/webdav/client/methods/ server/src/java/org/apache/jackrabbit/webdav/jcr/observation/ webdav/src/java/org/apache/jackrabbit/webdav/ webdav/src/java/org/apache/j...

Author: angela
Date: Wed Jul 12 02:38:25 2006
New Revision: 421206

URL: http://svn.apache.org/viewvc?rev=421206&view=rev
Log:
- add methods for observation to client
- minor improvements to observation impl in jcr-server
- fix client LockMethod and adjust LockDiscovery accordingly
- fix client MergeMethod adjust corresponding Info object accordingly
- improve UpdateInfo
- use CodedURLHeader instaed of building locktoken header manually

Added:
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java   (with props)
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java   (with props)
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java   (with props)
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java   (with props)
Modified:
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/LockMethod.java
    jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/MergeMethod.java
    jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java
    jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionManagerImpl.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/WebdavResponseImpl.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/header/TimeoutHeader.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/lock/LockDiscovery.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/EventDiscovery.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionDiscovery.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionInfo.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/MergeInfo.java
    jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/UpdateInfo.java

Modified: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java (original)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/DavMethodBase.java Wed Jul 12 02:38:25 2006
@@ -250,9 +250,7 @@
     protected void processStatusLine(HttpState httpState, HttpConnection httpConnection) {
         super.processStatusLine(httpState, httpConnection);
         int code = getStatusCode();
-        // default
-        success = code < DavServletResponse.SC_BAD_REQUEST;
-        // sub classes overwrites
+        // sub classes define if status code indicates success.
         success = isSuccess(code);
     }
 
@@ -265,7 +263,7 @@
      * @param httpConnection
      * @see HttpMethodBase#processResponseBody(HttpState, HttpConnection)
      */
-    protected final void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+    protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
         // in case of multi-status response
         if (getStatusCode() == DavServletResponse.SC_MULTI_STATUS) {
             try {

Modified: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/LockMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/LockMethod.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/LockMethod.java (original)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/LockMethod.java Wed Jul 12 02:38:25 2006
@@ -18,17 +18,24 @@
 
 import org.apache.jackrabbit.webdav.DavConstants;
 import org.apache.jackrabbit.webdav.DavMethods;
-import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
 import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.apache.jackrabbit.webdav.header.DepthHeader;
 import org.apache.jackrabbit.webdav.header.IfHeader;
 import org.apache.jackrabbit.webdav.header.TimeoutHeader;
-import org.apache.jackrabbit.webdav.lock.ActiveLock;
+import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
 import org.apache.jackrabbit.webdav.lock.LockInfo;
 import org.apache.jackrabbit.webdav.lock.Scope;
 import org.apache.jackrabbit.webdav.lock.Type;
+import org.apache.jackrabbit.webdav.lock.LockDiscovery;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.HttpState;
+import org.apache.commons.httpclient.HttpConnection;
+import org.apache.commons.httpclient.Header;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
 
 import java.io.IOException;
 
@@ -39,6 +46,9 @@
 
     private static Logger log = LoggerFactory.getLogger(LockMethod.class);
 
+    private final boolean isRefresh;
+    private LockDiscovery lockDiscovery;
+
     /**
      * Creates a new <code>LockMethod</code>.
      *
@@ -62,20 +72,21 @@
      */
     public LockMethod(String uri, LockInfo lockInfo) throws IOException {
         super(uri);
-        if (lockInfo != null) {
+        if (lockInfo != null && !lockInfo.isRefreshLock()) {
             TimeoutHeader th = new TimeoutHeader(lockInfo.getTimeout());
             setRequestHeader(th);
-            if (!lockInfo.isRefreshLock()) {
-                DepthHeader dh = new DepthHeader(lockInfo.isDeep());
-                setRequestHeader(dh);
-                setRequestHeader(DavConstants.HEADER_CONTENT_TYPE, "text/xml; charset=UTF-8");
-                setRequestBody(lockInfo);
-            }
+            DepthHeader dh = new DepthHeader(lockInfo.isDeep());
+            setRequestHeader(dh);
+            setRequestHeader(DavConstants.HEADER_CONTENT_TYPE, "text/xml; charset=UTF-8");
+            setRequestBody(lockInfo);
+            isRefresh = false;
+        } else {
+            throw new IllegalArgumentException("Cannot create a LOCK request without lock info. Use the constructor taking lock tokens in order to build a LOCK request for refresh.");
         }
     }
 
     /**
-     * Create a new 'Refresh' lock method.
+     * Create a new Lock method used to 'REFRESH' an existing lock.
      *
      * @param uri
      * @param timeout
@@ -88,18 +99,48 @@
         setRequestHeader(th);
         IfHeader ifh = new IfHeader(lockTokens);
         setRequestHeader(ifh);
+        isRefresh = true;
     }
 
-    public ActiveLock getResponseAsLock() throws IOException {
+    /**
+     *
+     * @return
+     * @throws IOException
+     * @throws DavException
+     */
+    public LockDiscovery getResponseAsLockDiscovery() throws IOException, DavException {
         checkUsed();
-        // todo -> build lockdiscovery-prop -> retrieve activelock
-        return null;
+        // lockDiscovery has been build while processing the response body.
+        // if its still null, this indicates that either the method failed
+        // or that the response body could not be parsed.
+        // in either case this is an error and will be reported to the caller.
+        if (lockDiscovery != null) {
+            return lockDiscovery;
+        } else {
+            DavException dx = getResponseException();
+            if (dx != null) {
+                throw dx;
+            } else {
+                throw new DavException(getStatusCode(), getName() + " resulted with unexpected status: " + getStatusLine());
+            }
+        }
     }
 
+    /**
+     *
+     * @return
+     */
     public String getLockToken() {
         checkUsed();
-        CodedUrlHeader cuh = new CodedUrlHeader(DavConstants.HEADER_LOCK_TOKEN, getResponseHeader(DavConstants.HEADER_LOCK_TOKEN).getValue());
-        return cuh.getCodedUrl();
+        Header ltHeader = getResponseHeader(DavConstants.HEADER_LOCK_TOKEN);
+        if (ltHeader != null) {
+            CodedUrlHeader cuh = new CodedUrlHeader(DavConstants.HEADER_LOCK_TOKEN, ltHeader.getValue());
+            return cuh.getCodedUrl();
+        } else {
+            // not Lock-Token header must be sent in response to a 'refresh'.
+            // see the validation performed while processing the response.
+            return null;
+        }
     }
 
     //---------------------------------------------------------< HttpMethod >---
@@ -110,6 +151,17 @@
         return DavMethods.METHOD_LOCK;
     }
 
+    //----------------------------------------------------------< DavMethod >---
+    /**
+     * @return true, if the status code indicates success and if the response
+     * contains a Lock-Token header for a request used to create a new lock.
+     */
+    public boolean succeeded() {
+        checkUsed();
+        String lt = getLockToken();
+        boolean containsRequiredHeader = (isRefresh) ? lt == null : lt != null;
+        return getSuccess() && containsRequiredHeader;
+    }
     //------------------------------------------------------< DavMethodBase >---
     /**
      *
@@ -118,5 +170,55 @@
      */
     protected boolean isSuccess(int statusCode) {
         return statusCode == DavServletResponse.SC_OK;
+    }
+
+    //-----------------------------------------------------< HttpMethodBase >---
+    /**
+     * Retrieves the DAV:lockdiscovery property present in the response body
+     * and builds 'ActiveLock' objects from the corresponding DAV:activelock
+     * child elements inside the lock discovery. If parsing the response body
+     * fails for whatever reason or if the DAV:lockdiscovery did not contain
+     * at least a single DAV:activelock entry (the one created by the LOCK
+     * call) this methods in addition resets the 'success' flag to false.
+     *
+     * @param httpState
+     * @param httpConnection
+     * @see HttpMethodBase#processResponseBody(HttpState, HttpConnection)
+     */
+    protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+        // in case of successful response code -> parse xml body into lockDiscovery.
+        if (getSuccess()) {
+            try {
+                setSuccess(buildDiscoveryFromRoot(getRootElement()));
+            } catch (IOException e) {
+                log.error("Error while parsing multistatus response: " + e);
+                setSuccess(false);
+            }
+        }
+    }
+
+    //------------------------------------------------------------< private >---
+    /**
+     * Builds a new <code>LockDiscovery</code> object from the root XML
+     * present in the response body and returns true if the object could be
+     * created successfully.
+     *
+     * @param root
+     * @return if a <code>LockDiscovery</code> object could be created from the
+     * given XML, false otherwise.
+     */
+    private boolean buildDiscoveryFromRoot(Element root) {
+        if (DomUtil.matches(root, XML_PROP, NAMESPACE) && DomUtil.hasChildElement(root, PROPERTY_LOCKDISCOVERY, NAMESPACE)) {
+            Element lde = DomUtil.getChildElement(root, PROPERTY_LOCKDISCOVERY, NAMESPACE);
+            if (DomUtil.hasChildElement(lde, XML_ACTIVELOCK, NAMESPACE)) {
+                lockDiscovery = LockDiscovery.createFromXml(lde);
+                return true;
+            } else {
+                log.debug("The DAV:lockdiscovery must contain a least a single DAV:activelock in response to a successful LOCK request.");
+            }
+        } else {
+            log.debug("Missing DAV:prop response body in LOCK method.");
+        }
+        return false;
     }
 }

Modified: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/MergeMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/MergeMethod.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/MergeMethod.java (original)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/MergeMethod.java Wed Jul 12 02:38:25 2006
@@ -51,6 +51,7 @@
      * @return true if status code is {@link DavServletResponse#SC_MULTI_STATUS 207 (Multi-Status)}.
      */
     protected boolean isSuccess(int statusCode) {
+        // TODO: is this correct?
         return statusCode == DavServletResponse.SC_MULTI_STATUS;
     }
 }

Added: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java?rev=421206&view=auto
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java (added)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java Wed Jul 12 02:38:25 2006
@@ -0,0 +1,127 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.client.methods;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.webdav.DavMethods;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.ElementIterator;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+import org.apache.jackrabbit.webdav.observation.EventDiscovery;
+import org.apache.jackrabbit.webdav.observation.EventBundle;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.HttpState;
+import org.apache.commons.httpclient.HttpConnection;
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
+
+import java.io.IOException;
+
+/**
+ * <code>PollMethod</code> imple
+ */
+public class PollMethod extends DavMethodBase implements ObservationConstants {
+
+    private static Logger log = LoggerFactory.getLogger(PollMethod.class);
+
+    private EventDiscovery eventDiscovery;
+
+    public PollMethod(String uri, String subscriptionId) {
+        super(uri);
+        setRequestHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, subscriptionId);
+    }
+
+    public EventDiscovery getResponseAsEventDiscovery() throws IOException, DavException {
+        checkUsed();
+        if (eventDiscovery != null) {
+            return eventDiscovery;
+        } else {
+            DavException dx = getResponseException();
+            if (dx != null) {
+                throw dx;
+            } else {
+                throw new DavException(getStatusCode(), getName() + " resulted with unexpected status: " + getStatusLine());
+            }
+        }
+    }
+    //---------------------------------------------------------< HttpMethod >---
+    public String getName() {
+        return DavMethods.METHOD_POLL;
+    }
+
+    //------------------------------------------------------< DavMethodBase >---
+    protected boolean isSuccess(int statusCode) {
+        return DavServletResponse.SC_OK == statusCode;
+    }
+
+    //------------------------------------------------------< HttpMethodBase >---
+     /**
+      * Retrieves the DAV:subscriptiondiscovery property present in the response body
+      * and builds 'Subscription' objects from the corresponding DAV:subscription
+      * child elements inside the discovery. If parsing the response body
+      * fails for whatever reason or if the DAV:subscriptiondiscovery did not contain
+      * at least a single DAV:subscription entry (the one created by the SUBSCRIBE
+      * call) this methods in addition resets the 'success' flag to false.
+      *
+      * @param httpState
+      * @param httpConnection
+      * @see HttpMethodBase#processResponseBody(HttpState, HttpConnection)
+      */
+     protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+         // in case of successful response code -> parse xml body discovery object
+         if (getSuccess()) {
+             try {
+                 setSuccess(buildDiscoveryFromRoot(getRootElement()));
+             } catch (IOException e) {
+                 log.error("Error while parsing multistatus response: " + e);
+                 setSuccess(false);
+             }
+         }
+     }
+
+     //------------------------------------------------------------< private >---
+     /**
+      *
+      * @param root
+      * @return
+      */
+     private boolean buildDiscoveryFromRoot(Element root) {
+         if (DomUtil.matches(root, XML_PROP, DavConstants.NAMESPACE) &&
+             DomUtil.hasChildElement(root, XML_EVENTDISCOVERY, ObservationConstants.NAMESPACE)) {
+             Element ld = DomUtil.getChildElement(root, XML_EVENTDISCOVERY, ObservationConstants.NAMESPACE);
+             eventDiscovery = new EventDiscovery();
+
+             ElementIterator it = DomUtil.getChildren(ld, XML_SUBSCRIPTION, ObservationConstants.NAMESPACE);
+             while (it.hasNext()) {
+                 final Element ebElement = it.nextElement();
+                 EventBundle eb = new EventBundle() {
+                     public Element toXml(Document document) {
+                         return (Element) document.importNode(ebElement, true);
+                     }
+                 };
+                 eventDiscovery.addEventBundle(eb);
+             }
+             return true;
+         } else {
+             log.debug("Missing DAV:prop response body in SUBSCRIBE method.");
+         }
+         return false;
+     }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/PollMethod.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java?rev=421206&view=auto
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java (added)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java Wed Jul 12 02:38:25 2006
@@ -0,0 +1,180 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.client.methods;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.webdav.DavMethods;
+import org.apache.jackrabbit.webdav.DavConstants;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.DavException;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
+import org.apache.jackrabbit.webdav.header.DepthHeader;
+import org.apache.jackrabbit.webdav.header.TimeoutHeader;
+import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
+import org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpMethodBase;
+import org.apache.commons.httpclient.HttpState;
+import org.apache.commons.httpclient.HttpConnection;
+import org.w3c.dom.Element;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * <code>SubscribeMethod</code>...
+ */
+public class SubscribeMethod extends DavMethodBase implements ObservationConstants {
+
+    private static Logger log = LoggerFactory.getLogger(SubscribeMethod.class);
+
+    private SubscriptionDiscovery subscriptionDiscovery;
+
+    /**
+     * Create a new <code>SubscribeMethod</code> used to register to the
+     * observation events specified within the given <code>SubscriptionInfo</code>.
+     * See {@link #SubscribeMethod(String, SubscriptionInfo, String) for a
+     * constructor that allows to redefined an existing subscription.
+     *
+     * @param uri
+     * @param subscriptionInfo
+     * @throws IOException
+     */
+    public SubscribeMethod(String uri, SubscriptionInfo subscriptionInfo) throws IOException {
+        this(uri, subscriptionInfo, null);
+    }
+
+    /**
+     * Create a new <code>SubscribeMethod</code> used to register to the
+     * observation events specified within the given <code>SubscriptionInfo</code>.
+     * Note that in contrast to {@link #SubscribeMethod(String, SubscriptionInfo)}
+     * this constructor optionally takes a subscription id identifying a
+     * subscription made before. In this case the subscription will be modified
+     * according to the definitions present in the <code>SubscriptionInfo</code>.
+     * If the id is <code>null</code> this constructor is identical to
+     * {@link #SubscribeMethod(String, SubscriptionInfo)}.
+     *
+     * @param uri
+     * @param subscriptionInfo
+     * @throws IOException
+     */
+    public SubscribeMethod(String uri, SubscriptionInfo subscriptionInfo, String subscriptionId) throws IOException {
+        super(uri);
+        if (subscriptionInfo == null) {
+            throw new IllegalArgumentException("SubscriptionInfo must not be null.");
+        }
+        // optional subscriptionId (only required to modify an existing subscription)
+        if (subscriptionId != null) {
+           setRequestHeader(new CodedUrlHeader(HEADER_SUBSCRIPTIONID, subscriptionId));
+        }
+        // optional timeout header
+        long to = subscriptionInfo.getTimeOut();
+        if (to != DavConstants.UNDEFINED_TIMEOUT) {
+            setRequestHeader(new TimeoutHeader(subscriptionInfo.getTimeOut()));
+        }
+        // always set depth header since value is boolean flag
+        setRequestHeader(new DepthHeader(subscriptionInfo.isDeep()));
+
+        setRequestHeader(DavConstants.HEADER_CONTENT_TYPE, "text/xml; charset=UTF-8");
+       	setRequestBody(subscriptionInfo);
+    }
+
+    public SubscriptionDiscovery getResponseAsSubscriptionDiscovery() throws IOException, DavException {
+        checkUsed();
+        if (subscriptionDiscovery != null) {
+            return subscriptionDiscovery;
+        } else {
+            DavException dx = getResponseException();
+            if (dx != null) {
+                throw dx;
+            } else {
+                throw new DavException(getStatusCode(), getName() + " resulted with unexpected status: " + getStatusLine());
+            }
+        }
+    }
+
+    public String getSubscriptionId() {
+        checkUsed();
+        Header sbHeader = getResponseHeader(HEADER_SUBSCRIPTIONID);
+        if (sbHeader != null) {
+            CodedUrlHeader cuh = new CodedUrlHeader(HEADER_SUBSCRIPTIONID, sbHeader.getValue());
+            return cuh.getCodedUrl();
+        }
+        return null;
+    }
+
+    //---------------------------------------------------------< HttpMethod >---
+    public String getName() {
+        return DavMethods.METHOD_SUBSCRIBE;
+    }
+
+    //------------------------------------------------------< DavMethodBase >---
+    protected boolean isSuccess(int statusCode) {
+        return DavServletResponse.SC_OK == statusCode;
+    }
+
+   //------------------------------------------------------< HttpMethodBase >---
+    /**
+     * Retrieves the DAV:subscriptiondiscovery property present in the response body
+     * and builds 'Subscription' objects from the corresponding DAV:subscription
+     * child elements inside the discovery. If parsing the response body
+     * fails for whatever reason or if the DAV:subscriptiondiscovery did not contain
+     * at least a single DAV:subscription entry (the one created by the SUBSCRIBE
+     * call) this methods in addition resets the 'success' flag to false.
+     *
+     * @param httpState
+     * @param httpConnection
+     * @see HttpMethodBase#processResponseBody(HttpState, HttpConnection)
+     */
+    protected void processResponseBody(HttpState httpState, HttpConnection httpConnection) {
+        // in case of successful response code -> parse xml body discovery object
+        if (getSuccess()) {
+            try {
+                setSuccess(buildDiscoveryFromRoot(getRootElement()));
+            } catch (IOException e) {
+                log.error("Error while parsing multistatus response: " + e);
+                setSuccess(false);
+            }
+        }
+    }
+
+    //------------------------------------------------------------< private >---
+    /**
+     *
+     * @param root
+     * @return
+     */
+    private boolean buildDiscoveryFromRoot(Element root) {
+        if (DomUtil.matches(root, XML_PROP, DavConstants.NAMESPACE) &&
+            DomUtil.hasChildElement(root, SUBSCRIPTIONDISCOVERY.getName(), SUBSCRIPTIONDISCOVERY.getNamespace())) {
+            Element sdElem = DomUtil.getChildElement(root, SUBSCRIPTIONDISCOVERY.getName(), SUBSCRIPTIONDISCOVERY.getNamespace());
+
+            SubscriptionDiscovery sd = SubscriptionDiscovery.createFromXml(sdElem);
+            if (!((List)sd.getValue()).isEmpty()) {
+                subscriptionDiscovery = sd;
+                return true;
+            } else {
+                log.debug("Missing 'subscription' elements in SUBSCRIBE response body. At least a single subscription must be present if SUBSCRIBE was successful.");
+            }
+        } else {
+            log.debug("Missing DAV:prop response body in SUBSCRIBE method.");
+        }
+        return false;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/SubscribeMethod.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Added: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java?rev=421206&view=auto
==============================================================================
--- jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java (added)
+++ jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java Wed Jul 12 02:38:25 2006
@@ -0,0 +1,45 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements.  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.client.methods;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.webdav.DavMethods;
+import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
+
+/**
+ * <code>UnSubscribeMethod</code>...
+ */
+public class UnSubscribeMethod extends DavMethodBase {
+
+    private static Logger log = LoggerFactory.getLogger(UnSubscribeMethod.class);
+
+    public UnSubscribeMethod(String uri, String subscriptionId) {
+        super(uri);
+        setRequestHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, subscriptionId);
+    }
+
+    //---------------------------------------------------------< HttpMethod >---
+    public String getName() {
+        return DavMethods.METHOD_UNSUBSCRIBE;
+    }
+
+    //------------------------------------------------------< DavMethodBase >---
+    protected boolean isSuccess(int statusCode) {
+        return DavServletResponse.SC_NO_CONTENT == statusCode;
+    }
+}
\ No newline at end of file

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jcr-server/client/src/java/org/apache/jackrabbit/webdav/client/methods/UnSubscribeMethod.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java (original)
+++ jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionImpl.java Wed Jul 12 02:38:25 2006
@@ -28,6 +28,7 @@
 import org.apache.jackrabbit.webdav.observation.ObservationResource;
 import org.apache.jackrabbit.webdav.observation.Subscription;
 import org.apache.jackrabbit.webdav.observation.SubscriptionInfo;
+import org.apache.jackrabbit.webdav.observation.DefaultEventType;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -104,6 +105,7 @@
         locator = resource.getLocator();
     }
 
+    //-------------------------------------------------------< Subscription >---
     /**
      * Returns the id of this subscription.
      *
@@ -113,6 +115,7 @@
         return subscriptionId;
     }
 
+    //----------------------------------------------------< XmlSerializable >---
     /**
      * Return the Xml representation of this <code>Subscription</code> as required
      * for the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery}
@@ -130,8 +133,10 @@
         subscr.appendChild(DomUtil.depthToXml(info.isDeep(), document));
         subscr.appendChild(DomUtil.timeoutToXml(info.getTimeOut(), document));
 
-        Element id = DomUtil.addChildElement(subscr, XML_SUBSCRIPTIONID, NAMESPACE);
-        id.appendChild(DomUtil.hrefToXml(subscriptionId, document));
+        if (getSubscriptionId() != null) {
+            Element id = DomUtil.addChildElement(subscr, XML_SUBSCRIPTIONID, NAMESPACE);
+            id.appendChild(DomUtil.hrefToXml(getSubscriptionId(), document));
+        }
         return subscr;
     }
 
@@ -155,11 +160,11 @@
      * @return JCR compliant integer representation of the event types defined
      * for this {@link SubscriptionInfo}.
      */
-    int getEventTypes() throws DavException {
+    int getJcrEventTypes() throws DavException {
         EventType[] eventTypes = info.getEventTypes();
         int events = 0;
         for (int i = 0; i < eventTypes.length; i++) {
-            events |= getEventType(eventTypes[i].getName());
+            events |= getJcrEventType(eventTypes[i]);
         }
         return events;
     }
@@ -223,7 +228,7 @@
      * resource.
      */
     boolean isSubscribedToResource(ObservationResource resource) {
-        return locator.equals(resource.getLocator());
+        return locator.getResourcePath().equals(resource.getResourcePath());
     }
 
     /**
@@ -274,47 +279,71 @@
 
     //--------------------------------------------------------------------------
     /**
-     * Static utility method in order to convert the types defined by
-     * {@link javax.jcr.observation.Event} into their Xml representation.
-     *
-     * @param eventType The jcr event type
-     * @return Xml representation of the event type.
-     */
-    private static String getEventName(int eventType) {
-        String eventName;
-        switch (eventType) {
+     * Static utility method to convert the type defined by a
+     * {@link javax.jcr.observation.Event JCR event} into an <code>EventType</code>
+     * object.
+     *
+     * @param jcrEventType
+     * @return <code>EventType</code> representation of the given JCR event type.
+     * @throws IllegalArgumentException if the given int does not represent a
+     * valid type constants as defined by {@link Event}.<br>
+     * Valid values are
+     * <ul>
+     * <li>{@link Event#NODE_ADDED}</li>
+     * <li>{@link Event#NODE_REMOVED}</li>
+     * <li>{@link Event#PROPERTY_ADDED}</li>
+     * <li>{@link Event#PROPERTY_REMOVED}</li>
+     * <li>{@link Event#PROPERTY_CHANGED}</li>
+     * </ul>
+     */
+    public static EventType getEventType(int jcrEventType) {
+        String localName;
+        switch (jcrEventType) {
             case Event.NODE_ADDED:
-                eventName = EVENT_NODEADDED;
+                localName = EVENT_NODEADDED;
                 break;
             case Event.NODE_REMOVED:
-                eventName = EVENT_NODEREMOVED;
+                localName = EVENT_NODEREMOVED;
                 break;
             case Event.PROPERTY_ADDED:
-                eventName = EVENT_PROPERTYADDED;
+                localName = EVENT_PROPERTYADDED;
                 break;
             case Event.PROPERTY_CHANGED:
-                eventName = EVENT_PROPERTYCHANGED;
+                localName = EVENT_PROPERTYCHANGED;
                 break;
-            default:
-                //Event.PROPERTY_REMOVED:
-                eventName = EVENT_PROPERTYREMOVED;
+            case Event.PROPERTY_REMOVED:
+                localName = EVENT_PROPERTYREMOVED;
                 break;
+            default: // no default
+                throw new IllegalArgumentException("Invalid JCR event type: " + jcrEventType);
         }
-        return eventName;
+        return DefaultEventType.create(localName, NAMESPACE);
     }
 
     /**
-     * Static utility method to convert an event type name as present in the
-     * Xml request body into the corresponding constant defined by
+     * Static utility method to convert an <code>EventType</code> as present in
+     * the Xml body into the corresponding JCR event constant defined by
      * {@link javax.jcr.observation.Event}.
      *
-     * @param eventName
-     * @return event type as defined by {@link javax.jcr.observation.Event}.
-     * @throws DavException if the given element cannot be translated
-     * to any of the events defined by {@link javax.jcr.observation.Event}.
-     */
-    private static int getEventType(String eventName) throws DavException {
+     * @param eventType
+     * @return Any of the event types defined by {@link Event}.<br>
+     * Possible values are
+     * <ul>
+     * <li>{@link Event#NODE_ADDED}</li>
+     * <li>{@link Event#NODE_REMOVED}</li>
+     * <li>{@link Event#PROPERTY_ADDED}</li>
+     * <li>{@link Event#PROPERTY_REMOVED}</li>
+     * <li>{@link Event#PROPERTY_CHANGED}</li>
+     * </ul>
+     * @throws DavException if the given event type does not define a valid
+     * JCR event type, such as returned by {@link #getEventType(int)}.
+     */
+    public static int getJcrEventType(EventType eventType) throws DavException {
+        if (eventType == null || !NAMESPACE.equals(eventType.getNamespace())) {
+            throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Invalid JCR event type: "+ eventType + ": Namespace mismatch.");
+        }
         int eType;
+        String eventName = eventType.getName();
         if (EVENT_NODEADDED.equals(eventName)) {
             eType = Event.NODE_ADDED;
         } else if (EVENT_NODEREMOVED.equals(eventName)) {
@@ -364,12 +393,11 @@
                 eventElem.appendChild(DomUtil.hrefToXml(eHref, document));
                 // eventtype
                 Element eType = DomUtil.addChildElement(eventElem, XML_EVENTTYPE, NAMESPACE);
-                DomUtil.addChildElement(eType, getEventName(event.getType()), NAMESPACE);
+                eType.appendChild(getEventType(event.getType()).toXml(document));
                 // user id
                 DomUtil.addChildElement(eventElem, XML_EVENTUSERID, NAMESPACE, event.getUserID());
             }
             return bundle;
         }
-
     }
-}
\ No newline at end of file
+}

Modified: jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionManagerImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionManagerImpl.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionManagerImpl.java (original)
+++ jackrabbit/trunk/jcr-server/server/src/java/org/apache/jackrabbit/webdav/jcr/observation/SubscriptionManagerImpl.java Wed Jul 12 02:38:25 2006
@@ -29,6 +29,8 @@
 import org.apache.jackrabbit.webdav.observation.SubscriptionManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.Session;
@@ -54,14 +56,16 @@
     private final SubscriptionMap subscriptions = new SubscriptionMap();
 
     /**
-     * Retrieve the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery} object for the given
-     * resource. Note, that the discovery object will be empty if there are
-     * no subscriptions present.
+     * Retrieve the {@link org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery}
+     * object for the given resource. Note, that the discovery object will be empty
+     * if there are no subscriptions present.<br>
+     * Note that all subscriptions present on the given resource are returned.
+     * However, the subscription id will not be visible in order to avoid abuse
+     * by clients not having registered the subscription originally.
      *
      * @param resource
      */
     public SubscriptionDiscovery getSubscriptionDiscovery(ObservationResource resource) {
-        // todo: is it correct to return subscriptions made by another session?
         Subscription[] subsForResource = subscriptions.getByPath(resource.getLocator());
         return new SubscriptionDiscovery(subsForResource);
     }
@@ -80,20 +84,23 @@
                                   ObservationResource resource)
             throws DavException {
 
-        SubscriptionImpl subscription;
+        Subscription subscription;
         if (subscriptionId == null) {
             // new subscription
-            subscription = new SubscriptionImpl(info, resource);
-            registerSubscription(subscription, resource);
+            SubscriptionImpl newSubs = new SubscriptionImpl(info, resource);
+            registerSubscription(newSubs, resource);
 
             // ajust references to this subscription
-            subscriptions.put(subscription.getSubscriptionId(), subscription);
-            resource.getSession().addReference(subscription.getSubscriptionId());
+            subscriptions.put(newSubs.getSubscriptionId(), newSubs);
+            resource.getSession().addReference(newSubs.getSubscriptionId());
+            subscription = newSubs;
         } else {
             // refresh/modify existing one
-            subscription = validate(subscriptionId, resource);
-            subscription.setInfo(info);
-            registerSubscription(subscription, resource);
+            SubscriptionImpl existing = validate(subscriptionId, resource);
+            existing.setInfo(info);
+            registerSubscription(existing, resource);
+
+            subscription = new WrappedSubscription(existing);
         }
         return subscription;
     }
@@ -112,7 +119,7 @@
             Session session = getRepositorySession(resource);
             ObservationManager oMgr = session.getWorkspace().getObservationManager();
             String itemPath = subscription.getLocator().getRepositoryPath();
-            oMgr.addEventListener(subscription, subscription.getEventTypes(),
+            oMgr.addEventListener(subscription, subscription.getJcrEventTypes(),
                     itemPath, subscription.isDeep(),
                     subscription.getUuidFilters(),
                     subscription.getNodetypeNameFilters(),
@@ -220,6 +227,31 @@
         return JcrDavSession.getRepositorySession(resource.getSession());
     }
 
+    //----------------------------------------------< private inner classes >---
+    /**
+     * Private inner class wrapping around an <code>Subscription</code> as
+     * present in the internal map. This allows to hide the subscription Id
+     * from other sessions, that did create the subscription.
+     */
+    private class WrappedSubscription implements Subscription {
+
+        private final Subscription delegatee;
+
+        private WrappedSubscription(Subscription subsc) {
+            this.delegatee = subsc;
+        }
+
+        public String getSubscriptionId() {
+            // always return null, since the subscription id must not be exposed
+            // but to the client, that created the subscription.
+            return null;
+        }
+
+        public Element toXml(Document document) {
+            return delegatee.toXml(document);
+        }
+    }
+
     /**
      * Private inner class <code>SubscriptionMap</code> that allows for quick
      * access by resource path as well as by subscription id.
@@ -264,7 +296,8 @@
                 Subscription[] subsForResource = new Subscription[idSet.size()];
                 int i = 0;
                 while (idIterator.hasNext()) {
-                    subsForResource[i] = (Subscription) subscriptions.get(idIterator.next());
+                    SubscriptionImpl s = (SubscriptionImpl) subscriptions.get(idIterator.next());
+                    subsForResource[i] = new WrappedSubscription(s);
                 }
                 return subsForResource;
             } else {

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/WebdavResponseImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/WebdavResponseImpl.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/WebdavResponseImpl.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/WebdavResponseImpl.java Wed Jul 12 02:38:25 2006
@@ -21,9 +21,12 @@
 import org.apache.jackrabbit.webdav.observation.EventDiscovery;
 import org.apache.jackrabbit.webdav.observation.Subscription;
 import org.apache.jackrabbit.webdav.observation.SubscriptionDiscovery;
+import org.apache.jackrabbit.webdav.observation.ObservationConstants;
 import org.apache.jackrabbit.webdav.property.DavPropertySet;
 import org.apache.jackrabbit.webdav.xml.XmlSerializable;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.header.CodedUrlHeader;
+import org.apache.jackrabbit.webdav.header.Header;
 import org.apache.xml.serialize.OutputFormat;
 import org.apache.xml.serialize.XMLSerializer;
 import org.slf4j.Logger;
@@ -111,8 +114,10 @@
      * @see DavServletResponse#sendLockResponse(org.apache.jackrabbit.webdav.lock.ActiveLock)
      */
     public void sendLockResponse(ActiveLock lock) throws IOException {
-        httpResponse.setHeader(DavConstants.HEADER_LOCK_TOKEN, "<" + lock.getToken() + ">");
-	DavPropertySet propSet = new DavPropertySet();
+        CodedUrlHeader ltHeader = new CodedUrlHeader(DavConstants.HEADER_LOCK_TOKEN, lock.getToken());
+        httpResponse.setHeader(ltHeader.getHeaderName(), ltHeader.getHeaderValue());
+
+        DavPropertySet propSet = new DavPropertySet();
         propSet.add(new LockDiscovery(lock));
         sendXmlResponse(propSet, SC_OK);
     }
@@ -172,7 +177,12 @@
      * @see org.apache.jackrabbit.webdav.observation.ObservationDavServletResponse#sendSubscriptionResponse(org.apache.jackrabbit.webdav.observation.Subscription)
      */
     public void sendSubscriptionResponse(Subscription subscription) throws IOException {
-	DavPropertySet propSet = new DavPropertySet();
+        String id = subscription.getSubscriptionId();
+        if (id != null) {
+            Header h = new CodedUrlHeader(ObservationConstants.HEADER_SUBSCRIPTIONID, id);
+            httpResponse.setHeader(h.getHeaderName(), h.getHeaderValue());
+        }
+        DavPropertySet propSet = new DavPropertySet();
         propSet.add(new SubscriptionDiscovery(subscription));
         sendXmlResponse(propSet, SC_OK);
     }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/header/TimeoutHeader.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/header/TimeoutHeader.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/header/TimeoutHeader.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/header/TimeoutHeader.java Wed Jul 12 02:38:25 2006
@@ -48,20 +48,34 @@
     }
 
     /**
-     * Parse the request timeout header and convert the timeout value
+     * Parses the request timeout header and converts it into a new
+     * <code>TimeoutHeader</code> object.<br>The default value is used as
+     * fallback if the String is not parseable.
+     *
+     * @param request
+     * @param defaultValue
+     * @return a new TimeoutHeader object.
+     */
+    public static TimeoutHeader parse(HttpServletRequest request, long defaultValue) {
+        String timeoutStr = request.getHeader(HEADER_TIMEOUT);
+        long timeout = parse(timeoutStr, defaultValue);
+        return new TimeoutHeader(timeout);
+    }
+
+    /**
+     * Parses the given timeout String and converts the timeout value
      * into a long indicating the number of milliseconds until expiration time
      * is reached.<br>
-     * NOTE: If the requested timeout is 'infinite' {@link Long.MAX_VALUE}
-     * is returned. If the header is missing or is in an invalid format that
-     * cannot be parsed, the default value is returned.
+     * NOTE: If the timeout String equals to {@link #TIMEOUT_INFINITE 'infinite'}
+     * {@link Long.MAX_VALUE} is returned. If the Sting is invalid or is in an
+     * invalid format that cannot be parsed, the default value is returned.
      *
-     * @param request
+     * @param timeoutStr
      * @param defaultValue
      * @return long representing the timeout present in the header or the default
      * value if the header is missing or could not be parsed.
      */
-    public static TimeoutHeader parse(HttpServletRequest request, long defaultValue) {
-        String timeoutStr = request.getHeader(HEADER_TIMEOUT);
+    public static long parse(String timeoutStr, long defaultValue) {
         long timeout = defaultValue;
         if (timeoutStr != null && timeoutStr.length() > 0) {
             int secondsInd = timeoutStr.indexOf("Second-");
@@ -81,6 +95,6 @@
                 timeout = INFINITE_TIMEOUT;
             }
         }
-        return new TimeoutHeader(timeout);
+        return timeout;
     }
 }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/lock/LockDiscovery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/lock/LockDiscovery.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/lock/LockDiscovery.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/lock/LockDiscovery.java Wed Jul 12 02:38:25 2006
@@ -18,6 +18,9 @@
 
 import org.apache.jackrabbit.webdav.property.AbstractDavProperty;
 import org.apache.jackrabbit.webdav.property.DavPropertyName;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.ElementIterator;
+import org.apache.jackrabbit.webdav.header.TimeoutHeader;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
@@ -104,4 +107,116 @@
 	return lockdiscovery;
     }
 
+    //---------------------------------------------------< factory from xml >---
+    /**
+     * Builds a new <code>LockDiscovery</code> object from the given xml element.
+     *
+     * @param lockDiscoveryElement
+     * @return
+     * @throws IllegalArgumentException if the given xml element is not a
+     * DAV:lockdiscovery element.
+     */
+    public static LockDiscovery createFromXml(Element lockDiscoveryElement) {
+        if (!DomUtil.matches(lockDiscoveryElement, PROPERTY_LOCKDISCOVERY, NAMESPACE)) {
+            throw new IllegalArgumentException("DAV:lockdiscovery element expected.");
+        }
+
+        List activeLocks = new ArrayList();
+        ElementIterator it = DomUtil.getChildren(lockDiscoveryElement, XML_ACTIVELOCK, NAMESPACE);
+        while (it.hasNext()) {
+            Element al = it.nextElement();
+            activeLocks.add(new ALockImpl(al));
+        }
+
+        return new LockDiscovery((ActiveLock[]) activeLocks.toArray(new ActiveLock[activeLocks.size()]));
+    }
+
+    //------< inner class >-----------------------------------------------------
+    /**
+     * Simple implementation of <code>ActiveLock</code> interface, that retrieves
+     * the values from the DAV:activelock XML element.<br>
+     * Note, that all set-methods as well as {@link #isExpired()} are not
+     * implemented.
+     */
+    private static class ALockImpl implements ActiveLock {
+
+        private final Element alElement;
+
+        private ALockImpl(Element alElement) {
+            if (!DomUtil.matches(alElement, XML_ACTIVELOCK, NAMESPACE)) {
+                throw new IllegalArgumentException("DAV:activelock element expected.");
+            }
+            this.alElement = alElement;
+        }
+
+        public boolean isLockedByToken(String lockToken) {
+            String lt = getToken();
+            if (lt == null) {
+                return false;
+            } else {
+                return lt.equals(lockToken);
+            }
+        }
+
+        public boolean isExpired() {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+
+        public String getToken() {
+            Element ltEl = DomUtil.getChildElement(alElement, XML_LOCKTOKEN, NAMESPACE);
+            if (ltEl != null) {
+                return DomUtil.getChildText(alElement, XML_HREF, NAMESPACE);
+            }
+            return null;
+        }
+
+        public String getOwner() {
+            String owner = null;
+            Element ow = DomUtil.getChildElement(alElement, XML_OWNER, NAMESPACE);
+            if (ow != null) {
+                if (DomUtil.hasChildElement(ow, XML_HREF, NAMESPACE)) {
+                    owner = DomUtil.getChildTextTrim(ow, XML_HREF, NAMESPACE);
+                } else {
+                    owner = DomUtil.getTextTrim(ow);
+                }
+            }
+            return owner;
+        }
+
+        public void setOwner(String owner) {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+
+        public long getTimeout() {
+            // get timeout string. if no DAV:timeout element is present,
+            // 't' will be 'null' and the undefined timeout will be returned.
+            String t = DomUtil.getChildTextTrim(alElement, XML_TIMEOUT, NAMESPACE);
+            return TimeoutHeader.parse(t, UNDEFINED_TIMEOUT);
+        }
+
+        public void setTimeout(long timeout) {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+
+        public boolean isDeep() {
+            String depth = DomUtil.getChildTextTrim(alElement, XML_DEPTH, NAMESPACE);
+            return DEPTH_INFINITY_S.equalsIgnoreCase(depth);
+        }
+
+        public void setIsDeep(boolean isDeep) {
+            throw new UnsupportedOperationException("Not implemented");
+        }
+
+        public Type getType() {
+            return Type.createFromXml(DomUtil.getChildElement(alElement, XML_LOCKTYPE, NAMESPACE));
+        }
+
+        public Scope getScope() {
+            return Scope.createFromXml(DomUtil.getChildElement(alElement, XML_LOCKSCOPE, NAMESPACE));
+        }
+
+        public Element toXml(Document document) {
+            return (Element) document.importNode(alElement, true);
+        }
+    }
 }

Added: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java?rev=421206&view=auto
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java (added)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java Wed Jul 12 02:38:25 2006
@@ -0,0 +1,130 @@
+/*
+ * $URL$
+ * $Id$
+ *
+ * Copyright 1997-2005 Day Management AG
+ * Barfuesserplatz 6, 4001 Basel, Switzerland
+ * All Rights Reserved.
+ *
+ * This software is the confidential and proprietary information of
+ * Day Management AG, ("Confidential Information"). You shall not
+ * disclose such Confidential Information and shall use it only in
+ * accordance with the terms of the license agreement you entered into
+ * with Day.
+ */
+package org.apache.jackrabbit.webdav.observation;
+
+import org.apache.jackrabbit.webdav.xml.Namespace;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.XmlSerializable;
+import org.apache.jackrabbit.webdav.xml.ElementIterator;
+import org.w3c.dom.Element;
+import org.w3c.dom.Document;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * <code>DefaultEventType</code> defines a simple EventType implementation that
+ * only consists of a qualified event name consisting of namespace plus local
+ * name. 
+ */
+public class DefaultEventType implements EventType {
+
+    private static final Map eventTypes = new HashMap();
+
+    private final String localName;
+    private final Namespace namespace;
+
+    /**
+     * Avoid instanciation of <code>DefaultEventType</code>. Since the amount
+     * of available (and registered) events is considered to be limited, the
+     * static {@link #create(String, Namespace) method is defined.
+     *
+     * @param localName
+     * @param namespace
+     */
+    private DefaultEventType(String localName, Namespace namespace) {
+        this.localName = localName;
+        this.namespace = namespace;
+    }
+
+    /**
+     * Factory method to create a new <code>EventType</code>.
+     *
+     * @param localName
+     * @param namespace
+     * @return
+     */
+    public static EventType create(String localName, Namespace namespace) {
+        if (localName == null || "".equals(localName)) {
+            throw new IllegalArgumentException("null and '' are not valid local names of an event type.");
+        }
+        String key = DomUtil.getQualifiedName(localName, namespace);
+        if (eventTypes.containsKey(key)) {
+            return (EventType) eventTypes.get(key);
+        } else {
+            EventType type = new DefaultEventType(localName, namespace);
+            eventTypes.put(key, type);
+            return type;
+        }
+    }
+
+    /**
+     * Retrieves one or multiple <code>EventType</code>s from the 'eventtype'
+     * Xml element. While a subscription may register multiple types (thus
+     * the 'eventtype' contains multiple child elements), a single event may only
+     * refer to one single type.
+     *
+     * @param eventType
+     * @return
+     */
+    public static EventType[] createFromXml(Element eventType) {
+        if (!DomUtil.matches(eventType, ObservationConstants.XML_EVENTTYPE, ObservationConstants.NAMESPACE)) {
+            throw new IllegalArgumentException("'eventtype' element expected which contains a least a single child element.");
+        }
+
+        List etypes = new ArrayList();
+        ElementIterator it = DomUtil.getChildren(eventType);
+        while (it.hasNext()) {
+            Element el = it.nextElement();
+            etypes.add(create(el.getLocalName(), DomUtil.getNamespace(el)));
+        }
+        return (EventType[]) etypes.toArray(new EventType[etypes.size()]);
+    }
+
+    //----------------------------------------------------------< EventType >---
+    /**
+     * @see EventType#getName()
+     */
+    public String getName() {
+        return localName;
+    }
+
+    /**
+     * @see EventType#getNamespace()
+     */
+    public Namespace getNamespace() {
+        return namespace;
+    }
+
+    //----------------------------------------------------< XmlSerializable >---
+    /**
+     * Returns a single empty Xml element where namespace and local name of this
+     * event type define the elements name.
+     * <pre>
+     * EventType.create("someevent", Namespace.getNamespace("F", "http://www.foo.bar/eventtypes"));
+     *
+     * returns the following element upon call of toXml:
+     *
+     * &lt;F:someevent xmlns:F="http://www.foo.bar/eventtypes" /&gt;
+     * </pre>
+     *
+     * @see XmlSerializable#toXml(Document)
+     */
+    public Element toXml(Document document) {
+        return DomUtil.createElement(document, localName, namespace);
+    }
+}

Propchange: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/DefaultEventType.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/EventDiscovery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/EventDiscovery.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/EventDiscovery.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/EventDiscovery.java Wed Jul 12 02:38:25 2006
@@ -37,7 +37,7 @@
 
     private static Logger log = LoggerFactory.getLogger(EventDiscovery.class);
 
-    private List bundles = new ArrayList();
+    private final List bundles = new ArrayList();
 
     /**
      * Add the Xml representation of an single 'eventBundle' listing the
@@ -54,6 +54,27 @@
     }
 
     /**
+     * Returns an iterator over the {@link EventBundle event bundles} currently
+     * present on this discovery.
+     *
+     * @return iterator over event bundles present.
+     */
+    public Iterator getEventBundles() {
+        return bundles.iterator();
+    }
+
+    /**
+     * Returns true, if this event discovery does not report any events (thus
+     * {@link #getEventBundles()} would return an empty iterator.
+     *
+     * @return true if {@link #getEventBundles()} would return an empty iterator,
+     * false otherwise.
+     */
+    public boolean isEmpty() {
+        return bundles.isEmpty();
+    }
+
+    /**
      * Returns the Xml representation of this <code>EventDiscovery</code> as
      * being present in the POLL response body.
      *
@@ -63,12 +84,11 @@
      */
     public Element toXml(Document document) {
         Element ed = DomUtil.createElement(document, XML_EVENTDISCOVERY, NAMESPACE);
-        Iterator it = bundles.iterator();
+        Iterator it = getEventBundles();
         while (it.hasNext()) {
             EventBundle bundle = (EventBundle)it.next();
             ed.appendChild(bundle.toXml(document));
         }
         return ed;
     }
-
 }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionDiscovery.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionDiscovery.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionDiscovery.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionDiscovery.java Wed Jul 12 02:38:25 2006
@@ -17,9 +17,16 @@
 package org.apache.jackrabbit.webdav.observation;
 
 import org.apache.jackrabbit.webdav.property.AbstractDavProperty;
+import org.apache.jackrabbit.webdav.xml.DomUtil;
+import org.apache.jackrabbit.webdav.xml.ElementIterator;
+import org.apache.jackrabbit.webdav.xml.XmlSerializable;
+import org.apache.jackrabbit.webdav.DavConstants;
 import org.w3c.dom.Document;
 import org.w3c.dom.Element;
 
+import java.util.List;
+import java.util.ArrayList;
+
 /**
  * <code>SubscriptionDiscovery</code> encapsulates the 'subscriptiondiscovery'
  * property of a webdav resource.
@@ -79,4 +86,38 @@
         return elem;
     }
 
+    //-----------------------------------------------------< static Factory >---
+    public static SubscriptionDiscovery createFromXml(Element sDiscoveryElement) {
+        if (!DomUtil.matches(sDiscoveryElement, ObservationConstants.SUBSCRIPTIONDISCOVERY.getName(), ObservationConstants.SUBSCRIPTIONDISCOVERY.getNamespace())) {
+            throw new IllegalArgumentException("'subscriptiondiscovery' element expected.");
+        }
+
+        List subscriptions = new ArrayList();
+        ElementIterator it = DomUtil.getChildren(sDiscoveryElement, ObservationConstants.XML_SUBSCRIPTION, ObservationConstants.NAMESPACE);
+        while (it.hasNext()) {
+            final Element sb = it.nextElement();
+            // anonymous inner class: Subscription interface
+            Subscription s = new Subscription() {
+                /**
+                 * @see Subscription#getSubscriptionId()
+                 */
+                public String getSubscriptionId() {
+                    Element ltEl = DomUtil.getChildElement(sb, ObservationConstants.XML_SUBSCRIPTIONID, ObservationConstants.NAMESPACE);
+                    if (ltEl != null) {
+                        return DomUtil.getChildText(sb, DavConstants.XML_HREF, DavConstants.NAMESPACE);
+                    }
+                    return null;
+                }
+                /**
+                 * @see XmlSerializable#toXml(Document)
+                 */
+                public Element toXml(Document document) {
+                    return (Element) document.importNode(sb, true);
+                }
+            };
+            subscriptions.add(s);
+        }
+
+        return new SubscriptionDiscovery((Subscription[]) subscriptions.toArray(new Subscription[subscriptions.size()]));
+    }
 }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionInfo.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionInfo.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/observation/SubscriptionInfo.java Wed Jul 12 02:38:25 2006
@@ -18,6 +18,8 @@
 
 import org.apache.jackrabbit.webdav.DavException;
 import org.apache.jackrabbit.webdav.DavServletResponse;
+import org.apache.jackrabbit.webdav.header.TimeoutHeader;
+import org.apache.jackrabbit.webdav.header.DepthHeader;
 import org.apache.jackrabbit.webdav.xml.DomUtil;
 import org.apache.jackrabbit.webdav.xml.ElementIterator;
 import org.apache.jackrabbit.webdav.xml.Namespace;
@@ -98,11 +100,16 @@
     }
 
     /**
-     * Create a new <code>SubscriptionInfo</code>
-     *
+     * Create a new <code>SubscriptionInfo</code> from the given Xml element
+     * and from additional information that is transported within the request
+     * header:
+     * <ul>
+     * <li>{@link TimeoutHeader timeout},</li>
+     * <li>{@link DepthHeader isDeep}</li>
+     * </ul>
      * @param reqInfo Xml element present in the request body.
-     * @param timeout as defined by the {@link org.apache.jackrabbit.webdav.DavConstants#HEADER_TIMEOUT timeout header}.
-     * @param isDeep as defined by the {@link org.apache.jackrabbit.webdav.DavConstants#HEADER_DEPTH depth header}.
+     * @param timeout as defined in the {@link org.apache.jackrabbit.webdav.DavConstants#HEADER_TIMEOUT timeout header}.
+     * @param isDeep as defined in the {@link org.apache.jackrabbit.webdav.DavConstants#HEADER_DEPTH depth header}.
      * @throws IllegalArgumentException if the reqInfo element does not contain the mandatory elements.
      */
     public SubscriptionInfo(Element reqInfo, long timeout, boolean isDeep) throws DavException {
@@ -110,26 +117,18 @@
             log.warn("Element with name 'subscriptioninfo' expected");
             throw new DavException(DavServletResponse.SC_BAD_REQUEST);
         }
-        List typeList = new ArrayList();
         Element el = DomUtil.getChildElement(reqInfo, XML_EVENTTYPE, NAMESPACE);
         if (el != null) {
-            ElementIterator it = DomUtil.getChildren(el);
-            while (it.hasNext()) {
-                Element typeElem = it.nextElement();
-                EventType et = new SimpleEventType(typeElem.getLocalName(), DomUtil.getNamespace(typeElem));
-                typeList.add(et);
+            eventTypes = DefaultEventType.createFromXml(el);
+            if (eventTypes.length == 0) {
+                log.warn("'subscriptioninfo' must at least indicate a single, valid event type.");
+                throw new DavException(DavServletResponse.SC_BAD_REQUEST);
             }
         } else {
             log.warn("'subscriptioninfo' must contain an 'eventtype' child element.");
             throw new DavException(DavServletResponse.SC_BAD_REQUEST);
         }
 
-        if (typeList.isEmpty()) {
-            log.warn("'subscriptioninfo' must at least indicate a single event type.");
-            throw new DavException(DavServletResponse.SC_BAD_REQUEST);
-        }
-        eventTypes = (EventType[]) typeList.toArray(new EventType[typeList.size()]);
-
         List filters = new ArrayList();
         el = DomUtil.getChildElement(reqInfo, XML_FILTER, NAMESPACE);
         if (el != null) {
@@ -239,33 +238,5 @@
             DomUtil.addChildElement(subscrInfo, XML_NOLOCAL, NAMESPACE);
         }
         return subscrInfo;
-    }
-
-    //--------------------------------------------------------< inner class >---
-    /**
-     * Simple EventType implementation that only consists of a qualified event
-     * name.
-     */
-    private class SimpleEventType implements EventType {
-
-        private String localName;
-        private Namespace namespace;
-
-        SimpleEventType(String localName, Namespace namespace) {
-            this.localName = localName;
-            this.namespace = namespace;
-        }
-
-        public Element toXml(Document document) {
-            return DomUtil.createElement(document, localName, namespace);
-        }
-
-        public String getName() {
-            return localName;
-        }
-
-        public Namespace getNamespace() {
-            return namespace;
-        }
     }
 }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/MergeInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/MergeInfo.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/MergeInfo.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/MergeInfo.java Wed Jul 12 02:38:25 2006
@@ -154,4 +154,29 @@
         return elem;
     }
 
+
+    /**
+     * Factory method to create a minimal DAV:merge element to create a new
+     * <code>MergeInfo</code> object.
+     *
+     * @param mergeSource
+     * @param isNoAutoMerge
+     * @param isNoCheckout
+     * @param factory
+     * @return
+     */
+    public static Element createMergeElement(String[] mergeSource, boolean isNoAutoMerge, boolean isNoCheckout, Document factory) {
+        Element mergeElem = DomUtil.createElement(factory, XML_MERGE, NAMESPACE);
+        Element source = DomUtil.addChildElement(mergeElem, DavConstants.XML_SOURCE, DavConstants.NAMESPACE);
+        for (int i = 0; i < mergeSource.length; i++) {
+            DomUtil.addChildElement(source, DavConstants.XML_HREF, DavConstants.NAMESPACE, mergeSource[i]);
+        }
+        if (isNoAutoMerge) {
+            DomUtil.addChildElement(mergeElem, XML_N0_AUTO_MERGE, NAMESPACE);
+        }
+        if (isNoCheckout) {
+            DomUtil.addChildElement(mergeElem, XML_N0_CHECKOUT, NAMESPACE);
+        }
+        return mergeElem;
+    }
 }

Modified: jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/UpdateInfo.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/UpdateInfo.java?rev=421206&r1=421205&r2=421206&view=diff
==============================================================================
--- jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/UpdateInfo.java (original)
+++ jackrabbit/trunk/jcr-server/webdav/src/java/org/apache/jackrabbit/webdav/version/UpdateInfo.java Wed Jul 12 02:38:25 2006
@@ -65,8 +65,8 @@
     public static final int UPDATE_BY_WORKSPACE = 2;
 
     private Element updateElement;
-    private DavPropertyNameSet propertyNameSet = new DavPropertyNameSet();
 
+    private DavPropertyNameSet propertyNameSet = new DavPropertyNameSet();
     private String[] source;
     private int type;
 
@@ -192,24 +192,45 @@
         if (updateElement != null) {
             elem = (Element)document.importNode(updateElement, true);
         } else {
-            elem = DomUtil.createElement(document, XML_UPDATE, NAMESPACE);
-            switch (type) {
-                case UPDATE_BY_VERSION:
-                    Element vE = DomUtil.addChildElement(elem, XML_VERSION, NAMESPACE);
-                    for (int i = 0; i < source.length; i++) {
-                        vE.appendChild(DomUtil.hrefToXml(source[i], document));
-                    }
-                    break;
-                case UPDATE_BY_LABEL:
-                    DomUtil.addChildElement(elem, XML_LABEL_NAME, NAMESPACE, source[0]);
-                    break;
-                case UPDATE_BY_WORKSPACE:
-                    DomUtil.addChildElement(elem, XML_WORKSPACE, NAMESPACE, source[0]);
-                // no default.
-            }
+            elem = createUpdateElement(source, type, document);
         }
         if (!propertyNameSet.isEmpty()) {
             elem.appendChild(propertyNameSet.toXml(document));
+        }
+        return elem;
+    }
+
+    /**
+     * Factory method to create the basic structure of an <code>UpdateInfo</code>
+     * object.
+     *
+     * @param updateSource
+     * @param updateType
+     * @param factory
+     * @return
+     */
+    public static Element createUpdateElement(String[] updateSource, int updateType, Document factory) {
+        if (updateSource == null || updateSource.length == 0) {
+            throw new IllegalArgumentException("Update source must specific at least a single resource used to run the update.");
+        }
+
+        Element elem = DomUtil.createElement(factory, XML_UPDATE, NAMESPACE);
+        switch (updateType) {
+            case UPDATE_BY_VERSION:
+                Element vE = DomUtil.addChildElement(elem, XML_VERSION, NAMESPACE);
+                for (int i = 0; i < updateSource.length; i++) {
+                    vE.appendChild(DomUtil.hrefToXml(updateSource[i], factory));
+                }
+                break;
+            case UPDATE_BY_LABEL:
+                DomUtil.addChildElement(elem, XML_LABEL_NAME, NAMESPACE, updateSource[0]);
+                break;
+            case UPDATE_BY_WORKSPACE:
+                DomUtil.addChildElement(elem, XML_WORKSPACE, NAMESPACE, updateSource[0]);
+                break;
+            // no default.
+            default:
+                throw new IllegalArgumentException("Invalid update type: " + updateType);
         }
         return elem;
     }