0001: package com.ibm.webdav.protocol.http;
0002:
0003: /*
0004: * (C) Copyright IBM Corp. 2000 All rights reserved.
0005: *
0006: * The program is provided "AS IS" without any warranty express or
0007: * implied, including the warranty of non-infringement and the implied
0008: * warranties of merchantibility and fitness for a particular purpose.
0009: * IBM will not be liable for any damages suffered by you as a result
0010: * of using the Program. In no event will IBM be liable for any
0011: * special, indirect or consequential damages or lost profits even if
0012: * IBM has been advised of the possibility of their occurrence. IBM
0013: * will not be liable for any third party claims against you.
0014: *
0015: * Portions Copyright (C) Simulacra Media Ltd, 2004.
0016: */
0017: import com.ibm.webdav.protocol.http.WebDAVURLConnection;
0018: import com.ibm.webdav.protocol.http.WebDAVAuthenticator;
0019: import com.ibm.webdav.*;
0020: import com.ibm.webdav.impl.*;
0021: import com.ibm.webdav.ServerException;
0022: import java.io.*;
0023: import java.net.URL;
0024: import org.w3c.dom.*;
0025: import java.util.Vector;
0026: import java.util.Properties;
0027: import java.util.Enumeration;
0028: import javax.xml.parsers.*;
0029:
0030: /** Implements a client-side proxy stub of the Resource interface using HTTP plus WebDAV extensions
0031: * as a remote procedure call protocol. It does this by creating a WebDAVURLConnection
0032: * and dispatching the request to the servlet ResourceHTTPSkel which in turn dispatches
0033: * to ResourceImpl. This is the place to see how the DAV4J client API is mapped to
0034: * the WebDAV protocol.
0035: */
0036: public class ResourceHTTPStub implements IRResource {
0037: protected URL url = null;
0038:
0039: // contexts for communicating HTTP and WebDAV headers (method contol couples)
0040: protected ResourceContext context = new ResourceContext();
0041:
0042: static {
0043:
0044: WebDAVURLConnection
0045: .setDefaultAuthenticator(new WebDAVAuthenticator());
0046: }
0047:
0048: protected WebDAVURLConnection connection = null;
0049:
0050: // properties taken from the dav4j.properties file.
0051: protected static Properties properties = ResourceFactory.properties;
0052:
0053: /** The default constructor. Should be rarely used.
0054: */
0055: public ResourceHTTPStub() {
0056: this .url = null;
0057: }
0058:
0059: /** Construct a ResourceHTTPStub with the given URL. The resource having
0060: * the url may not exist as this constructor does not access the resource from
0061: * the server. Use exists() or attmept to get the contents of the resource to
0062: * see if it exists. Other constructors are provided using parameters for the
0063: * various parts of the URL. See java.net.URLConnection for details.
0064: *
0065: * @param url the URL of the resource.
0066: * @exception MalformedURLException
0067: * @exception java.io.IOException
0068: * @see URLConnection
0069: * @see com.ibm.webdav.ResourceFactory
0070: */
0071: public ResourceHTTPStub(String url) throws java.io.IOException {
0072: this (new URL(url), (TargetSelector) null);
0073: }
0074:
0075: /** Create a ResourceHTTPStub from the given URL components.
0076: * @param protocol the protocol to use, http:, rmi:, or iiop:
0077: * @param host the name or IP addres of the server host. Using the client host name,
0078: * or 'localhost' without a port uses local access with no RPC or server required.
0079: * @param port the TCP port to use. HTTP uses 80 by default.
0080: * @param file the resource URL relative to the server including any query string, etc.
0081: * @exception java.io.IOException
0082: * @see URLConnection
0083: * @see com.ibm.webdav.ResourceFactory
0084: */
0085: public ResourceHTTPStub(String protocol, String host, int port,
0086: String file) throws java.io.IOException {
0087: this (new URL(protocol, host, port, file), (TargetSelector) null);
0088: }
0089:
0090: /** Create a ResourceHTTPStub from the given URL components. This constructor uses the default
0091: * HTTP port.
0092: * @param protocol the protocol to use, http:, rmi:, or iiop:
0093: * @param host the name or IP addres of the server host. Using the client host name,
0094: * or 'localhost' without a port uses local access with no RPC or server required.
0095: * @param file the resource URL relative to the server including any query string, etc.
0096: * @exception java.io.IOException
0097: * @see URLConnection
0098: * @see com.ibm.webdav.ResourceFactory
0099: */
0100: public ResourceHTTPStub(String protocol, String host, String file)
0101: throws java.io.IOException {
0102: this (new URL(protocol, host, file), (TargetSelector) null);
0103: }
0104:
0105: /** Construct a ResourceHTTPStub with the given URL. The resource having
0106: * the url may not exist as this constructor does not access the resource from
0107: * the server. Use exists() or attmept to get the contents of the resource to
0108: * see if it exists. Other constructors are provided using parameters for the
0109: * various parts of the URL. See java.net.URLConnection for details.
0110: *
0111: * @param url the URL of the resource.
0112: * @param targetSelector the revision target selector for this Collection
0113: * @exception com.ibm.webdav.WebDAVException
0114: * @see URLConnection
0115: * @see com.ibm.webdav.ResourceFactory
0116: */
0117: public ResourceHTTPStub(URL url, TargetSelector targetSelector)
0118: throws WebDAVException {
0119: this .url = url;
0120: }
0121:
0122: /** Construct a ResourceP with the given URL specification in the given context. The resource having
0123: * the url may not exist as this constructor does not access the resource from
0124: * the server. Use exists() or attmept to get the contents of the resource to
0125: * see if it exists. Other constructors are provided using parameters for the
0126: * various parts of the URL. See java.net.URLConnection for details.
0127: *
0128: * @param context a URL giving the context in which the spec is evaluated
0129: * @param spec a URL whose missing parts are provided by the context
0130: * @exception java.io.IOException
0131: * @see URLConnection
0132: * @see com.ibm.webdav.ResourceFactory
0133: */
0134: public ResourceHTTPStub(URL context, String spec)
0135: throws java.io.IOException {
0136: this (new URL(context, spec), (TargetSelector) null);
0137: }
0138:
0139: /** This method must be called after the client has completed writing to the contents
0140: * output stream that was obtained from <code>getContentsOutputStream()</code>.
0141: * @exception com.ibm.webdav.WebDAVException
0142: */
0143: public void closeContentsOutputStream(ResourceContext context)
0144: throws WebDAVException {
0145: this .closeContentsOutputStream(context, null);
0146: }
0147:
0148: public void closeContentsOutputStream(ResourceContext context,
0149: String sContentType) throws WebDAVException {
0150: this .context = context;
0151:
0152: getResults();
0153: }
0154:
0155: /** Copy this resource to the destination URL.
0156: * Partial results are possible, check the returned status for details.
0157: *
0158: * @param destinationURL the destination
0159: * @param overwrite true implies overrite the destination if it exists
0160: * @param propertiesToCopy a collection of properties that must be copied or
0161: * the method will fail. propertiesToCopy may have one of the following values:
0162: * <ul>
0163: * <li>null - ignore properties that cannot be copied</li>
0164: * <li>empty collection - all properties must be copied or the method will fail</li>
0165: * <li>a collection of URIs - a list of the properties that must be copied
0166: * or the method will fail</li>
0167: * </ul>
0168: *
0169: * @return the status of the copy operation for each resource copied
0170: * @exception com.ibm.webdav.WebDAVException
0171: */
0172: public MultiStatus copy(ResourceContext context,
0173: String destinationURL, boolean overwrite,
0174: Vector propertiesToCopy) throws WebDAVException {
0175: this .context = context;
0176:
0177: context.getRequestContext().overwrite(overwrite ? "T" : "F");
0178: context.getRequestContext().destination(destinationURL);
0179:
0180: setupRequest("COPY");
0181:
0182: return doCopyOrMove(propertiesToCopy);
0183: }
0184:
0185: /** Delete this resouce from the server. The actual effect of the delete operation is
0186: * determined by the underlying repository manager. The visible effect to WebDAV
0187: * is that the resource is no longer available.
0188: *
0189: * @return a MultiStatus containing the status of the delete method on each
0190: * effected resource.
0191: * @exception com.ibm.webdav.WebDAVException
0192: */
0193: public MultiStatus delete(ResourceContext context)
0194: throws WebDAVException {
0195: this .context = context;
0196:
0197: setupRequest("DELETE");
0198: getResults();
0199:
0200: return responseToMultiStatus();
0201: }
0202:
0203: /** Do the work involved in a remote procedure call for a copy or move
0204: * operation.
0205: * @param propertiesToCopy a collection of properties that must be copied or
0206: * the method will fail. propertiesToCopy may have one of the following values:
0207: * <ul>
0208: * <li>null - ignore properties that cannot be copied</li>
0209: * <li>empty collection - all properties must be copied or the method will fail</li>
0210: * <li>a collection of URIs - a list of the properties that must be copied
0211: * or the method will fail</li>
0212: * </ul>
0213: *
0214: * @return the status of the copy operation for each resource copied
0215: * @exception com.ibm.webdav.WebDAVException
0216: */
0217: private MultiStatus doCopyOrMove(Vector properties)
0218: throws WebDAVException {
0219: context.getRequestContext().contentType("text/xml");
0220:
0221: // Convert the propertiesToCopy to XML
0222: Document document = null;
0223: try {
0224: document = DocumentBuilderFactory.newInstance()
0225: .newDocumentBuilder().newDocument();
0226: } catch (Exception e) {
0227: throw new WebDAVException(WebDAVStatus.SC_PROCESSING, e
0228: .getMessage());
0229: }
0230: //document.setVersion(Resource.XMLVersion);
0231: //document.setEncoding(Resource.defaultXMLEncoding);
0232:
0233: Element propertybehavior = document.createElementNS("DAV:",
0234: "D:propertybehavior");
0235: propertybehavior.setAttribute("xmlns:D", "DAV:");
0236: document.appendChild(propertybehavior);
0237:
0238: if (properties == null) {
0239: propertybehavior.appendChild(document.createElementNS(
0240: "DAV:", "D:omit"));
0241: } else {
0242: Element keepalive = document.createElementNS("DAV:",
0243: "D:keepalive");
0244: propertybehavior.appendChild(keepalive);
0245: if (properties.isEmpty()) {
0246: keepalive.appendChild(document.createTextNode("*"));
0247: } else {
0248: Enumeration props = properties.elements();
0249: while (props.hasMoreElements()) {
0250: String uri = (String) props.nextElement();
0251: Element href = document.createElementNS("DAV:",
0252: "D:href");
0253: href.appendChild(document.createTextNode(uri));
0254: keepalive.appendChild(href);
0255: }
0256: }
0257: }
0258:
0259: // output the properties to copy request entity
0260: connection.setDoOutput(true);
0261: try {
0262: Writer writer = new OutputStreamWriter(connection
0263: .getOutputStream(), Resource.defaultCharEncoding);
0264: PrintWriter pw = new PrintWriter(writer, false);
0265: pw.print(XMLUtility
0266: .printNode(document.getDocumentElement()));
0267: //document.print(pw);
0268: pw.flush();
0269: getResults();
0270: } catch (java.io.UnsupportedEncodingException exc) {
0271: throw new WebDAVException(WebDAVStatus.SC_BAD_REQUEST,
0272: "Unsupported encoding requiested");
0273: } catch (WebDAVException exc) {
0274: throw exc;
0275: } catch (java.io.IOException exc) {
0276: throw new WebDAVException(
0277: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0278: }
0279: return responseToMultiStatus();
0280: }
0281:
0282: /** Get an InputStream for accessing the contents of this resource. This method may provide
0283: * more efficient access for resources that have large contents. Clients may want to create
0284: * a Reader to perform appropriate character conversions on this stream.
0285: *
0286: * @return an InputStream on the contents
0287: * @exception com.ibm.webdav.WebDAVException
0288: */
0289: public InputStream getContentsInputStream(ResourceContext context)
0290: throws WebDAVException {
0291: this .context = context;
0292:
0293: setupRequest("GET");
0294: getResults();
0295: InputStream stream = null;
0296: try {
0297: stream = connection.getInputStream();
0298: } catch (WebDAVException exc) {
0299: System.err
0300: .println("ResourceHTTPStub.getContentsInputStream: Exception -");
0301: exc.printStackTrace(System.err);
0302: throw exc;
0303: } catch (java.io.IOException exc) {
0304: System.err
0305: .println("ResourceHTTPStub.getContentsInputStream: Exception -");
0306: exc.printStackTrace(System.err);
0307: throw new WebDAVException(
0308: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0309: }
0310: return stream;
0311: }
0312:
0313: /** Get an OutputStream for setting the contents of this resource. This method may provide
0314: * more efficient access for resources that have large contents. Remember to call
0315: * closeContentsOutputStream() when all the data has been written.
0316: *
0317: * @return an OutputStream to set the contents
0318: * @exception com.ibm.webdav.WebDAVException
0319: */
0320: public OutputStream getContentsOutputStream(ResourceContext context)
0321: throws WebDAVException {
0322: this .context = context;
0323: HTTPHeaders requestContext = context.getRequestContext();
0324: if (requestContext.contentType() == null) {
0325: requestContext.contentType("text/plain");
0326: }
0327:
0328: setupRequest("PUT");
0329: connection.setDoOutput(true);
0330: OutputStream stream = null;
0331: try {
0332: stream = connection.getOutputStream();
0333: } catch (WebDAVException exc) {
0334: throw exc;
0335: } catch (java.io.IOException exc) {
0336: throw new WebDAVException(
0337: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0338: }
0339: return stream;
0340: }
0341:
0342: /** This method can be used for obtaining meta-information about this resource without
0343: * actually reading the resource contents. This meta-information is maintained by the server
0344: * in addition to the resource properties. The meta-information is obtained from an
0345: * HTTP HEAD method.</p>
0346: * <p>
0347: * After this call, the resource context has been updated and
0348: * <code>getStatusCode()</code>, <code>getStatusMessage()</code>, and <code>getResponseContext()</code>
0349: * as well as all the ResourceContext methods return updated values based on the current
0350: * state of the resource.</p>
0351: * <p>This methods corresponds to the HTTP HEAD method.</p>
0352: * <p>
0353: * Do a getContentsInputStream() to set the response context,
0354: * then just don't return the stream.
0355: * @exception com.ibm.webdav.WebDAVException
0356: */
0357: public void getMetaInformation(ResourceContext context)
0358: throws WebDAVException {
0359: this .context = context;
0360:
0361: setupRequest("HEAD");
0362: getResults();
0363: }
0364:
0365: /** Get all the properties for this resource and (potentially) its children.
0366: *
0367: * @param depth an indicator for immediate members or recursively all children.
0368: * <ul>
0369: * <li>thisResource: propeprties of this resource</li>
0370: * <li>immediateMembers: propeprties of this resource and its immediate children</li>
0371: * <li>allMembers: properties of this resource and recursively all its children</li>
0372: * </ul>
0373: *
0374: * @return a MultiStatus of PropertyResponses
0375: * @exception com.ibm.webdav.WebDAVException
0376: */
0377: public MultiStatus getProperties(ResourceContext context)
0378: throws WebDAVException {
0379: this .context = context;
0380: context.getRequestContext().contentType("text/xml");
0381: // default is deep in WebDAV, but we don't want this behavior
0382: if (context.getRequestContext().depth() == null) {
0383: context.getRequestContext().depth(Collection.this Resource);
0384: }
0385:
0386: setupRequest("PROPFIND");
0387:
0388: Document requestBody = null;
0389: try {
0390: requestBody = DocumentBuilderFactory.newInstance()
0391: .newDocumentBuilder().newDocument();
0392: } catch (Exception e) {
0393: throw new WebDAVException(WebDAVStatus.SC_PROCESSING, e
0394: .getMessage());
0395: }
0396: //requestBody.setVersion(Resource.XMLVersion);
0397: //requestBody.setEncoding(Resource.defaultXMLEncoding);
0398: Element propfind = requestBody.createElementNS("DAV:",
0399: "D:propfind");
0400: propfind.setAttribute("xmlns:D", "DAV:");
0401: requestBody.appendChild(propfind);
0402: propfind.appendChild(requestBody.createElementNS("DAV:",
0403: "D:allprop"));
0404:
0405: connection.setDoOutput(true);
0406: try {
0407: Writer writer = new OutputStreamWriter(connection
0408: .getOutputStream(), Resource.defaultCharEncoding);
0409: PrintWriter pw = new PrintWriter(writer, false);
0410: pw.print(XMLUtility.printNode(requestBody
0411: .getDocumentElement()));
0412: //requestBody.print(pw);
0413: pw.flush();
0414: } catch (WebDAVException exc) {
0415: throw exc;
0416: } catch (java.io.IOException exc) {
0417: throw new WebDAVException(
0418: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0419: "IO error or bad encoding");
0420: }
0421:
0422: getResults();
0423:
0424: return responseToMultiStatus();
0425: }
0426:
0427: /** Get the named properties for this resource and (potentially) its children.
0428: *
0429: * @param names an arrary of property names to retrieve.
0430: * @param depth an indicator for immediate members or recursively all children.
0431: * <ul>
0432: * <li>immediateMembers: propeprties of this resource and its immediate children</li>
0433: * <li>allMembers: properties of this resource and recursively all its children</li>
0434: * </ul>
0435: *
0436: * @return a MultiStatus of PropertyResponses
0437: * @exception com.ibm.webdav.WebDAVException
0438: */
0439: public MultiStatus getProperties(ResourceContext context,
0440: PropertyName names[]) throws WebDAVException {
0441: this .context = context;
0442: context.getRequestContext().contentType("text/xml");
0443: // default is deep in WebDAV, but we don't want this behavior
0444: if (context.getRequestContext().depth() == null) {
0445: context.getRequestContext().depth(Collection.this Resource);
0446: }
0447:
0448: setupRequest("PROPFIND");
0449:
0450: Document requestBody = null;
0451: try {
0452: requestBody = DocumentBuilderFactory.newInstance()
0453: .newDocumentBuilder().newDocument();
0454: } catch (Exception e) {
0455: throw new WebDAVException(WebDAVStatus.SC_PROCESSING, e
0456: .getMessage());
0457: }
0458: //requestBody.setVersion(Resource.XMLVersion);
0459: //requestBody.setEncoding(Resource.defaultXMLEncoding);
0460: Element propfind = requestBody.createElementNS("DAV:",
0461: "D:propfind");
0462: propfind.setAttribute("xmlns:D", "DAV:");
0463: requestBody.appendChild(propfind);
0464: Element prop = requestBody.createElementNS("DAV:", "D:prop");
0465: propfind.appendChild(prop);
0466: for (int i = 0; i < names.length; i++) {
0467: // substitute the prefix for any namespace
0468: // todo: has to iterate through all the namespaces
0469: PropertyName name = names[i];
0470: Element el = requestBody.createElementNS("DAV:", "D:"
0471: + name.getLocal());
0472: if (!name.getNamespace().equals("DAV:")) {
0473: el.setAttribute("xmlns:D", name.getNamespace());
0474: }
0475:
0476: prop.appendChild(el);
0477: }
0478:
0479: connection.setDoOutput(true);
0480: try {
0481: Writer writer = new OutputStreamWriter(connection
0482: .getOutputStream(), Resource.defaultCharEncoding);
0483: PrintWriter pw = new PrintWriter(writer, false);
0484: pw.print(XMLUtility.printNode(requestBody
0485: .getDocumentElement()));
0486: //requestBody.print(pw);
0487: pw.flush();
0488: } catch (WebDAVException exc) {
0489: throw exc;
0490: } catch (java.io.IOException exc) {
0491: throw new WebDAVException(
0492: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0493: "IO error or bad encoding");
0494: }
0495: getResults();
0496:
0497: return responseToMultiStatus();
0498: }
0499:
0500: /** Get the names of all properties for this resource and (potentially) its children.
0501: *
0502: * @param depth an indicator for immediate members or recursively all children.
0503: * <ul>
0504: * <li>thisResource: propeprties of this resource</li>
0505: * <li>immediateMembers: propeprties of this resource and its immediate children</li>
0506: * <li>allMembers: properties of this resource and recursively all its children</li>
0507: * </ul>
0508: *
0509: * @return a MultiStatus of PropertyResponses
0510: * (PropertyValue.value is always null, PropertyValue.status contains the status)
0511: * @exception com.ibm.webdav.WebDAVException
0512: */
0513: public MultiStatus getPropertyNames(ResourceContext context)
0514: throws WebDAVException {
0515: this .context = context;
0516: context.getRequestContext().contentType("text/xml");
0517: // default is deep in WebDAV, but we don't want this behavior
0518: if (context.getRequestContext().depth() == null) {
0519: context.getRequestContext().depth(Collection.this Resource);
0520: }
0521:
0522: setupRequest("PROPFIND");
0523:
0524: Document requestBody = null;
0525: try {
0526: requestBody = DocumentBuilderFactory.newInstance()
0527: .newDocumentBuilder().newDocument();
0528: } catch (Exception e) {
0529: throw new WebDAVException(WebDAVStatus.SC_PROCESSING, e
0530: .getMessage());
0531: }
0532: //requestBody.setVersion(Resource.XMLVersion);
0533: //requestBody.setEncoding(Resource.defaultXMLEncoding);
0534: Element propfind = requestBody.createElementNS("DAV:",
0535: "D:propfind");
0536: propfind.setAttribute("xmlns:D", "DAV:");
0537: requestBody.appendChild(propfind);
0538: propfind.appendChild(requestBody.createElementNS("DAV:",
0539: "D:propname"));
0540: connection.setDoOutput(true);
0541:
0542: try {
0543: Writer writer = new OutputStreamWriter(connection
0544: .getOutputStream(), Resource.defaultCharEncoding);
0545: PrintWriter pw = new PrintWriter(writer, false);
0546: pw.print(XMLUtility.printNode(requestBody
0547: .getDocumentElement()));
0548: //requestBody.print(pw);
0549: pw.flush();
0550: } catch (WebDAVException exc) {
0551: throw exc;
0552: } catch (java.io.IOException exc) {
0553: throw new WebDAVException(
0554: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0555: "IO error or bad encoding");
0556: }
0557:
0558: getResults();
0559:
0560: return responseToMultiStatus();
0561: }
0562:
0563: /** Get the response entity for the previous WebDAVURLConnection request.
0564: * @return the response entity contents
0565: * @exception com.ibm.webdav.WebDAVException
0566: */
0567: protected byte[] getResponseEntity() throws WebDAVException {
0568: byte[] data = new byte[0];
0569: try {
0570: BufferedInputStream is = new BufferedInputStream(connection
0571: .getInputStream());
0572: int length = (int) context.getResponseContext()
0573: .contentLength();
0574: if (length != -1) {
0575: int rcvd = 0;
0576: int size = 0;
0577: data = new byte[length];
0578: do {
0579: size += rcvd;
0580: rcvd = is.read(data, size, length - size);
0581: } while (size < length && rcvd != -1);
0582: if (rcvd == -1)
0583: // premature EOF
0584: data = resizeArray(data, size);
0585: } else {
0586: data = new byte[0];
0587: int inc = 8192;
0588: int off = data.length;
0589: int rcvd = 0;
0590: do {
0591: off += rcvd;
0592: data = resizeArray(data, off + inc);
0593: rcvd = is.read(data, off, inc);
0594: } while (rcvd != -1);
0595: data = resizeArray(data, off);
0596: }
0597: } catch (WebDAVException exc) {
0598: throw exc;
0599: } catch (java.io.IOException exc) {
0600: throw new WebDAVException(
0601: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0602: }
0603: return data;
0604: }
0605:
0606: /** Cause a WebDAVURLConnection request to be sent and get the
0607: * status code and message. After this method has been called,
0608: * getResponseEntity() can be called.
0609: * @exception com.ibm.webdav.WebDAVException
0610: */
0611: protected void getResults() throws WebDAVException {
0612: // cause the request to be sent and raise any necessary exceptions
0613: String statusMessage = null;
0614: int responseCode = 0;
0615: try {
0616: responseCode = connection.getResponseCode();
0617: context.getStatusCode().setStatusCode(responseCode);
0618: statusMessage = connection.getResponseMessage();
0619: } catch (WebDAVException exc) {
0620: throw exc;
0621: } catch (java.io.IOException exc) {
0622: throw new WebDAVException(
0623: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0624: }
0625:
0626: if (statusMessage == null || statusMessage.equals("Unknown")) {
0627: statusMessage = context.getStatusCode().getStatusMessage();
0628: }
0629: // copy the response and contents headers into the response context
0630: // Note the index starts at 1 not 0
0631: int i = 1;
0632: String name = null;
0633: while ((name = connection.getHeaderFieldKey(i)) != null) {
0634: name = name.toLowerCase();
0635: String value = connection.getHeaderField(i);
0636: context.getResponseContext().put(name, value);
0637: i++;
0638: }
0639:
0640: if (responseCode >= 300 && responseCode <= 399) {
0641: throw new RedirectionException(responseCode, statusMessage);
0642: } else if (responseCode >= 400 && responseCode <= 499) {
0643: throw new ClientException(responseCode, statusMessage);
0644: } else if (responseCode >= 500 && responseCode <= 599) {
0645: throw new ServerException(responseCode, statusMessage);
0646: }
0647: }
0648:
0649: /** Lock this resource collection and potentially all its members
0650: * based on the given parameters. This allows control of the lock
0651: * scope (exclusive or shared) the lock type (write), owner information, etc.
0652: *
0653: * @param scope the scope of the lock, exclusive or shared
0654: * @param type the type of the lock, currently only write
0655: * @param timeout the number of seconds before the lock times out or
0656: * 0 for infinite timeout.
0657: * @param owner an XML element containing useful information that can be
0658: * used to identify the owner of the lock. An href to a home page, an
0659: * email address, phone number, etc. Can be null if no owner information
0660: * is provided.
0661: *
0662: * @return a MultiStatus containing a lockdiscovery property indicating
0663: * the results of the lock operation.
0664: * @exception com.ibm.webdav.WebDAVException
0665: */
0666: public MultiStatus lock(ResourceContext context, String scope,
0667: String type, int timeout, Element owner)
0668: throws WebDAVException {
0669: this .context = context;
0670:
0671: context.getRequestContext().contentType("text/xml");
0672: context.getRequestContext().setTimeout(timeout);
0673: context.getRequestContext().precondition((String) null);
0674: // default is deep in WebDAV, but we don't want this behavior
0675: if (context.getRequestContext().depth() == null) {
0676: context.getRequestContext().depth(Collection.this Resource);
0677: }
0678:
0679: setupRequest("LOCK");
0680:
0681: // construct the request entity body
0682: Document requestBody = null;
0683: try {
0684: requestBody = DocumentBuilderFactory.newInstance()
0685: .newDocumentBuilder().newDocument();
0686: } catch (Exception e) {
0687: throw new WebDAVException(WebDAVStatus.SC_PROCESSING, e
0688: .getMessage());
0689: }
0690: //requestBody.setVersion(Resource.XMLVersion);
0691: //requestBody.setEncoding(Resource.defaultXMLEncoding);
0692: Element lockinfo = requestBody.createElementNS("DAV:",
0693: "D:lockinfo");
0694: lockinfo.setAttribute("xmlns:D", "DAV:");
0695: requestBody.appendChild(lockinfo);
0696: Element lockscope = requestBody.createElementNS("DAV:",
0697: "D:lockscope");
0698: lockscope.appendChild(requestBody.createElementNS("DAV:", "D:"
0699: + scope));
0700: lockinfo.appendChild(lockscope);
0701: Element locktype = requestBody.createElementNS("DAV:",
0702: "D:locktype");
0703: locktype.appendChild(requestBody.createElementNS("DAV:", "D:"
0704: + type));
0705: lockinfo.appendChild(locktype);
0706: if (owner != null) {
0707: lockinfo.appendChild((Element) owner);
0708: }
0709:
0710: // output the request entity body
0711: connection.setDoOutput(true);
0712: try {
0713: PrintWriter pw = new PrintWriter(connection
0714: .getOutputStream(), false);
0715: pw.print(XMLUtility.printNode(requestBody
0716: .getDocumentElement()));
0717: //requestBody.print(pw);
0718: } catch (WebDAVException exc) {
0719: throw exc;
0720: } catch (java.io.IOException exc) {
0721: System.err.println(exc);
0722: exc.printStackTrace();
0723: throw new WebDAVException(
0724: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0725: }
0726:
0727: // either a prop containing a lockdiscovery, or a multistatus
0728: getResults();
0729:
0730: return lockResponseToMultiStatus();
0731: }
0732:
0733: /** Convert the response from a LOCK method (an XML multistatus with a prop element
0734: * containing a lockdiscovery) into a MultiStatus.
0735: * @return a MultiStatus created from the DAV:multistatus from a LOCK method
0736: * @exception com.ibm.webdav.ServerException thrown if there is a syntax error in the XML DAV:multistatus
0737: */
0738: protected MultiStatus lockResponseToMultiStatus()
0739: throws WebDAVException {
0740: // parse the response
0741: WebDAVErrorHandler errorHandler = new WebDAVErrorHandler(url
0742: .toString());
0743: Reader reader = null;
0744: Document document = null;
0745:
0746: /*Parser xmlParser = new Parser(url.toString(), errorListener, null);
0747: xmlParser.setWarningNoDoctypeDecl(false);
0748: xmlParser.setProcessNamespace(true);*/
0749:
0750: try {
0751: DocumentBuilderFactory factory = DocumentBuilderFactory
0752: .newInstance();
0753: factory.setNamespaceAware(true);
0754: DocumentBuilder docbuilder = factory.newDocumentBuilder();
0755: docbuilder.setErrorHandler(errorHandler);
0756: reader = new InputStreamReader(connection.getInputStream(),
0757: Resource.defaultCharEncoding);
0758: document = docbuilder.parse(new org.xml.sax.InputSource(
0759: reader));
0760: } catch (WebDAVException exc) {
0761: throw exc;
0762: } catch (java.io.IOException exc) {
0763: throw new WebDAVException(
0764: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0765: } catch (Exception exc) {
0766: throw new WebDAVException(
0767: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, exc
0768: .getMessage());
0769: }
0770:
0771: if (errorHandler.getErrorCount() > 0) {
0772: context.getStatusCode().setStatusCode(
0773: WebDAVStatus.SC_INTERNAL_SERVER_ERROR);
0774: throw new ServerException(
0775: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0776: "Syntax error in multistatus response entity body");
0777: }
0778:
0779: // see if it's a multistatus
0780: if (context.getStatusCode().getStatusCode() != WebDAVStatus.SC_MULTI_STATUS) {
0781: // it's a prop containing a lockdiscovery. Put the prop in a propstat in a
0782: // response and then convert that to a MultiStatus
0783: Element prop = document.getDocumentElement();
0784: Element response = document.createElementNS("DAV:",
0785: "D:response");
0786:
0787: Element href = document.createElementNS("DAV:", "D:href");
0788: href.appendChild(document.createTextNode(url.toString()));
0789:
0790: Element status = document.createElementNS("DAV:",
0791: "D:status");
0792: String statusText = Response.HTTPVersion + " "
0793: + context.getStatusCode().getStatusCode() + " "
0794: + context.getStatusCode().getStatusMessage();
0795: status.appendChild(document.createTextNode(statusText));
0796:
0797: Element propstat = document.createElementNS("DAV:",
0798: "D:propstat");
0799: propstat.appendChild((Element) ((Element) prop)
0800: .cloneNode(true));
0801: propstat.appendChild(status);
0802: response.appendChild(href);
0803: response.appendChild(propstat);
0804:
0805: Element multistatus = document.createElementNS("DAV:",
0806: "D:multistatus");
0807: multistatus.setAttribute("xmlns:D", "DAV:");
0808: multistatus.appendChild(response);
0809: Document newDocument = null;
0810: try {
0811: newDocument = DocumentBuilderFactory.newInstance()
0812: .newDocumentBuilder().newDocument();
0813: } catch (Exception exc) {
0814: throw new WebDAVException(
0815: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, exc
0816: .getMessage());
0817: }
0818: //newDocument.setVersion(Resource.XMLVersion);
0819: //newDocument.appendChild(multistatus);
0820: document = newDocument;
0821: }
0822: MultiStatus multiStatus = new MultiStatus(document);
0823: return multiStatus;
0824: }
0825:
0826: /** Move this resource to the destination URL.
0827: * Partial results are possible, check the returned status for details
0828: *
0829: * @param destinationURL the destination
0830: * @param overwrite true implies overrite the destination if it exists
0831: * @param propertiesToMove a collection of properties that must be moved or
0832: * the method will fail. propertiesToMove may have one of the following values:
0833: * <ul>
0834: * <li>null - ignore properties that cannot be moved</li>
0835: * <li>empty collection - all properties must be moved or the method will fail</li>
0836: * <li>a collection of URIs - a list of the properties that must be moved
0837: * or the method will fail</li>
0838: * </ul>
0839: *
0840: * @return the status of the move operation for each resource moved
0841: * @exception com.ibm.webdav.WebDAVException
0842: */
0843: public MultiStatus move(ResourceContext context,
0844: String destinationURL, boolean overwrite,
0845: Vector propertiesToMove) throws WebDAVException {
0846: this .context = context;
0847:
0848: context.getRequestContext().depth(Collection.deep); // (always deep for move, set it anyway)
0849: context.getRequestContext().overwrite(overwrite ? "T" : "F");
0850: context.getRequestContext().destination(destinationURL);
0851:
0852: setupRequest("MOVE");
0853:
0854: return doCopyOrMove(propertiesToMove);
0855: }
0856:
0857: /** This method treats this resource as a method or service, and sends its parameter to
0858: * this resource where it is handled in a resource-specific way. For example,
0859: * sending data from an HTML form to a URL representing a Servlet or CGI script that processes
0860: * the form data to produce some result.
0861: *
0862: * @param args a string representing the arguments to the method represented by this URL. The
0863: * arguments are in the form ?parameterName1=value1&parameterName2=value2... as specified
0864: * for URL queries.
0865: *
0866: * @return the results of sending the arguments to the URL
0867: * @exception com.ibm.webdav.WebDAVException
0868: */
0869: public byte[] performWith(ResourceContext context, String args)
0870: throws WebDAVException {
0871: this .context = context;
0872:
0873: setupRequest("POST");
0874: putRequestEntity(args.getBytes());
0875: getResults();
0876: return getResponseEntity();
0877: }
0878:
0879: /** Write the request entity body to the WebDAVURLConnection.
0880: * @param value the request entity body to write
0881: * @exception com.ibm.webdav.WebDAVException
0882: */
0883: protected void putRequestEntity(byte[] value)
0884: throws WebDAVException {
0885: connection.setDoOutput(true);
0886: try {
0887: BufferedOutputStream outputStream = new BufferedOutputStream(
0888: connection.getOutputStream());
0889: outputStream.write(value, 0, value.length);
0890: outputStream.flush();
0891: } catch (WebDAVException exc) {
0892: throw exc;
0893: } catch (java.io.IOException exc) {
0894: throw new WebDAVException(
0895: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
0896: }
0897: }
0898:
0899: /** Refresh the lock on this resource by resetting the lock timeout.
0900: * The context must contain the proper authorization for the requesting
0901: * principal.
0902: *
0903: * @param lockToken the lock token identifying the lock.
0904: * @param timeout the new timeout in seconds. -1 means infinite timeout.
0905: *
0906: * @return updated information about the lock status of this resource
0907: * @exception com.ibm.webdav.WebDAVException
0908: */
0909: public MultiStatus refreshLock(ResourceContext context,
0910: String lockToken, int timeout) throws WebDAVException {
0911: this .context = context;
0912:
0913: // on lock refresh, the precondition (If header) must contain the
0914: // lock token
0915: context.getRequestContext().precondition(
0916: "(<" + lockToken + ">)");
0917: context.getRequestContext().setTimeout(timeout);
0918: // default is deep in WebDAV, but we don't want this behavior
0919: context.getRequestContext().depth(Collection.this Resource);
0920:
0921: // A lock refresh doesn't have a request entity body
0922: setupRequest("LOCK");
0923:
0924: // either a prop containing a lockdiscovery, or a multistatus
0925: getResults();
0926: return lockResponseToMultiStatus();
0927: }
0928:
0929: /** A utility to resize a byte array and copy its current contents.
0930: * @param src the source array
0931: * @param new_size the new size to make the array
0932: * @param the newly sized array (may be smaller than src)
0933: */
0934: private final static byte[] resizeArray(byte[] src, int new_size) {
0935: byte tmp[] = new byte[new_size];
0936: System.arraycopy(src, 0, tmp, 0,
0937: (src.length < new_size ? src.length : new_size));
0938: return tmp;
0939: }
0940:
0941: /** Convert the response (an XML multistatus or simple status code) into
0942: * a MultiStatus.
0943: * @return a MultiStatus constructed from a DAV:multistatus or response code
0944: * @exception com.ibm.webdav.ServerException thrown if there is a syntax error in the DAV:multistatus
0945: */
0946: protected MultiStatus responseToMultiStatus()
0947: throws WebDAVException {
0948: MultiStatus multiStatus = null;
0949: if (context.getStatusCode().getStatusCode() == WebDAVStatus.SC_MULTI_STATUS) {
0950: BufferedReader reader = null;
0951: Document document = null;
0952: //WebDAVErrorListener errorListener = new WebDAVErrorListener(url.toString());
0953: WebDAVErrorHandler errorHandler = new WebDAVErrorHandler(
0954: url.toString());
0955:
0956: try {
0957: DocumentBuilderFactory factory = DocumentBuilderFactory
0958: .newInstance();
0959: factory.setNamespaceAware(true);
0960: DocumentBuilder docbuilder = factory
0961: .newDocumentBuilder();
0962: docbuilder.setErrorHandler(errorHandler);
0963: /*Parser xmlParser = new Parser(url.toString(), errorListener, null);
0964: xmlParser.setWarningNoDoctypeDecl(false);
0965: xmlParser.setProcessNamespace(true);*/
0966:
0967: reader = new BufferedReader(new InputStreamReader(
0968: connection.getInputStream(),
0969: Resource.defaultCharEncoding));
0970: document = docbuilder
0971: .parse(new org.xml.sax.InputSource(reader));
0972: } catch (WebDAVException exc) {
0973: throw exc;
0974: } catch (java.io.IOException exc) {
0975: throw new WebDAVException(
0976: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0977: "IO Error or bad encoding");
0978: } catch (Exception exc) {
0979: throw new WebDAVException(
0980: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, exc
0981: .getMessage());
0982: }
0983:
0984: if (errorHandler.getErrorCount() > 0) {
0985: context.getStatusCode().setStatusCode(
0986: WebDAVStatus.SC_INTERNAL_SERVER_ERROR);
0987: throw new ServerException(
0988: WebDAVStatus.SC_INTERNAL_SERVER_ERROR,
0989: "Syntax error in multistatus response entity body");
0990: }
0991: multiStatus = new MultiStatus(document);
0992: } else {
0993: multiStatus = new MultiStatus();
0994: MethodResponse response = new MethodResponse(
0995: url.toString(), context.getStatusCode()
0996: .getStatusCode());
0997: response.setDescription(context.getStatusCode()
0998: .getStatusMessage());
0999: multiStatus.addResponse(response);
1000: }
1001: return multiStatus;
1002: }
1003:
1004: /** Edit the properties of a resource. The updates must refer to a Document containing a WebDAV
1005: * DAV:propertyupdates element as the document root.
1006: *
1007: * @param updates an XML Document containing DAV:propertyupdate elements
1008: * describing the edits to be made
1009: * @return a MultiStatus indicating the status of the updates
1010: * @exception com.ibm.webdav.WebDAVException
1011: */
1012: public MultiStatus setProperties(ResourceContext context,
1013: Document updates) throws WebDAVException {
1014: this .context = context;
1015: context.getRequestContext().contentType("text/xml");
1016:
1017: setupRequest("PROPPATCH");
1018:
1019: // output the request entity body
1020: connection.setDoOutput(true);
1021: try {
1022: PrintWriter pw = new PrintWriter(connection
1023: .getOutputStream(), false);
1024: pw
1025: .print(XMLUtility.printNode(updates
1026: .getDocumentElement()));
1027:
1028: //((Document) updates).print(pw);
1029: } catch (WebDAVException exc) {
1030: throw exc;
1031: } catch (java.io.IOException exc) {
1032: throw new WebDAVException(
1033: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
1034: }
1035:
1036: getResults();
1037:
1038: return responseToMultiStatus();
1039: }
1040:
1041: /** Setup a WebDAVURLConnection by opening a connection and setting the
1042: * method. This should be done before outputting any request entity body.
1043: * @param method the HTTP/1.1 or WebDAV request method
1044: * @exception com.ibm.webdav.WebDAVException
1045: */
1046: protected void setupRequest(String method) throws WebDAVException {
1047: // open a connection
1048: try {
1049: connection = new WebDAVURLConnection(url, "localhost", url
1050: .getPort());
1051:
1052: //connection = (WebDAVURLConnection) url.openConnection();
1053: connection.setRequestMethod(method);
1054: } catch (java.net.ProtocolException exc) {
1055: exc.printStackTrace();
1056: } catch (WebDAVException exc) {
1057: throw exc;
1058: } catch (java.io.IOException exc) {
1059: throw new WebDAVException(
1060: WebDAVStatus.SC_INTERNAL_SERVER_ERROR, "IO Error");
1061: }
1062:
1063: // put the context into the request headers
1064: Enumeration propertyNames = context.getRequestContext().keys();
1065: while (propertyNames.hasMoreElements()) {
1066: String name = (String) propertyNames.nextElement();
1067: String value = (String) context.getRequestContext().get(
1068: name);
1069: connection.setRequestProperty(name, value);
1070: }
1071: }
1072:
1073: /** Unlock the lock identified by the lockToken on this resource. The request context
1074: * must contain the proper authorization.
1075: *
1076: * @param lockToken the lock token obtained from the ActiveLock of a previous <code>lock() </code>
1077: * or <code>getLocks()</code>.
1078: *
1079: * @return a MultiStatus containing any responses on resources that could not
1080: * be unlocked.
1081: * @exception com.ibm.webdav.WebDAVException
1082: */
1083: public MultiStatus unlock(ResourceContext context, String lockToken)
1084: throws WebDAVException {
1085: this .context = context;
1086:
1087: context.getRequestContext().lockToken(lockToken);
1088:
1089: setupRequest("UNLOCK");
1090:
1091: // a status code or a multistatus if the unlock fails
1092: getResults();
1093:
1094: return responseToMultiStatus();
1095: }
1096: }
|