/** * Copyright 2010 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.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); } }