/*
* Copyright 2015 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* 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.eclipse.webdav.client;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import org.eclipse.webdav.*;
import org.eclipse.webdav.dom.*;
import org.eclipse.webdav.internal.kernel.*;
import org.eclipse.webdav.internal.kernel.utils.Assert;
import org.eclipse.webdav.internal.kernel.utils.EmptyEnumeration;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* The <code>AbstractResourceHandle</code> class is the abstract
* superclass for all types of resource references. A resource
* handle is a client-side 'proxy' for the server resource. Instances
* of handle classes understand the methods appropriate for the
* corresponding server resource and provide a convenient high-level
* API for sending WebDAV methods to the server to manipulate
* the resource.
* <p>
* It is certainly posible to create a stale or invalid handle in
* numerous ways. For example, the existance of a handle does not
* imply the existance of a corresponding server resource (indeed
* resource can be created by sending create() to a handle, or deleted
* using delete() -- so the life cycles are not coupled. It is also
* possible to create, say, a collection handle on a regular resource
* and invoke invalid WebDAV methods. These will typically result
* in an exception.</p>
* <p>
* The API on these classes are intended to convenience methods for
* the most common operations on server resources. They make some
* assumptions about the way you want to receive the results. To
* get finer (but possibly less convenient) control over the WebDAV
* methods use the <code>Server</code> interface of <code>DAVClient
* </code> directly.</p>
*/
public abstract class AbstractResourceHandle implements WebDAVPropertyNames, WebDAVPropertyValues {
// The DAVClient that is used to access the WebDAV server.
// Given during initialization, this object contains the
// authentication and proxy information etc.
protected DAVClient davClient;
// The Locator represents the universal identifier of the server
// resource.
protected ILocator locator;
/**
* Creates a new <code>AbstractResourceHandle</code> with the given
* DAV client and <code>Locator</code>.
*
* @param davClient the <code>DAVClient</code> that contains the server
* reference and proxy/authentication information.
* @param locator the <code>Locator</code> identity of the resource.
*/
public AbstractResourceHandle(DAVClient davClient, ILocator locator) {
Assert.isNotNull(davClient);
Assert.isNotNull(locator);
this.davClient = davClient;
this.locator = locator;
}
/**
* Answer a new collection handle on the same underlying server resource.
* Since the handle represents a means of accessing the resource, it is valid to
* consider a collection resource as a collection or regular resource depending
* upon how it is being accessed. Note that not all resources have collection
* semantics.
*
* @return an equivalent collection handle on the resource.
*/
public CollectionHandle asCollectionHandle() {
return new CollectionHandle(davClient, locator);
}
/**
* Answer a new resource handle on the same underlying server resource.
* Since the handle represents a means of accessing the resource, it is valid to
* consider a collection resource as a collection or regular resource depending
* upon how it is being accessed.
*
* @return an equivalent handle on the resource.
*/
public ResourceHandle asResourceHandle() {
return new ResourceHandle(davClient, locator);
}
/**
* Return a boolean value indicating whether or not the server for this resource
* is DAV compliant.
*
* @return boolean <code>true</code> if the server can respond to DAV
* requests, or <code>false</code> otherwise.
* @exception DAVException if there was a problem checking for DAV compliance
*/
public boolean canTalkDAV() throws DAVException {
IResponse response = null;
try {
// Send an options request.
response = davClient.options(locator, newContext());
examineResponse(response);
// Check for at least DAV level 1.
String davHeader = response.getContext().getDAV();
return !((davHeader == null) || (davHeader.indexOf("1") == -1)); //$NON-NLS-1$
} catch (IOException exception) {
throw new SystemException(exception);
} finally {
closeResponse(response);
}
}
/**
* Check-in this resource. Returns a handle on the new version.
* <p>
* Note that versioned collections do not have internal members
* so they are represented by <code>ResourceHandle</code> handles.</p>
* <p>
* If the receiver is a working resource it becomes invalid after
* the check in (because the server deletes the working resource),
* however, if the receiver is a version-controlled resource the
* receiver can be used as a checked-in resource.</p>
*
* @return a handle to the newly created version.
* @throws DAVException if a problem occurs with the check in on
* the WebDAV server.
*/
public ResourceHandle checkIn() throws DAVException {
ILocator versionLocator = protectedCheckIn();
return new ResourceHandle(davClient, versionLocator);
}
/**
* Check out this resource. Returns a resource handle on the checked out
* version-controlled resource, or the working resource if a version is checked
* out.
* <p>
* Note that a checked-out version-controlled collection has members that are
* themselves version-controlled resources, or unversioned resources; however,
* working collection members are always version history resources.</p>
*
* @throws DAVException if a problem occurs checking out the resource.
*/
public abstract AbstractResourceHandle checkOut() throws DAVException;
/**
* Helper method to close a response from the server.
* <p>
* Note that the argument MAY be <code>null</code> in which case
* the call has no effect.</p>
*
* @param response the response from the server, or <cod>null</code>
* denoting a no-op.
* @throws SystemException if a problem occurred closing the response.
*/
protected void closeResponse(IResponse response) throws SystemException {
if (response == null)
return;
try {
response.close();
} catch (IOException e) {
throw new SystemException(e);
}
}
/**
* Make a copy of this resource and place it at the location defined
* by the given locator.
* <p>
* Uses default values of depth: infinity and overwrite: false for
* the copy.</p>
*
* @param destination the <code>Locator</code> to the destination of the copy.
* @exception DAVException if there was a problem copying this resource.
* @see IServer#copy(ILocator, ILocator, IContext, Document)
*/
public void copy(ILocator destination) throws DAVException {
copy(destination, IContext.DEPTH_INFINITY, false, null);
}
/**
* Make a copy of this resource and place it at the location specified
* by the given destination locator.
*
* @param destination the location to put the copy.
* @param depth how deep to make the copy.
* @param overwrite how to react if a resource already exists at the destination.
* @param propertyNames <code>Collection</code> of <code>QualifiedName</code>s
* of properties that MUST be copied as live properties. Specifying <code>null</code>
* mean that <i>all</i> properties must be kept alive; specifying an empty collection allows for
* no properties to be kept live. (ref http://andrew2.andrew.cmu.edu/rfc/rfc2518.html#sec-12.12.1)
* @exception DAVException if there was a problem copying this resource.
* @see IServer#copy(ILocator, ILocator, IContext, Document)
*/
public void copy(ILocator destination, String depth, boolean overwrite, Collection propertyNames) throws DAVException {
// Define the request context.
IContext context = newContext();
context.setDepth(depth);
context.setOverwrite(overwrite);
// Set up the request body to specify which properties should be kept alive.
Document document = newDocument();
PropertyBehavior propertyBehavior = PropertyBehavior.create(document);
if (propertyNames == null)
propertyBehavior.setIsKeepAllAlive(true);
else {
Iterator namesItr = propertyNames.iterator();
while (namesItr.hasNext()) {
QualifiedName name = (QualifiedName) namesItr.next();
String nameURI = name.getQualifier() + "/" + name.getLocalName(); //$NON-NLS-1$
propertyBehavior.addProperty(nameURI);
} // end-while
} // end-if
// Call the server to perform the copy.
IResponse response = null;
try {
response = davClient.copy(locator, destination, context, document);
examineResponse(response);
examineMultiStatusResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Create this resource in the repository.
* <p>
* Subclasses should override this method with the correct behavior
* for their type.</p>
*
* @exception DAVException if there was a problem creating
* this resource.
*/
public abstract void create() throws DAVException;
/**
* Delete this resource from the repository.
* <p>
* As a convenience, if the resource does not exist this method will
* do nothing (rather than throw an exception). If the caller needs to know
* if a resource was deleted they can use delete(boolean).
*
* @exception DAVException if there was a problem deleting this resource.
* @see #delete(boolean)
* @see IServer#delete(Locator, Context)
*/
public void delete() throws DAVException {
// As a convenience, we will assume an attempt to
// delete a missing resource is a successful outcome.
delete(false);
}
/**
* Answers true iff the receiver and the argument are considered equal,
* otherwise answers false.
* <p>
* Note that this is a handle equivalence test, and does not imply
* that the resources are the same resource on the server. Indeed,
* the method does not contact the server.</p>
*
* @param obj the target of the comparison.
* @return whether the two objects are equal.
*/
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof AbstractResourceHandle))
return false;
AbstractResourceHandle otherHandle = (AbstractResourceHandle) obj;
// It all comes down to locator equality.
return locator.equals(otherHandle.locator);
}
/**
* Answers the hashcode of the receiver as defined by <code>Object#hashCode()</code>.
*
* @return the receiver's hash code.
*/
public int hashCode() {
return locator.hashCode();
}
/**
* If the given response contains a multistatus body, the bodies status'
* are checked for errors. If an error is found, an exception is thrown.
*
* @param response the response from the server to examine.
* @throws DAVException if the given response contains a multistatus
* body that contains a status code signalling an error.
*/
protected void examineMultiStatusResponse(IResponse response) throws DAVException {
// If it is not a multistatus we don't look at it.
if (response.getStatusCode() != IResponse.SC_MULTI_STATUS)
return;
// It is declared a multistatus, so if there is no response body
// then that is a problem.
if (!response.hasDocumentBody())
throw new DAVException(Policy.bind("exception.responseMustHaveDocBody")); //$NON-NLS-1$
// Extract the XML document from the response.
Element documentElement;
try {
documentElement = response.getDocumentBody().getDocumentElement();
if (documentElement == null)
throw new DAVException(Policy.bind("exception.invalidDoc")); //$NON-NLS-1$
} catch (IOException exception) {
throw new SystemException(exception);
}
// Enumerate all the responses in the multistat and check that
// they are indicating success (i.e. are 200-series response codes).
try {
MultiStatus multistatus = new MultiStatus(documentElement);
Enumeration responseEnum = multistatus.getResponses();
while (responseEnum.hasMoreElements()) {
ResponseBody responseBody = (ResponseBody) responseEnum.nextElement();
Enumeration propstatEnum = responseBody.getPropStats();
while (propstatEnum.hasMoreElements()) {
PropStat propstat = (PropStat) propstatEnum.nextElement();
examineStatusCode(propstat.getStatusCode(), propstat.getResponseDescription());
} // end-while
} // end-while
} catch (MalformedElementException e) {
throw new SystemException(e);
}
}
/**
* Check the status code of the given response and throw a WebDAV
* exception if the code indicates failure.
*
* @param response the response to check
* @exception WebDAVException if the server returned an HTTP/WebDAV
* error code (i.e., anything outside the 200-series codes).
*/
protected void examineResponse(IResponse response) throws WebDAVException {
examineStatusCode(response.getStatusCode(), response.getStatusMessage());
}
/**
* Helper method to extract the property status response from
* a multi status reponse, and populate a URLTable with the
* results.
*
* @param multiStatus an editor on the response from the server.
* @return all the property status in a <code>URLTable</code>.
* @throws IOException if there is a problem parsing the resource URLs.
* @throws MalformedElementException if the XML is badly formed.
*/
protected URLTable extractPropStats(MultiStatus multiStatus) throws IOException, MalformedElementException {
// Construct a URLTable to return to the user.
URLTable reply = new URLTable();
// For each response (resource).
Enumeration responses = multiStatus.getResponses();
while (responses.hasMoreElements()) {
ResponseBody responseBody = (ResponseBody) responses.nextElement();
String href = responseBody.getHref();
// The href may be relative to the request URL.
URL resourceURL = new URL(new URL(locator.getResourceURL()), href);
Hashtable properties = new Hashtable();
reply.put(resourceURL, properties);
// For each property status grouping.
Enumeration propstats = responseBody.getPropStats();
while (propstats.hasMoreElements()) {
PropStat propstat = (PropStat) propstats.nextElement();
org.eclipse.webdav.dom.Status status = new org.eclipse.webdav.dom.Status(propstat.getStatus());
// For each property with this status.
Enumeration elements = propstat.getProp().getProperties();
while (elements.hasMoreElements()) {
Element element = (Element) elements.nextElement();
QualifiedName name = ElementEditor.getQualifiedName(element);
// Add a property status object to the result set.
PropertyStatus propertyStatus = new PropertyStatus(element, status.getStatusCode(), status.getStatusMessage());
properties.put(name, propertyStatus);
} // end-while
} // end-while
} // end-while
return reply;
}
/**
* Return the content of this resource as an input stream. The input
* stream should be closed by the user.
*
* @return the input stream
* @exception DAVException if there was a problem getting the contents
* @see IServer#get(Locator, Context)
*/
public ResponseInputStream getContent() throws DAVException {
IResponse response = null;
try {
response = davClient.get(locator, newContext());
examineResponse(response);
} catch (IOException e) {
closeResponse(response);
throw new SystemException(e);
}
return new ResponseInputStream(response);
}
/**
* Answer the DAVClient being used by this resource handle for accessing
* the resource.
*
* @return the receiver's <code>DAVClient</code>.
*/
public DAVClient getDAVClient() {
return davClient;
}
/**
* Return the locator for this resource.
*
* @return the locator for this resource
*/
public ILocator getLocator() {
return locator;
}
/**
* Return an Enumeration over ActiveLocks which lists the locks currently
* held on this resource. Return an empty enumeration if the lock discovery
* property is not found on the resource.
*
* @return the enumeration of active locks
* @exception DAVException if there was a problem getting the locks
* @see #getProperty(QualifiedName)
*/
public Enumeration getLocks() throws DAVException {
LockDiscovery lockdiscovery = null;
try {
Element element = getProperty(DAV_LOCK_DISCOVERY).getProperty();
lockdiscovery = new LockDiscovery(element);
return lockdiscovery.getActiveLocks();
} catch (WebDAVException exception) {
if (exception.getStatusCode() == IResponse.SC_NOT_FOUND)
return new EmptyEnumeration();
throw exception;
} catch (MalformedElementException elemException) {
throw new SystemException(elemException);
}
}
/**
* Returns a collection handle for the parent of this resource.
* <p>
* Note that this method does NOT perform a method call to the
* server to ensure that the collection exists.</p>
* <p>
* Returns <code>null</code> if this resource is the root.
*
* <em>NOTE</em>
* The parent of a resource is, in general, ambiguous and may not
* be immediately discernable from a resource locator. For example,
* a locator with a 'label' qualifier will identify a version of a version-
* controlled resource, and the parent will not be found by a simple URL
* operation. Where a handle is created on a stable URL (i.e. a version URL)
* there is no concept of a 'parent' resource.
* Clients require further contextual information to determine
* the 'parent' of a resource in these cases.
*
* @return the handle for the parent of this resource, or
* <code>null</code>.
*/
public CollectionHandle getParent() throws DAVException {
Assert.isTrue(locator.getLabel() == null);
Assert.isTrue(!locator.isStable());
try {
URL url = URLTool.getParent(locator.getResourceURL());
if (url == null)
return null;
String parentName = url.toString();
ILocator parentLocator = davClient.getDAVFactory().newLocator(parentName);
return new CollectionHandle(davClient, parentLocator);
} catch (MalformedURLException e) {
throw new SystemException(e);
}
}
/**
* Return a <code>URLTable</code> which contains all of this resources
* properties to the given depth. The returned <code>URLTable</code>
* maps resource <code>URL</code>s to <code>Hashtable</code>s
* which in turn maps property <code>QualifiedName</code>s to
* <code>PropertyStatus</code>.
*
* @param depth the depth of the request; for example,
* <code>Context.DEPTH_ZERO</code>.
* @return a <code>URLTable</code> containing properties.
* @exception DAVException if there was a problem retrieving the properties.
* @see #getProperties(Collection, String)
*/
public URLTable getProperties(String depth) throws DAVException {
return getProperties((Collection) null, depth);
}
/**
* Fetches and returns the specified properties for this resource and its
* children to the given depth. The returned table is a URLTable of
* hashtables. The keys in the first table are the <code>URL</code>s of
* the resources. The nested table is a table where the keys are the names
* (<code>QualifiedName</code>) of the properties and the values are the
* properties' values (<code>PropertyStatus</code>).
*
* @param propertyNames collection of property names to search for
* (<code>QualifiedName</code>), or <code>null</code> to retrieve
* all properties.
* @param depth the depth of the search (eg. <code>Context.DEPTH_INFINITY</code>)
* @return URLTable of hashtables keyed by resource <code>URLKey</code>
* then by property name.
* @exception DAVException if there was a problem fetching the properties.
* @see IServer#propfind(Locator, Context, Document)
*/
public URLTable getProperties(Collection propertyNames, String depth) throws DAVException {
// Set up the request context.
IContext context = newContext();
context.setDepth(depth);
// Set up the request body.
Document document = newDocument();
PropFind propfind = PropFind.create(document);
// null is a special value meaning 'all properties'.
if (propertyNames == null)
propfind.setIsAllProp(true);
else {
// Add all the property names to the request body.
Prop prop = propfind.setProp();
Iterator namesItr = propertyNames.iterator();
while (namesItr.hasNext())
prop.addPropertyName((QualifiedName) namesItr.next());
}
// Were ready to make the server call.
IResponse response = null;
try {
// This contacts the server.
response = davClient.propfind(locator, context, document);
examineResponse(response);
// Create a multi-status element editor on the response.
if (!response.hasDocumentBody())
throw new DAVException(Policy.bind("exception.respMustShareXMLDoc")); //$NON-NLS-1$
Element documentElement = response.getDocumentBody().getDocumentElement();
if (documentElement == null)
throw new DAVException(Policy.bind("exception.respHasInvalidDoc")); //$NON-NLS-1$
MultiStatus multiStatus = new MultiStatus(documentElement);
// Construct a URLTable of results to return to the user.
return extractPropStats(multiStatus);
} catch (IOException e) {
throw new SystemException(e);
} catch (MalformedElementException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Return the property status for the property with the given name.
*
* @param propertyName the name of the property
* @return the property status
* @exception DAVException if there was a problem getting the property
* @see #getProperties(Collection, String)
*/
public PropertyStatus getProperty(QualifiedName propertyName) throws DAVException {
Collection names = new HashSet();
names.add(propertyName);
URLTable result = getProperties(names, IContext.DEPTH_ZERO);
URL url = null;
try {
url = new URL(locator.getResourceURL());
} catch (MalformedURLException e) {
throw new SystemException(e);
}
Hashtable propTable = (Hashtable) result.get(url);
if (propTable == null)
throw new DAVException(Policy.bind("exception.lookup", url.toExternalForm())); //$NON-NLS-1$
return (PropertyStatus) propTable.get(propertyName);
}
/**
* Fetch and return the property names for the resource, and the children
* resources to the specified depth. Returns <code>URLTable</code>
* mapping resource URLs to enumerations over the property names for that
* resource.
*
* @param depth eg. <code>Context.DEPTH_ZERO</code>
* @return a <code>URLTable</code> of <code>Enumerations</code> over
* <code>QualfiedNames</code>
* @throws DAVException if there was a problem getting the property names
* @see IServer#propfind(Locator, Context, Document)
*/
public URLTable getPropertyNames(String depth) throws DAVException {
// create and send the request
IContext context = newContext();
context.setDepth(depth);
IResponse response = null;
try {
Document document = newDocument();
PropFind propfind = PropFind.create(document);
propfind.setIsPropName(true);
response = davClient.propfind(locator, context, document);
examineResponse(response);
if (!response.hasDocumentBody()) {
throw new DAVException(Policy.bind("exception.respMustHaveElmtBody")); //$NON-NLS-1$
}
Element documentElement = response.getDocumentBody().getDocumentElement();
if (documentElement == null) {
throw new DAVException(Policy.bind("exception.bodyMustHaveElmt")); //$NON-NLS-1$
}
MultiStatus multistatus = new MultiStatus(documentElement);
//construct the URLTable to return to the user
URLTable reply = new URLTable(10);
Enumeration responses = multistatus.getResponses();
while (responses.hasMoreElements()) {
ResponseBody responseBody = (ResponseBody) responses.nextElement();
String href = responseBody.getHref();
URL resourceUrl = new URL(new URL(locator.getResourceURL()), href);
Enumeration propstats = responseBody.getPropStats();
Vector vector = new Vector();
while (propstats.hasMoreElements()) {
PropStat propstat = (PropStat) propstats.nextElement();
Prop prop = propstat.getProp();
Enumeration names = prop.getPropertyNames();
while (names.hasMoreElements()) {
QualifiedName dname = (QualifiedName) names.nextElement();
vector.addElement(dname);
}
}
reply.put(resourceUrl, vector.elements());
}
return reply;
} catch (IOException e) {
throw new SystemException(e);
} catch (MalformedElementException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Retrieve the version tree infomration for the receiver, assuming
* that the receiver is a version or a version-controlled resource.
* <p>
* The version tree info comprises a <code>URLTable</code> whose keys
* are the <code>URL</code>s of each version in the version history,
* and whose values are <code>Vector</code>s of the resource's immediate
* predecessor <code>URL</code>s. Note that the root version is
* (uniquely) identified by an empty set of predecessors.</p>
*
* @return the map from resource URL to predecessor set.
*/
public URLTable getVersionTree() throws DAVException {
// Issue a version tree report against the receiver to retrieve
// the successor set of all the versions.
Document document = newDocument();
Element root = ElementEditor.create(document, "version-tree"); //$NON-NLS-1$
Element propElement = ElementEditor.appendChild(root, "prop"); //$NON-NLS-1$
ElementEditor.appendChild(propElement, "predecessor-set"); //$NON-NLS-1$
IResponse response = null;
try {
// Run the REPORT and check for errors.
response = davClient.report(locator, newContext(), document);
examineResponse(response);
if (!response.hasDocumentBody())
throw new DAVException(Policy.bind("exception.respMustHaveElmtBody")); //$NON-NLS-1$
// Get the body as a MultiStatus.
Element documentElement = response.getDocumentBody().getDocumentElement();
if (documentElement == null)
throw new DAVException(Policy.bind("exception.bodyMustHaveElmt")); //$NON-NLS-1$
MultiStatus multistatus = new MultiStatus(documentElement);
// Construct the predecessor table.
// This will contain the result.
URLTable predecessorTable = new URLTable();
// For each response.
Enumeration responses = multistatus.getResponses();
while (responses.hasMoreElements()) {
ResponseBody responseBody = (ResponseBody) responses.nextElement();
// Get the absolute URL of the resource.
String href = responseBody.getHref();
URL resourceURL = new URL(new URL(locator.getResourceURL()), href);
// Add an entry to the predecessor table.
Vector predecessors = new Vector();
predecessorTable.put(resourceURL, predecessors);
// For each propstat.
Enumeration propstats = responseBody.getPropStats();
while (propstats.hasMoreElements()) {
PropStat propstat = (PropStat) propstats.nextElement();
// We are going to assume that the status is OK, or error out.
if (propstat.getStatusCode() != IResponse.SC_OK)
throw new DAVException(Policy.bind("exception.errorRetrievingProp")); //$NON-NLS-1$
// For each property in the prop (there should only be one).
Prop prop = propstat.getProp();
Enumeration elements = prop.getProperties();
while (elements.hasMoreElements()) {
Element element = (Element) elements.nextElement();
// Look explicitly for the DAV:predecessor-set
QualifiedName name = ElementEditor.getQualifiedName(element);
if (name.equals(DAV_PREDECESSOR_SET)) {
Enumeration e = new HrefSet(element, DAV_PREDECESSOR_SET).getHrefs();
while (e .hasMoreElements()) {
URL predURL = new URL((String) e.nextElement());
predecessors.add(predURL);
} // end-while
} //end-if
} // end-while
} // end-while
} //end-while
// Phew, were done.
return predecessorTable;
} catch (IOException e) {
throw new SystemException(e);
} catch (MalformedElementException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
public CollectionHandle[] getWorkspaceCollections() throws DAVException {
PropertyStatus propertyStatus = getProperty(DAV_WORKSPACE_COLLECTION_SET);
Vector v = new Vector(5);
Element element = propertyStatus.getProperty();
if (!ElementEditor.isDAVElement(element, "workspace-collection-set")) //$NON-NLS-1$
throw new DAVException(Policy.bind("exception.malformedElement")); //$NON-NLS-1$
Element child = ElementEditor.getFirstChild(element, "href"); //$NON-NLS-1$
while (child != null) {
String href = ElementEditor.getFirstText(child);
ILocator locator = davClient.getDAVFactory().newLocator(href);
v.addElement(new CollectionHandle(davClient, locator));
child = ElementEditor.getNextSibling(child);
}
CollectionHandle[] result = new CollectionHandle[v.size()];
v.copyInto(result);
return result;
}
/**
* Return the header from a message send to the server.
*
* @return a context with the message header contents
* @exception DAVException if there was a problem sending the message
* @see IServer#head(Locator, Context)
*/
public IContext head() throws DAVException {
IResponse response = null;
try {
response = davClient.head(locator, newContext());
examineResponse(response);
return response.getContext();
} catch (IOException exception) {
throw new SystemException(exception);
} finally {
closeResponse(response);
}
}
/**
* Return a boolean value indicating whether or not this resource is a
* collection.
* <p>
* A resource is a collection (i.e., implements collection semantics) if
* it's resource type includes a <DAV:collection> element.</p>
*
* @return boolean <code>true</code> if the resource implements collection
* semantics, and <code>false</code> otherwise.
*/
public boolean isCollection() throws DAVException {
return propertyHasChild(DAV_RESOURCE_TYPE, DAV_COLLECTION_RESOURCE_TYPE);
}
/**
* Return a boolean value indicating whether or not this resource
* is currently locked.
*
* @return boolean indicator
* @exception DAVException if there was a problem getting the locks
* @see #getLocks()
*/
public boolean isLocked() throws DAVException {
// see if there are any active locks
return getLocks().hasMoreElements();
}
/**
* Lock this resource with default values.
*
* <p>Note: default values of DEPTH_ZERO for depth and -1 for timeout are used.</p>
*
* @return the lock token
* @exception DAVException if there was a problem locking this resource
* @see #lock(boolean, String, int, String)
*/
public LockToken lock() throws DAVException {
return lock(false, IContext.DEPTH_ZERO, -1, null);
}
/**
* Lock this resource using the specified parameters.
*
* @param isShared true if the lock is shared, false if the lock is exclusive
* @param depth eg. <code>Context.DEPTH_ZERO</code>
* @param timeout the timeout value for the lock
* @param owner the owner of the lock
* @return the lock token
* @exception DAVException if there was a problem locking this resource
* @see IServer#lock(Locator, Context, Document)
*/
public LockToken lock(boolean isShared, String depth, int timeout, String owner) throws DAVException {
// Define the request context.
IContext context = newContext();
context.setDepth(depth);
context.setTimeout(timeout);
// Create the request body.
Document document = newDocument();
LockInfo lockinfo = LockInfo.create(document);
lockinfo.setIsShared(isShared);
// Add the owner if it is given.
if (owner != null) {
Owner ownerEditor = lockinfo.setOwner();
ownerEditor.getElement().appendChild(document.createTextNode(owner));
}
// Send the lock request.
IResponse response = null;
try {
response = davClient.lock(locator, context, document);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
// Extract the token from the resulting context.
LockToken token = new LockToken(response.getContext().getLockToken());
//fServerManager.addLock(newURL(fLocator.getResourceURL()), token, depth);
return token;
}
/**
* Move this resource to the destination specified by the given locator.
* Use default values for overwrite and properties to move.
*
* @param destination the location to move this resource to
* @exception DAVException if there was a problem moving this resource
* @see #move(Locator, boolean, Enumeration)
*/
public void move(ILocator destination) throws DAVException {
move(destination, false, null);
}
/**
* Move this resource to the location specified by the given locator.
* If a resource already exists at the destination and the overwrite
* boolean is true, then write over top of the existing resource. Otherwise
* do not. The enumeration is over qualified names which are the names of
* the properties to move.
*
* @param destination the location to move to
* @param overwrite how to react if a resource already exists at the
* destination
* @param names <code>Enumeration</code> over <code>QualifiedNames</code>
* @exception DAVException if there was a problem moving this resource
* @see IServer#move(Locator, Locator, Context, Document)
*/
public void move(ILocator destination, boolean overwrite, Enumeration names) throws DAVException {
IContext context = newContext();
context.setOverwrite(overwrite);
Document document = newDocument();
PropertyBehavior propertyBehavior = PropertyBehavior.create(document);
if (names == null) {
propertyBehavior.setIsKeepAllAlive(true);
} else {
while (names.hasMoreElements()) {
Object obj = names.nextElement();
Assert.isTrue(obj instanceof QualifiedName, Policy.bind("assert.propNameMustBeEnumOverQual")); //$NON-NLS-1$
// fix this...can we really add property names to href elements?
propertyBehavior.addProperty(((QualifiedName) obj).getLocalName());
}
}
IResponse response = null;
try {
response = davClient.move(locator, destination, context, document);
examineResponse(response);
examineMultiStatusResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Answer a new empty context for requests sent to the
* receivers server.
*
* @return a new request <code>Context</code>.
*/
protected IContext newContext() {
return davClient.getDAVFactory().newContext();
}
/**
* Answer a new empty DOM Document suitable for creating requests
* to the receiver's server.
*
* @return a new DOM <code>Document</code>.
*/
protected Document newDocument() {
return davClient.getDAVFactory().newDocument();
}
/**
* Check in the receiver and answer a new Locator on the
* resulting version resource.
*
* @return the <code>Locator</code> to the receiver's version.
* @throws DAVException if a problem occurs with the check in request.
*/
protected ILocator protectedCheckIn() throws DAVException {
IResponse response = null;
try {
response = davClient.checkin(locator, newContext(), null);
examineResponse(response);
String versionUrl = response.getContext().getLocation();
return davClient.getDAVFactory().newStableLocator(versionUrl);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Check out the receiver and answer a new Locator on the
* resulting checked out resource. The result MAY be the same
* as the receiver's Locator if the server did not create
* a new resource as a consequence of the check out (i.e.
* if it was checking out a vesion-controlled resource rather
* than a version).
*
* @return the <code>Locator</code> to the receiver's version.
* @throws DAVException if a problem occurs with the check in request.
*/
protected ILocator protectedCheckOut() throws DAVException {
IResponse response = null;
try {
response = davClient.checkout(locator, newContext(), null);
examineResponse(response);
String resourceUrl = response.getContext().getLocation();
return davClient.getDAVFactory().newStableLocator(resourceUrl);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Refresh the lock on this resource with the given lock token. Use
* the specified timeout value.
*
* @param lockToken the lock token to refresh
* @param timeout the new timeout value to use
* @exception DAVException if there was a problem refreshing the lock
*/
public void refreshLock(LockToken lockToken, int timeout) throws DAVException {
// Set up the request in the context.
IContext context = newContext();
context.setTimeout(timeout);
context.setLockToken(lockToken.getToken());
// Send the request to the server.
IResponse response = null;
try {
response = davClient.lock(locator, context, null);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Remove the properties with the given names, from this resource.
*
* @param propertyNames <code>Enumeration</code> over
* <code>QualifiedNames</code>
* @exception DAVException if there was a problem removing the
* properties
* @see IServer#proppatch(Locator, Context, Document)
*/
public void removeProperties(Collection propertyNames) throws DAVException {
Assert.isNotNull(propertyNames);
// Removing no properties is easy.
if (propertyNames.isEmpty())
return;
// Add the names of the properties to remove to the request body.
Document document = newDocument();
PropertyUpdate propertyUpdate = PropertyUpdate.create(document);
Prop prop = propertyUpdate.addRemove();
Iterator namesItr = propertyNames.iterator();
while (namesItr.hasNext())
prop.addPropertyName((QualifiedName) namesItr.next());
// Send the PROPPATCH request.
IResponse response = null;
try {
response = davClient.proppatch(locator, newContext(), document);
examineResponse(response);
examineMultiStatusResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Remove the property with the given name from this resource.
*
* @param propertyName the name of the property to remove
* @exception DAVException if there was a problem removing the property
* @see #removeProperties(Collection)
*/
public void removeProperty(QualifiedName propertyName) throws DAVException {
Collection propertyNames = new Vector(1);
propertyNames.add(propertyName);
removeProperties(propertyNames);
}
/**
* Set the content of this resource to be the untyped data stored in the given
* input stream.
* The stream will automatically be closed after the data
* is consumed. If the resource does not exist it is created with the
* given content.
*
* @param input the inputstream containing the resource contents.
* @exception DAVException if there was a problem setting the contents.
* @see #setContent(String, InputStream)
* @see IServer#put(Locator, Context, InputStream)
*/
public void setContent(InputStream input) throws DAVException {
setContent("application/octet-stream", input); //$NON-NLS-1$
}
/**
* Set the content of this resource to be the data stored in the given
* input stream. The type encoding is given in the content type argument, and
* should be in the media format described by RFC2616 Sec 3.7.
* The stream will automatically be closed after the data
* is consumed. If the resource does not exist it is created with the
* given content.
*
* @param contentType the media type for the data on the input stream.
* @param input the inputstream containing the resource contents.
* @exception DAVException if there was a problem setting the contents.
* @see IServer#put(Locator, Context, InputStream)
*/
public void setContent(String contentType, InputStream input) throws DAVException {
IResponse response = null;
try {
IContext context = newContext();
context.setContentType(contentType);
response = davClient.put(locator, context, input);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Set the given properties on this resource.
*
* @param properties a <code>Collection</code> of property <code>Element</code>s.
* @exception DAVException if there was a problem setting the properties.
* @see IServer#proppatch(Locator, Context, Document)
*/
public void setProperties(Collection properties) throws DAVException {
Assert.isNotNull(properties);
// Setting no properties is a no-op.
if (properties.isEmpty())
return;
// Build the request body to describe the properties to set.
Document document = newDocument();
PropertyUpdate propertyUpdate = PropertyUpdate.create(document);
Prop prop = propertyUpdate.addSet();
Iterator propertiesItr = properties.iterator();
while (propertiesItr.hasNext()) {
Element element = (Element) propertiesItr.next();
try {
prop.addProperty(element);
} catch (MalformedElementException exception) {
throw new SystemException(exception);
}
} // end-while
// Send the request to the server and examine the response for failures.
IResponse response = null;
try {
response = davClient.proppatch(locator, newContext(), document);
examineResponse(response);
examineMultiStatusResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Set the given property on this resource.
*
* @param property the property to set
* @exception DAVException if there was a problem setting the property
* @see #setProperties(Collection)
*/
public void setProperty(Element property) throws DAVException {
Collection properties = new Vector(1);
properties.add(property);
setProperties(properties);
}
/**
* Return a string representation of this resource. Used for
* debugging purposes only.
*
* @return this resource, as a string
*/
public String toString() {
return locator.getResourceURL();
}
/**
* Send a message to the server. The contents of the resulting
* input stream should be the message that was sent, echoed
* back to the client.
* <p>
* The input stream should be closed by the user.</p>
*
* @return an input stream on the request as received.
* @exception DAVException if there was a problem sending the
* request to the server.
* @see IServer#trace(Locator, Context)
*/
public ResponseInputStream trace() throws DAVException {
IResponse response = null;
try {
response = davClient.trace(locator, newContext());
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} catch (DAVException e) {
closeResponse(response);
throw e;
}
return new ResponseInputStream(response);
}
/**
* Unlock this resource with the given lock token.
*
* @param token the lock token to remove from this resource
* @exception DAVException if there was a problem unlocking this resource
* @see IServer#unlock(Locator, Context)
*/
public void unlock(LockToken token) throws DAVException {
// Send the lock token in the header of the request.
IContext context = newContext();
context.setLockToken("<" + token.getToken() + ">"); //$NON-NLS-1$ //$NON-NLS-2$
IResponse response = null;
try {
response = davClient.unlock(locator, context);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Perform an UPDATE on the receiver to set the version it
* is based upon.
*
* @param version the <code>Locator</code> of the version that
* is the target of the update request.
* @throws DAVException if a problem occurs executing the update
* on the WebDAV server.
*/
public void update(ILocator version) throws DAVException {
Document document = newDocument();
Update.createVersion(document, version.getResourceURL());
IResponse response = null;
try {
response = davClient.update(locator, newContext(), document);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Bring the receiver under version control. This means that
* the receiver is replaced by a version-controlled resource.
* Note that the client may send version control to a resource
* that is already under version control with no adverse effects.
*
* @throws DAVException if a problem occurs bringing the
* resource under version control.
*/
public void versionControl() throws DAVException {
IResponse response = null;
try {
response = davClient.versionControl(locator, newContext(), null);
examineResponse(response);
} catch (IOException e) {
throw new SystemException(e);
} finally {
closeResponse(response);
}
}
/**
* Delete this resource from the repository, optionally succeeding
* in the delete if the resource was not found on the server.
*
* @param mustExist if <code>true</code> then the delete will
* fail if the resource was not on the server at the time of the
* delete request. If <code>false</code> the delete will succeed if
* there was no such resource to delete.
* @exception DAVException if there was a problem deleting
* this resource.
* @see IServer#delete(Locator, Context)
*/
public void delete(boolean mustExist) throws DAVException {
IResponse response = null;
try {
response = davClient.delete(locator, newContext());
if (!mustExist && (response.getStatusCode() == IResponse.SC_NOT_FOUND))
return;
examineResponse(response);
examineMultiStatusResponse(response);
} catch (IOException exception) {
throw new SystemException(exception);
} finally {
closeResponse(response);
}
}
/**
* Check the given status code and throw a WebDAV
* exception if the code indicates failure. If the code
* is success, this method does nothing.
*
* @param code the status code to check.
* @param message the status message accompanying the code.
* @exception WebDAVException if the server returned an HTTP/WebDAV
* error code (i.e., anything outside the 200-series codes).
*/
protected void examineStatusCode(int code, String message) throws WebDAVException {
if (code >= 300 && code <= 399)
throw new RedirectionException(code, message);
if (code >= 400 && code <= 499)
throw new ClientException(code, message);
if (code >= 500 && code <= 599)
throw new ServerException(code, message);
}
/**
* Return a boolean value indicating whether or not this resource
* exists on the server.
* <p>
* This implementation uses the HTTP HEAD method so the URL may or
* may not exist in the DAV namespace. The DAV RESOURCE_TYPE property
* is NOT checked.</p>
*
* @return boolean <code>true</code> if the resource exists on the server
* or <code>false</code> otherwise.
* @exception DAVException if there was a problem checking for
* existence.
*/
public boolean exists() throws DAVException {
// Test existance by issuing a HEAD request.
IResponse response = null;
try {
response = davClient.head(locator, newContext());
// If the resource was not found, then that answers the question.
if (response.getStatusCode() == IResponse.SC_NOT_FOUND)
return false;
// Otherwise check for errors.
examineResponse(response);
// No problems by this point, so the resource is there and OK.
return true;
} catch (IOException exception) {
throw new SystemException(exception);
} finally {
closeResponse(response);
}
}
/**
* Check to see if the resource is checked in (i.e., is an immutable
* resource).
* <p>
* The resource is checked in if it has a <DAV:checked-in>
* property.</p>
*
* @return <code>true</code> if the resource is checked in
* and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining the state
* of the resource.
*/
public boolean isCheckedIn() throws DAVException {
return supportsLiveProperty(DAV_CHECKED_IN);
}
/**
* Check to see if the resource is checked-out.
* <p>
* The resource is checked out if it has a <DAV:checked-out>
* property.</p>
*
* @return <code>true</code> if the resource is checked out
* and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining the state
* of the resource.
*/
public boolean isCheckedOut() throws DAVException {
return supportsLiveProperty(DAV_CHECKED_OUT);
}
/**
* Check to see if the resource is a version.
* <p>
* The resource is a version if it has <DAV:version-name>
* in the <DAV:supported-live-properties-set>.</p>
*
* @return <code>true</code> if the resource is a version
* and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining the state
* of the resource.
*/
public boolean isVersion() throws DAVException {
return supportsLiveProperty(DAV_VERSION_NAME);
}
/**
* Check to see if the resource is under version control.
* <p>
* The resource is version controlled if it has <DAV:auto-checkout>
* in the <DAV:supported-live-properties-set>.</p>
*
* @return <code>true</code> if the resource is under version
* control and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining the state
* of the resource.
*/
public boolean isVersionControlled() throws DAVException {
return supportsLiveProperty(DAV_AUTO_CHECKOUT);
}
/**
* Check to see if the resource is a working resource.
* <p>
* The resource is a working resource if it has
* <DAV:checked-out> and does not have <DAV:auto-checkout>
* in the <DAV:supported-live-properties-set>.</p>
*
* @return <code>true</code> if the resource is a working resource
* and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining the state
* of the resource.
*/
public boolean isWorkingResource() throws DAVException {
PropertyStatus propertyStat = getProperty(DAV_SUPPORTED_LIVE_PROPERTY_SET);
// If the live-property-set is not supported, then the answer is 'no'.
if (propertyStat.getStatusCode() == IResponse.SC_NOT_FOUND)
return false;
// If there was a problem getting the live property set, throw an exception.
examineStatusCode(propertyStat.getStatusCode(), propertyStat.getStatusMessage());
// Check to see if the required properties are/are not in the supported set.
try {
Element propertySet = propertyStat.getProperty();
return ((ElementEditor.hasChild(propertySet, DAV_CHECKED_OUT)) && !(ElementEditor.hasChild(propertySet, DAV_AUTO_CHECKOUT)));
} catch (MalformedElementException exception) {
throw new SystemException(exception);
}
}
/**
* This is a helper method to check to see if the resource has a
* property with the given name that in turn has a child with a
* given name.
*
* @return <code>true</code> if the resource does have such a
* property with a named child and <code>false</code> if it does
* not have such a property or does not have such a child of the
* property.
* @throws DAVException if a problem occurs determining result
* from the server.
*/
protected boolean propertyHasChild(QualifiedName propertyName, QualifiedName childName) throws DAVException {
// If the property is not found, then the answer is 'no'.
PropertyStatus propertyStat = getProperty(propertyName);
if (propertyStat.getStatusCode() == IResponse.SC_NOT_FOUND)
return false;
// If there was a problem getting the property, throw an exception.
examineStatusCode(propertyStat.getStatusCode(), propertyStat.getStatusMessage());
// Check to see if the named child is in the retrieved property.
try {
return ElementEditor.hasChild(propertyStat.getProperty(), childName);
} catch (MalformedElementException exception) {
throw new SystemException(exception);
}
}
/**
* Check to see if the resource supports the named live property.
*
* @return <code>true</code> if the resource does support the live
* property and <code>false</code> otherwise.
* @throws DAVException if a problem occurs determining result
* from the server.
*/
public boolean supportsLiveProperty(QualifiedName propertyName) throws DAVException {
return propertyHasChild(DAV_SUPPORTED_LIVE_PROPERTY_SET, propertyName);
}
}