0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.cocoon.components.source.impl;
0018:
0019: import java.io.BufferedInputStream;
0020: import java.io.ByteArrayInputStream;
0021: import java.io.ByteArrayOutputStream;
0022: import java.io.IOException;
0023: import java.io.InputStream;
0024: import java.io.OutputStream;
0025: import java.util.ArrayList;
0026: import java.util.Collection;
0027: import java.util.Enumeration;
0028: import java.util.Properties;
0029: import java.util.Vector;
0030:
0031: import javax.xml.transform.OutputKeys;
0032: import javax.xml.transform.TransformerFactory;
0033: import javax.xml.transform.sax.SAXTransformerFactory;
0034: import javax.xml.transform.sax.TransformerHandler;
0035: import javax.xml.transform.stream.StreamResult;
0036:
0037: import org.apache.avalon.framework.logger.AbstractLogEnabled;
0038: import org.apache.avalon.framework.logger.Logger;
0039: import org.apache.cocoon.caching.validity.EventValidity;
0040: import org.apache.cocoon.components.source.InspectableSource;
0041: import org.apache.cocoon.components.source.helpers.SourceProperty;
0042: import org.apache.cocoon.components.webdav.WebDAVEventFactory;
0043: import org.apache.cocoon.xml.XMLUtils;
0044: import org.apache.commons.httpclient.HttpException;
0045: import org.apache.commons.httpclient.HttpStatus;
0046: import org.apache.commons.httpclient.HttpURL;
0047: import org.apache.commons.httpclient.HttpsURL;
0048: import org.apache.commons.httpclient.URIException;
0049: import org.apache.excalibur.source.ModifiableSource;
0050: import org.apache.excalibur.source.ModifiableTraversableSource;
0051: import org.apache.excalibur.source.MoveableSource;
0052: import org.apache.excalibur.source.Source;
0053: import org.apache.excalibur.source.SourceException;
0054: import org.apache.excalibur.source.SourceNotFoundException;
0055: import org.apache.excalibur.source.SourceParameters;
0056: import org.apache.excalibur.source.SourceUtil;
0057: import org.apache.excalibur.source.SourceValidity;
0058: import org.apache.excalibur.source.TraversableSource;
0059: import org.apache.excalibur.source.impl.validity.TimeStampValidity;
0060: import org.apache.webdav.lib.Property;
0061: import org.apache.webdav.lib.PropertyName;
0062: import org.apache.webdav.lib.ResponseEntity;
0063: import org.apache.webdav.lib.WebdavResource;
0064: import org.apache.webdav.lib.methods.DepthSupport;
0065: import org.w3c.dom.Element;
0066: import org.w3c.dom.Node;
0067: import org.w3c.dom.NodeList;
0068: import org.w3c.dom.Text;
0069: import org.xml.sax.ContentHandler;
0070: import org.xml.sax.SAXException;
0071: import org.xml.sax.helpers.AttributesImpl;
0072:
0073: /**
0074: * A source implementation to get access to WebDAV repositories.
0075: *
0076: * <h2>Protocol syntax</h2>
0077: * <p><code>webdav://[user[:password]@]host[:port][/path][?cocoon:webdav-depth][&cocoon:webdav-action]</code></p>
0078: * <p>
0079: * <ul>
0080: * <li>
0081: * <code>cocoon:webdav-depth</code> allows to specify the default depth
0082: * to use during initialization of the webdav resource.
0083: * </li>
0084: * <li>
0085: * <code>cocoon:webdav-action</code> allows to specify a default action
0086: * to take upon initialization of the webdav resource.
0087: * </li>
0088: * </ul>
0089: * <p>
0090: *
0091: * @version $Id: WebDAVSource.java 479296 2006-11-26 06:28:51Z antonio $
0092: */
0093: public class WebDAVSource extends AbstractLogEnabled implements Source,
0094: TraversableSource, ModifiableSource,
0095: ModifiableTraversableSource, InspectableSource, MoveableSource {
0096:
0097: private static final String NAMESPACE = "http://apache.org/cocoon/webdav/1.0";
0098:
0099: private static final String PREFIX = "webdav";
0100: private static final String RESOURCE_NAME = "resource";
0101: private static final String COLLECTION_NAME = "collection";
0102:
0103: // the http url
0104: private final HttpURL url;
0105:
0106: // the scheme name
0107: private final String protocol;
0108:
0109: // cached uri and secureUri values
0110: private String uri;
0111: private String secureUri;
0112:
0113: // the event factory to get the Event objects from for event caching
0114: private WebDAVEventFactory eventfactory = null;
0115:
0116: // the SWCL resource
0117: private WebdavResource resource = null;
0118:
0119: // current resource initialization values
0120: private int depth = -1;
0121: private int action = -1;
0122:
0123: /**
0124: * Default constructor.
0125: */
0126: private WebDAVSource(HttpURL url, String protocol)
0127: throws URIException {
0128: this .protocol = protocol;
0129: this .url = url;
0130:
0131: String qs = url.getQuery();
0132: if (qs != null) {
0133: final SourceParameters sp = new SourceParameters(qs);
0134:
0135: // parse optional start depth and start action qs parameters
0136: this .depth = sp.getParameterAsInteger(
0137: "cocoon:webdav-depth", DepthSupport.DEPTH_1);
0138: this .action = sp.getParameterAsInteger(
0139: "cocoon:webdav-action", WebdavResource.NOACTION);
0140:
0141: // [UH] FIXME: Why this alternative way of passing in credentials?
0142: String principal = url.getUser();
0143: String password = url.getPassword();
0144: if (principal == null || password == null) {
0145: principal = sp.getParameter("cocoon:webdav-principal",
0146: principal);
0147: password = sp.getParameter("cocoon:webdav-password",
0148: password);
0149: if (principal != null) {
0150: url.setUser(principal);
0151: url.setPassword(password);
0152: }
0153: }
0154:
0155: sp.removeParameter("cocoon:webdav-depth");
0156: sp.removeParameter("cocoon:webdav-action");
0157: sp.removeParameter("cocoon:webdav-principal");
0158: sp.removeParameter("cocoon:webdav-password");
0159:
0160: // set the qs without WebdavSource specific parameters
0161: url.setQuery(sp.getQueryString());
0162: }
0163: }
0164:
0165: /**
0166: * Constructor used by getChildren() method.
0167: */
0168: private WebDAVSource(WebdavResource resource, HttpURL url,
0169: String protocol) throws URIException {
0170: this (url, protocol);
0171: this .resource = resource;
0172: }
0173:
0174: private void setWebDAVEventFactory(WebDAVEventFactory factory) {
0175: eventfactory = factory;
0176: }
0177:
0178: /**
0179: * Initialize the SWCL WebdavResource.
0180: * <p>
0181: * The action argument specifies a set of properties to load during initialization.
0182: * Its value is one of WebdavResource.NOACTION, WebdavResource.NAME,
0183: * WebdavResource.BASIC, WebdavResource.DEFAULT, WebdavResource.ALL.
0184: * Similarly the depth argument specifies the depth header of the PROPFIND
0185: * method that is executed upon initialization.
0186: * </p>
0187: * <p>
0188: * The different methods of this Source implementation call this method to
0189: * initialize the resource using their minimal action and depth requirements.
0190: * For instance the WebDAVSource.getMimeType() method requires WebdavResource.BASIC
0191: * properties and a search depth of 0 is sufficient.
0192: * </p>
0193: * <p>
0194: * However it may be that a later call (eg. WebDAVSource.getChildren()) requires more
0195: * information. In that case the WebdavResource would have to make another call to the Server.
0196: * It would be more efficient if previous initialization had been done using depth 1 instead.
0197: * In order give the user more control over this the WebDAVSource can be passed a minimal
0198: * action and depth using cocoon:webdav-depth and cocoon:webdav-action query string parameters.
0199: * By default the mimimum action is WebdavResource.BASIC (which loads all the following basic
0200: * webdav properties: DAV:displayname, DAV:getcontentlength, DAV:getcontenttype DAV:resourcetype,
0201: * DAV:getlastmodified and DAV:lockdiscovery). The default minimum depth is 1.
0202: * </p>
0203: *
0204: * @param action the set of propterties the WebdavResource should load.
0205: * @param depth the webdav depth.
0206: * @throws SourceException
0207: * @throws SourceNotFoundException
0208: */
0209: private void initResource(int action, int depth)
0210: throws SourceException, SourceNotFoundException {
0211: try {
0212: boolean update = false;
0213: if (action != WebdavResource.NOACTION) {
0214: if (action > this .action) {
0215: this .action = action;
0216: update = true;
0217: } else {
0218: action = this .action;
0219: }
0220: }
0221: if (depth > this .depth) {
0222: this .depth = depth;
0223: update = true;
0224: } else {
0225: depth = this .depth;
0226: }
0227: if (this .resource == null) {
0228: this .resource = new WebdavResource(this .url, action,
0229: depth);
0230: } else if (update) {
0231: this .resource.setProperties(action, depth);
0232: }
0233: if (this .action > WebdavResource.NOACTION) {
0234: if (this .resource.isCollection()) {
0235: String path = this .url.getPath();
0236: if (path.charAt(path.length() - 1) != '/') {
0237: this .url.setPath(path + "/");
0238: }
0239: }
0240: }
0241: } catch (HttpException e) {
0242: if (e.getReasonCode() == HttpStatus.SC_NOT_FOUND) {
0243: throw new SourceNotFoundException("Not found: "
0244: + getSecureURI(), e);
0245: }
0246: if (e.getReasonCode() == HttpStatus.SC_BAD_REQUEST) {
0247: throw new SourceException(
0248: "Server doesn't appear to understand WebDAV: "
0249: + getSecureURI(), e);
0250: }
0251: final String msg = "Could not initialize webdav resource at "
0252: + getSecureURI()
0253: + ". Server responded "
0254: + e.getReasonCode()
0255: + " ("
0256: + e.getReason()
0257: + ") - "
0258: + e.getMessage();
0259: throw new SourceException(msg, e);
0260: } catch (IOException e) {
0261: throw new SourceException(
0262: "Could not initialize webdav resource", e);
0263: }
0264: }
0265:
0266: /**
0267: * Static factory method to obtain a Source.
0268: */
0269: public static WebDAVSource newWebDAVSource(HttpURL url,
0270: String protocol, Logger logger,
0271: WebDAVEventFactory eventfactory) throws URIException {
0272: final WebDAVSource source = new WebDAVSource(url, protocol);
0273: source.enableLogging(logger);
0274: source.setWebDAVEventFactory(eventfactory);
0275: return source;
0276: }
0277:
0278: /**
0279: * Static factory method to obtain a Source.
0280: */
0281: private static WebDAVSource newWebDAVSource(
0282: WebdavResource resource, HttpURL url, String protocol,
0283: Logger logger, WebDAVEventFactory eventfactory)
0284: throws URIException {
0285: final WebDAVSource source = new WebDAVSource(resource, url,
0286: protocol);
0287: source.enableLogging(logger);
0288: source.setWebDAVEventFactory(eventfactory);
0289: return source;
0290: }
0291:
0292: // ---------------------------------------------------- Source implementation
0293:
0294: /**
0295: * Get the scheme for this Source.
0296: */
0297: public String getScheme() {
0298: return this .protocol;
0299: }
0300:
0301: /**
0302: * Return the unique identifer for this source
0303: */
0304: public String getURI() {
0305: if (this .uri == null) {
0306: String uri = this .url.toString();
0307: final int index = uri.indexOf("://");
0308: if (index != -1) {
0309: uri = uri.substring(index + 3);
0310: }
0311: final String userinfo = this .url.getEscapedUserinfo();
0312: if (userinfo != null) {
0313: uri = this .protocol + "://" + userinfo + "@" + uri;
0314: } else {
0315: uri = this .protocol + "://" + uri;
0316: }
0317: this .uri = uri;
0318: }
0319: return this .uri;
0320: }
0321:
0322: /**
0323: * Return the URI securely, without username and password
0324: */
0325: protected String getSecureURI() {
0326: if (this .secureUri == null) {
0327: String uri = this .url.toString();
0328: int index = uri.indexOf("://");
0329: if (index != -1) {
0330: uri = uri.substring(index + 3);
0331: }
0332: uri = this .protocol + "://" + uri;
0333: this .secureUri = uri;
0334: }
0335: return this .secureUri;
0336: }
0337:
0338: /**
0339: * Get the Validity object. This can either wrap the last modification
0340: * date or the expires information or...
0341: * If it is currently not possible to calculate such an information
0342: * <code>null</code> is returned.
0343: */
0344: public SourceValidity getValidity() {
0345:
0346: SourceValidity validity = null;
0347:
0348: if (eventfactory != null) {
0349: try {
0350: validity = new EventValidity(eventfactory
0351: .createEvent(this .url));
0352:
0353: if (getLogger().isDebugEnabled())
0354: getLogger().debug(
0355: "Created EventValidity for source: "
0356: + validity);
0357:
0358: } catch (Exception e) {
0359: if (getLogger().isErrorEnabled())
0360: getLogger().error(
0361: "could not create EventValidity!", e);
0362: }
0363: }
0364:
0365: if (validity == null) {
0366: if (getLogger().isDebugEnabled())
0367: getLogger().debug("Falling back to TimeStampValidity!");
0368:
0369: final long lm = getLastModified();
0370: if (lm > 0) {
0371: validity = new TimeStampValidity(lm);
0372: }
0373: }
0374:
0375: return validity;
0376: }
0377:
0378: /**
0379: * Refresh the content of this object after the underlying data
0380: * content has changed.
0381: */
0382: public void refresh() {
0383: this .resource = null;
0384: }
0385:
0386: /**
0387: * Return an <code>InputStream</code> object to read from the source.
0388: * This is the data at the point of invocation of this method,
0389: * so if this is Modifiable, you might get different content
0390: * from two different invocations.
0391: */
0392: public InputStream getInputStream() throws IOException,
0393: SourceException {
0394: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0395: try {
0396: if (this .resource.isCollection()) {
0397: // [UH] FIXME: why list collection as XML here?
0398: // I think its a concern for the TraversableGenerator.
0399: WebdavResource[] resources = this .resource
0400: .listWebdavResources();
0401: return resourcesToXml(resources);
0402: } else {
0403: BufferedInputStream bi = null;
0404: bi = new BufferedInputStream(this .resource
0405: .getMethodData());
0406: if (!this .resource.exists()) {
0407: throw new HttpException(getSecureURI()
0408: + " does not exist");
0409: }
0410: return bi;
0411: }
0412: } catch (HttpException he) {
0413: throw new SourceException("Could not get WebDAV resource "
0414: + getSecureURI(), he);
0415: } catch (Exception e) {
0416: throw new SourceException("Could not get WebDAV resource"
0417: + getSecureURI(), e);
0418: }
0419: }
0420:
0421: /**
0422: * The mime-type of the content described by this object.
0423: * If the source is not able to determine the mime-type by itself
0424: * this can be <code>null</code>.
0425: */
0426: public String getMimeType() {
0427: try {
0428: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0429: } catch (IOException e) {
0430: return null;
0431: }
0432: return this .resource.getGetContentType();
0433: }
0434:
0435: /**
0436: * Return the content length of the content or -1 if the length is
0437: * unknown
0438: */
0439: public long getContentLength() {
0440: try {
0441: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0442: } catch (IOException e) {
0443: return -1;
0444: }
0445: if (this .resource.isCollection()) {
0446: return -1;
0447: }
0448: return this .resource.getGetContentLength();
0449: }
0450:
0451: /**
0452: * Get the last modification date.
0453: * @return The last modification in milliseconds since January 1, 1970 GMT
0454: * or 0 if it is unknown
0455: */
0456: public long getLastModified() {
0457: try {
0458: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0459: } catch (IOException e) {
0460: return 0;
0461: }
0462: return this .resource.getGetLastModified();
0463: }
0464:
0465: /**
0466: * Does this source actually exist ?
0467: *
0468: * @return true if the resource exists.
0469: */
0470: public boolean exists() {
0471: try {
0472: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0473: } catch (SourceNotFoundException e) {
0474: return false;
0475: } catch (IOException e) {
0476: return true;
0477: }
0478: return this .resource.getExistence();
0479: }
0480:
0481: private InputStream resourcesToXml(WebdavResource[] resources)
0482: throws Exception {
0483: TransformerFactory tf = TransformerFactory.newInstance();
0484: TransformerHandler th = ((SAXTransformerFactory) tf)
0485: .newTransformerHandler();
0486: ByteArrayOutputStream bOut = new ByteArrayOutputStream();
0487: StreamResult result = new StreamResult(bOut);
0488: th.setResult(result);
0489: th.startDocument();
0490: th.startPrefixMapping(PREFIX, NAMESPACE);
0491: th.startElement(NAMESPACE, COLLECTION_NAME, PREFIX + ":"
0492: + COLLECTION_NAME, XMLUtils.EMPTY_ATTRIBUTES);
0493: resourcesToSax(resources, th);
0494: th.endElement(NAMESPACE, COLLECTION_NAME, PREFIX + ":"
0495: + COLLECTION_NAME);
0496: th.endPrefixMapping(PREFIX);
0497: th.endDocument();
0498:
0499: return new ByteArrayInputStream(bOut.toByteArray());
0500: }
0501:
0502: private void resourcesToSax(WebdavResource[] resources,
0503: ContentHandler handler) throws SAXException {
0504: for (int i = 0; i < resources.length; i++) {
0505: if (getLogger().isDebugEnabled()) {
0506: final String message = "RESOURCE: "
0507: + resources[i].getDisplayName();
0508: getLogger().debug(message);
0509: }
0510: if (resources[i].isCollection()) {
0511: try {
0512: WebdavResource[] childs = resources[i]
0513: .listWebdavResources();
0514: AttributesImpl attrs = new AttributesImpl();
0515: attrs.addAttribute(NAMESPACE, COLLECTION_NAME,
0516: PREFIX + ":name", "CDATA", resources[i]
0517: .getDisplayName());
0518: handler.startElement(NAMESPACE, COLLECTION_NAME,
0519: PREFIX + ":" + COLLECTION_NAME, attrs);
0520: this .resourcesToSax(childs, handler);
0521: handler.endElement(NAMESPACE, COLLECTION_NAME,
0522: PREFIX + ":" + COLLECTION_NAME);
0523: } catch (HttpException e) {
0524: if (getLogger().isDebugEnabled()) {
0525: final String message = "Unable to get WebDAV children. Server responded "
0526: + e.getReasonCode()
0527: + " ("
0528: + e.getReason()
0529: + ") - "
0530: + e.getMessage();
0531: getLogger().debug(message);
0532: }
0533: } catch (SAXException e) {
0534: if (getLogger().isDebugEnabled()) {
0535: final String message = "Unable to get WebDAV children: "
0536: + e.getMessage();
0537: getLogger().debug(message, e);
0538: }
0539: } catch (IOException e) {
0540: if (getLogger().isDebugEnabled()) {
0541: final String message = "Unable to get WebDAV children: "
0542: + e.getMessage();
0543: getLogger().debug(message, e);
0544: }
0545: } catch (Exception e) {
0546: if (getLogger().isDebugEnabled()) {
0547: final String message = "Unable to get WebDAV children: "
0548: + e.getMessage();
0549: getLogger().debug(message, e);
0550: }
0551: }
0552: } else {
0553: AttributesImpl attrs = new AttributesImpl();
0554: attrs.addAttribute(NAMESPACE, "name", PREFIX + ":name",
0555: "CDATA", resources[i].getDisplayName());
0556: handler.startElement(NAMESPACE, RESOURCE_NAME, PREFIX
0557: + ":" + RESOURCE_NAME, attrs);
0558: handler.endElement(NAMESPACE, RESOURCE_NAME, PREFIX
0559: + ":" + RESOURCE_NAME);
0560: }
0561: }
0562: }
0563:
0564: // ---------------------------------------------------- TraversableSource implementation
0565:
0566: /**
0567: * Get a collection child.
0568: *
0569: * @see org.apache.excalibur.source.TraversableSource#getChild(java.lang.String)
0570: */
0571: public Source getChild(String childName) throws SourceException {
0572: if (!isCollection()) {
0573: throw new SourceException(getSecureURI()
0574: + " is not a collection.");
0575: }
0576: try {
0577: HttpURL childURL;
0578: if (this .url instanceof HttpsURL) {
0579: childURL = new HttpsURL((HttpsURL) this .url, childName);
0580: } else {
0581: childURL = new HttpURL(this .url, childName);
0582: }
0583: return WebDAVSource.newWebDAVSource(childURL,
0584: this .protocol, getLogger(), eventfactory);
0585: } catch (URIException e) {
0586: throw new SourceException("Failed to create child", e);
0587: }
0588: }
0589:
0590: /**
0591: * Get the collection children.
0592: *
0593: * @see org.apache.excalibur.source.TraversableSource#getChildren()
0594: */
0595: public Collection getChildren() throws SourceException {
0596: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_1);
0597: ArrayList children = new ArrayList();
0598: try {
0599: WebdavResource[] resources = this .resource
0600: .listWebdavResources();
0601: for (int i = 0; i < resources.length; i++) {
0602: HttpURL childURL;
0603: if (this .url instanceof HttpsURL) {
0604: childURL = new HttpsURL((HttpsURL) this .url,
0605: resources[i].getName());
0606: } else {
0607: childURL = new HttpURL(this .url, resources[i]
0608: .getName());
0609: }
0610: WebDAVSource src = WebDAVSource.newWebDAVSource(
0611: resources[i], childURL, this .protocol,
0612: getLogger(), this .eventfactory);
0613: src.enableLogging(getLogger());
0614: children.add(src);
0615: }
0616: } catch (HttpException e) {
0617: if (getLogger().isDebugEnabled()) {
0618: final String message = "Unable to get WebDAV children. Server responded "
0619: + e.getReasonCode()
0620: + " ("
0621: + e.getReason()
0622: + ") - " + e.getMessage();
0623: getLogger().debug(message);
0624: }
0625: throw new SourceException(
0626: "Failed to get WebDAV collection children.", e);
0627: } catch (SourceException e) {
0628: throw e;
0629: } catch (IOException e) {
0630: throw new SourceException(
0631: "Failed to get WebDAV collection children.", e);
0632: }
0633: return children;
0634: }
0635:
0636: /**
0637: * Get the name of this resource.
0638: * @see org.apache.excalibur.source.TraversableSource#getName()
0639: */
0640: public String getName() {
0641: try {
0642: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0643: } catch (IOException e) {
0644: return "";
0645: }
0646: return this .resource.getName();
0647: }
0648:
0649: /**
0650: * Get the parent.
0651: *
0652: * @see org.apache.excalibur.source.TraversableSource#getParent()
0653: */
0654: public Source getParent() throws SourceException {
0655: String path;
0656: if (this .url.getEscapedPath().endsWith("/")) {
0657: path = "..";
0658: } else {
0659: path = ".";
0660: }
0661: try {
0662: HttpURL parentURL;
0663: if (url instanceof HttpsURL) {
0664: parentURL = new HttpsURL((HttpsURL) this .url, path);
0665: } else {
0666: parentURL = new HttpURL(this .url, path);
0667: }
0668: return WebDAVSource.newWebDAVSource(parentURL,
0669: this .protocol, getLogger(), eventfactory);
0670: } catch (URIException e) {
0671: throw new SourceException("Failed to create parent", e);
0672: }
0673: }
0674:
0675: /**
0676: * Check if this source is a collection.
0677: * @see org.apache.excalibur.source.TraversableSource#isCollection()
0678: */
0679: public boolean isCollection() {
0680: try {
0681: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
0682: } catch (IOException e) {
0683: return false;
0684: }
0685: return this .resource.isCollection();
0686: }
0687:
0688: // ---------------------------------------------------- ModifiableSource implementation
0689:
0690: /**
0691: * Get an <code>OutputStream</code> where raw bytes can be written to.
0692: * The signification of these bytes is implementation-dependent and
0693: * is not restricted to a serialized XML document.
0694: *
0695: * @return a stream to write to
0696: */
0697: public OutputStream getOutputStream() throws IOException {
0698: return new WebDAVSourceOutputStream(this );
0699: }
0700:
0701: /**
0702: * Can the data sent to an <code>OutputStream</code> returned by
0703: * {@link #getOutputStream()} be cancelled ?
0704: *
0705: * @return true if the stream can be cancelled
0706: */
0707: public boolean canCancel(OutputStream stream) {
0708: if (stream instanceof WebDAVSourceOutputStream) {
0709: WebDAVSourceOutputStream wsos = (WebDAVSourceOutputStream) stream;
0710: if (wsos.source == this ) {
0711: return wsos.canCancel();
0712: }
0713: }
0714: throw new IllegalArgumentException(
0715: "The stream is not associated to this source");
0716: }
0717:
0718: /**
0719: * Cancel the data sent to an <code>OutputStream</code> returned by
0720: * {@link #getOutputStream()}.
0721: * <p>
0722: * After cancel, the stream should no more be used.
0723: */
0724: public void cancel(OutputStream stream) throws SourceException {
0725: if (stream instanceof WebDAVSourceOutputStream) {
0726: WebDAVSourceOutputStream wsos = (WebDAVSourceOutputStream) stream;
0727: if (wsos.source == this ) {
0728: try {
0729: wsos.cancel();
0730: } catch (Exception e) {
0731: throw new SourceException(
0732: "Failure cancelling Source", e);
0733: }
0734: }
0735: }
0736: throw new IllegalArgumentException(
0737: "The stream is not associated to this source");
0738: }
0739:
0740: /**
0741: * Delete this source (unimplemented).
0742: * @see org.apache.excalibur.source.ModifiableSource#delete()
0743: */
0744: public void delete() throws SourceException {
0745: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0746: try {
0747: this .resource.deleteMethod();
0748: } catch (HttpException e) {
0749: throw new SourceException("Unable to delete source: "
0750: + getSecureURI(), e);
0751: } catch (IOException e) {
0752: throw new SourceException("Unable to delete source: "
0753: + getSecureURI(), e);
0754: }
0755: }
0756:
0757: private static class WebDAVSourceOutputStream extends
0758: ByteArrayOutputStream {
0759:
0760: private WebDAVSource source = null;
0761: private boolean isClosed = false;
0762:
0763: private WebDAVSourceOutputStream(WebDAVSource source) {
0764: this .source = source;
0765: }
0766:
0767: public void close() throws IOException {
0768: if (!isClosed) {
0769: try {
0770: super .close();
0771: this .source.initResource(WebdavResource.NOACTION,
0772: DepthSupport.DEPTH_0);
0773: this .source.resource.putMethod(toByteArray());
0774: } catch (HttpException he) {
0775: final String message = "Unable to close output stream. Server responded "
0776: + he.getReasonCode()
0777: + " ("
0778: + he.getReason() + ") - " + he.getMessage();
0779: this .source.getLogger().debug(message);
0780: throw new IOException(he.getMessage());
0781: } finally {
0782: this .isClosed = true;
0783: }
0784: }
0785: }
0786:
0787: private boolean canCancel() {
0788: return !isClosed;
0789: }
0790:
0791: private void cancel() {
0792: if (isClosed) {
0793: throw new IllegalStateException(
0794: "Cannot cancel: outputstream is already closed");
0795: }
0796: this .isClosed = true;
0797: }
0798: }
0799:
0800: // ---------------------------------------------------- ModifiableTraversableSource implementation
0801:
0802: /**
0803: * Create the collection, if it doesn't exist.
0804: * @see org.apache.excalibur.source.ModifiableTraversableSource#makeCollection()
0805: */
0806: public void makeCollection() throws SourceException {
0807: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0808: if (this .resource.exists())
0809: return;
0810: try {
0811: if (!this .resource.mkcolMethod()) {
0812: int status = this .resource.getStatusCode();
0813: if (status == 409) {
0814: // parent does not exist, create it and try again
0815: ((ModifiableTraversableSource) getParent())
0816: .makeCollection();
0817: makeCollection();
0818: } else if (status == 404) {
0819: // apparently mod_dav_svn wrongly returns 404
0820: // on MKCOL when parent does not exist
0821: ((ModifiableTraversableSource) getParent())
0822: .makeCollection();
0823: makeCollection();
0824: }
0825: // Ignore status 405 - Not allowed: collection already exists
0826: else if (status != 405) {
0827: final String msg = "Unable to create collection "
0828: + getSecureURI() + ". Server responded "
0829: + this .resource.getStatusCode() + " ("
0830: + this .resource.getStatusMessage() + ")";
0831: throw new SourceException(msg);
0832: }
0833: }
0834: } catch (HttpException e) {
0835: throw new SourceException("Unable to create collection(s) "
0836: + getSecureURI(), e);
0837: } catch (SourceException e) {
0838: throw e;
0839: } catch (IOException e) {
0840: throw new SourceException("Unable to create collection(s)"
0841: + getSecureURI(), e);
0842: }
0843: }
0844:
0845: // ---------------------------------------------------- InspectableSource implementation
0846:
0847: /**
0848: * Returns a enumeration of the properties
0849: *
0850: * @return Enumeration of SourceProperty
0851: *
0852: * @throws SourceException If an exception occurs.
0853: */
0854: public SourceProperty[] getSourceProperties()
0855: throws SourceException {
0856:
0857: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0858:
0859: Vector sourceproperties = new Vector();
0860: Enumeration props = null;
0861: org.apache.webdav.lib.Property prop = null;
0862:
0863: try {
0864: Enumeration responses = this .resource.propfindMethod(0);
0865: while (responses.hasMoreElements()) {
0866:
0867: ResponseEntity response = (ResponseEntity) responses
0868: .nextElement();
0869: props = response.getProperties();
0870: while (props.hasMoreElements()) {
0871: prop = (Property) props.nextElement();
0872: SourceProperty srcProperty = new SourceProperty(
0873: prop.getElement());
0874: sourceproperties.addElement(srcProperty);
0875: }
0876: }
0877:
0878: } catch (Exception e) {
0879: throw new SourceException("Error getting properties", e);
0880: }
0881: SourceProperty[] sourcepropertiesArray = new SourceProperty[sourceproperties
0882: .size()];
0883: for (int i = 0; i < sourceproperties.size(); i++) {
0884: sourcepropertiesArray[i] = (SourceProperty) sourceproperties
0885: .elementAt(i);
0886: }
0887: return sourcepropertiesArray;
0888: }
0889:
0890: /**
0891: * Returns a property from a source.
0892: *
0893: * @param namespace Namespace of the property
0894: * @param name Name of the property
0895: *
0896: * @return Property of the source.
0897: *
0898: * @throws SourceException If an exception occurs.
0899: */
0900: public SourceProperty getSourceProperty(String namespace,
0901: String name) throws SourceException {
0902:
0903: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0904:
0905: Vector propNames = new Vector(1);
0906: propNames.add(new PropertyName(namespace, name));
0907: Enumeration props = null;
0908: org.apache.webdav.lib.Property prop = null;
0909: try {
0910: Enumeration responses = this .resource.propfindMethod(0,
0911: propNames);
0912: while (responses.hasMoreElements()) {
0913: ResponseEntity response = (ResponseEntity) responses
0914: .nextElement();
0915: props = response.getProperties();
0916: if (props.hasMoreElements()) {
0917: prop = (Property) props.nextElement();
0918: return new SourceProperty(prop.getElement());
0919: }
0920: }
0921: } catch (Exception e) {
0922: throw new SourceException(
0923: "Error getting property: " + name, e);
0924: }
0925: return null;
0926: }
0927:
0928: /**
0929: * Remove a specified source property.
0930: *
0931: * @param namespace Namespace of the property.
0932: * @param name Name of the property.
0933: *
0934: * @throws SourceException If an exception occurs.
0935: */
0936: public void removeSourceProperty(String namespace, String name)
0937: throws SourceException {
0938:
0939: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0940:
0941: try {
0942: this .resource.proppatchMethod(new PropertyName(namespace,
0943: name), "", false);
0944: } catch (Exception e) {
0945: throw new SourceException("Could not remove property ", e);
0946: }
0947: }
0948:
0949: /**
0950: * Sets a property for a source.
0951: *
0952: * @param sourceproperty Property of the source
0953: *
0954: * @throws SourceException If an exception occurs during this operation
0955: */
0956: public void setSourceProperty(SourceProperty sourceproperty)
0957: throws SourceException {
0958:
0959: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
0960:
0961: try {
0962: Node node = null;
0963: NodeList list = sourceproperty.getValue().getChildNodes();
0964: for (int i = 0; i < list.getLength(); i++) {
0965: if ((list.item(i) instanceof Text && !"".equals(list
0966: .item(i).getNodeValue()))
0967: || list.item(i) instanceof Element) {
0968:
0969: node = list.item(i);
0970: break;
0971: }
0972: }
0973:
0974: Properties format = new Properties();
0975: format.put(OutputKeys.METHOD, "xml");
0976: format.put(OutputKeys.OMIT_XML_DECLARATION, "yes");
0977: String prop = XMLUtils.serializeNode(node, format);
0978:
0979: this .resource.proppatchMethod(new PropertyName(
0980: sourceproperty.getNamespace(), sourceproperty
0981: .getName()), prop, true);
0982:
0983: } catch (HttpException e) {
0984: final String message = "Unable to set property. Server responded "
0985: + e.getReasonCode()
0986: + " ("
0987: + e.getReason()
0988: + ") - "
0989: + e.getMessage();
0990: getLogger().debug(message);
0991: throw new SourceException("Could not set property ", e);
0992: } catch (Exception e) {
0993: throw new SourceException("Could not set property ", e);
0994: }
0995: }
0996:
0997: /**
0998: * Get the current credential for the source
0999: */
1000: // public SourceCredential getSourceCredential() throws SourceException {
1001: // if (this.principal != null) {
1002: // return new SourceCredential(this.principal, this.password);
1003: // }
1004: // return null;
1005: // }
1006: /**
1007: * Set the credential for the source
1008: */
1009: // public void setSourceCredential(SourceCredential sourcecredential)
1010: // throws SourceException {
1011: // if (sourcecredential != null) {
1012: // this.password = sourcecredential.getPassword();
1013: // this.principal = sourcecredential.getPrincipal();
1014: // refresh();
1015: // }
1016: // }
1017: // ---------------------------------------------------- MoveableSource
1018: /**
1019: * Move the current source to a specified destination.
1020: *
1021: * @param source
1022: *
1023: * @throws SourceException If an exception occurs during the move.
1024: */
1025: public void moveTo(Source source) throws SourceException {
1026: if (source instanceof WebDAVSource) {
1027: initResource(WebdavResource.NOACTION, DepthSupport.DEPTH_0);
1028: WebDAVSource destination = (WebDAVSource) source;
1029: destination.initResource(WebdavResource.BASIC,
1030: DepthSupport.DEPTH_0);
1031: try {
1032: this .resource.moveMethod(destination.resource
1033: .getHttpURL().getPath());
1034: } catch (HttpException e) {
1035: throw new SourceException("Cannot move source '"
1036: + getSecureURI() + "'", e);
1037: } catch (IOException e) {
1038: throw new SourceException("Cannot move source '"
1039: + getSecureURI() + "'", e);
1040: }
1041: } else {
1042: SourceUtil.move(this , source);
1043: }
1044: }
1045:
1046: /**
1047: * Copy the current source to a specified destination.
1048: *
1049: * @param source
1050: *
1051: * @throws SourceException If an exception occurs during the copy.
1052: */
1053: public void copyTo(Source source) throws SourceException {
1054: if (source instanceof WebDAVSource) {
1055: initResource(WebdavResource.BASIC, DepthSupport.DEPTH_0);
1056: WebDAVSource destination = (WebDAVSource) source;
1057: destination.initResource(WebdavResource.NOACTION,
1058: DepthSupport.DEPTH_0);
1059: try {
1060: this .resource.copyMethod(destination.resource
1061: .getHttpURL().getPath());
1062: } catch (HttpException e) {
1063: throw new SourceException("Cannot copy source '"
1064: + getSecureURI() + "'", e);
1065: } catch (IOException e) {
1066: throw new SourceException("Cannot copy source '"
1067: + getSecureURI() + "'", e);
1068: }
1069: } else {
1070: SourceUtil.copy(this, source);
1071: }
1072: }
1073: }
|