/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.vfs2.provider.webdav;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.methods.ByteArrayRequestEntity;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.commons.vfs2.FileContentInfoFactory;
import org.apache.commons.vfs2.FileNotFolderException;
import org.apache.commons.vfs2.FileNotFoundException;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileType;
import org.apache.commons.vfs2.NameScope;
import org.apache.commons.vfs2.provider.AbstractFileName;
import org.apache.commons.vfs2.provider.DefaultFileContent;
import org.apache.commons.vfs2.provider.URLFileName;
import org.apache.commons.vfs2.provider.http.HttpFileObject;
import org.apache.commons.vfs2.util.FileObjectUtils;
import org.apache.commons.vfs2.util.MonitorOutputStream;
import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.client.methods.CheckinMethod;
import org.apache.jackrabbit.webdav.client.methods.CheckoutMethod;
import org.apache.jackrabbit.webdav.client.methods.DavMethod;
import org.apache.jackrabbit.webdav.client.methods.DeleteMethod;
import org.apache.jackrabbit.webdav.client.methods.MkColMethod;
import org.apache.jackrabbit.webdav.client.methods.MoveMethod;
import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
import org.apache.jackrabbit.webdav.client.methods.PropPatchMethod;
import org.apache.jackrabbit.webdav.client.methods.PutMethod;
import org.apache.jackrabbit.webdav.client.methods.UncheckoutMethod;
import org.apache.jackrabbit.webdav.client.methods.VersionControlMethod;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
import org.apache.jackrabbit.webdav.property.DavPropertySet;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.version.DeltaVConstants;
import org.apache.jackrabbit.webdav.version.VersionControlledResource;
import org.apache.jackrabbit.webdav.xml.Namespace;
import org.w3c.dom.Node;

A WebDAV file.
Since:2.0
/** * A WebDAV file. * * @since 2.0 */
public class WebdavFileObject extends HttpFileObject<WebdavFileSystem> {
An OutputStream that writes to a Webdav resource.

TODO - Use piped stream to avoid temporary file.

/** * An OutputStream that writes to a Webdav resource. * <p> * TODO - Use piped stream to avoid temporary file. * </p> */
private class WebdavOutputStream extends MonitorOutputStream { private final WebdavFileObject file; public WebdavOutputStream(final WebdavFileObject file) { super(new ByteArrayOutputStream()); this.file = file; } private boolean createVersion(final String urlStr) { try { final VersionControlMethod method = new VersionControlMethod(urlStr); setupMethod(method); execute(method); return true; } catch (final Exception ex) { return false; } }
Called after this stream is closed.
/** * Called after this stream is closed. */
@Override protected void onClose() throws IOException { final RequestEntity entity = new ByteArrayRequestEntity(((ByteArrayOutputStream) out).toByteArray()); final URLFileName fileName = (URLFileName) getName(); final String urlStr = toUrlString(fileName); if (builder.isVersioning(getFileSystem().getFileSystemOptions())) { DavPropertySet set = null; boolean fileExists = true; boolean isCheckedIn = true; try { set = getPropertyNames(fileName); } catch (final FileNotFoundException fnfe) { fileExists = false; } if (fileExists && set != null) { if (set.contains(VersionControlledResource.CHECKED_OUT)) { isCheckedIn = false; } else if (!set.contains(VersionControlledResource.CHECKED_IN)) { DavProperty prop = set.get(VersionControlledResource.AUTO_VERSION); if (prop != null) { prop = getProperty(fileName, VersionControlledResource.AUTO_VERSION); if (DeltaVConstants.XML_CHECKOUT_CHECKIN.equals(prop.getValue())) { createVersion(urlStr); } } } } if (fileExists && isCheckedIn) { try { final CheckoutMethod checkout = new CheckoutMethod(urlStr); setupMethod(checkout); execute(checkout); isCheckedIn = false; } catch (final FileSystemException ex) { // Ignore the exception checking out. } } try { final PutMethod method = new PutMethod(urlStr); method.setRequestEntity(entity); setupMethod(method); execute(method); setUserName(fileName, urlStr); } catch (final FileSystemException ex) { if (!isCheckedIn) { try { final UncheckoutMethod method = new UncheckoutMethod(urlStr); setupMethod(method); execute(method); isCheckedIn = true; } catch (final Exception e) { // Ignore the exception. Going to throw original. } throw ex; } } if (!fileExists) { createVersion(urlStr); try { final DavPropertySet props = getPropertyNames(fileName); isCheckedIn = !props.contains(VersionControlledResource.CHECKED_OUT); } catch (final FileNotFoundException fnfe) { // Ignore the error } } if (!isCheckedIn) { final CheckinMethod checkin = new CheckinMethod(urlStr); setupMethod(checkin); execute(checkin); } } else { final PutMethod method = new PutMethod(urlStr); method.setRequestEntity(entity); setupMethod(method); execute(method); try { setUserName(fileName, urlStr); } catch (final IOException e) { // Ignore the exception if unable to set the user name. } } ((DefaultFileContent) this.file.getContent()).resetAttributes(); } private void setUserName(final URLFileName fileName, final String urlStr) throws IOException { final List<DefaultDavProperty> list = new ArrayList<>(); String name = builder.getCreatorName(getFileSystem().getFileSystemOptions()); final String userName = fileName.getUserName(); if (name == null) { name = userName; } else { if (userName != null) { final String comment = "Modified by user " + userName; list.add(new DefaultDavProperty(DeltaVConstants.COMMENT, comment)); } } list.add(new DefaultDavProperty(DeltaVConstants.CREATOR_DISPLAYNAME, name)); final PropPatchMethod method = new PropPatchMethod(urlStr, list); setupMethod(method); execute(method); } }
The character set property name.
/** The character set property name. */
public static final DavPropertyName RESPONSE_CHARSET = DavPropertyName.create("response-charset");
The FileSystemConfigBuilder
/** The FileSystemConfigBuilder */
private final WebdavFileSystemConfigBuilder builder; private final WebdavFileSystem fileSystem; protected WebdavFileObject(final AbstractFileName name, final WebdavFileSystem fileSystem) { super(name, fileSystem, WebdavFileSystemConfigBuilder.getInstance()); this.fileSystem = fileSystem; builder = (WebdavFileSystemConfigBuilder) WebdavFileSystemConfigBuilder.getInstance(); } protected void configureMethod(final HttpMethodBase httpMethod) { httpMethod.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, WebdavMethodRetryHandler.getInstance()); }
Creates this file as a folder.
/** * Creates this file as a folder. */
@Override protected void doCreateFolder() throws Exception { final DavMethod method = new MkColMethod(toUrlString((URLFileName) getName())); setupMethod(method); try { execute(method); } catch (final FileSystemException fse) { throw new FileSystemException("vfs.provider.webdav/create-collection.error", getName(), fse); } }
Deletes the file.
/** * Deletes the file. */
@Override protected void doDelete() throws Exception { final DavMethod method = new DeleteMethod(toUrlString((URLFileName) getName())); setupMethod(method); execute(method); }
Returns the properties of the Webdav resource.
/** * Returns the properties of the Webdav resource. */
@Override protected Map<String, Object> doGetAttributes() throws Exception { final Map<String, Object> attributes = new HashMap<>(); try { final URLFileName fileName = (URLFileName) getName(); DavPropertySet properties = getProperties(fileName, DavConstants.PROPFIND_ALL_PROP, new DavPropertyNameSet(), false); @SuppressWarnings("unchecked") // iterator() is documented to return DavProperty instances final Iterator<DavProperty> iter = properties.iterator(); while (iter.hasNext()) { final DavProperty property = iter.next(); attributes.put(property.getName().toString(), property.getValue()); } properties = getPropertyNames(fileName); @SuppressWarnings("unchecked") // iterator() is documented to return DavProperty instances final Iterator<DavProperty> iter2 = properties.iterator(); while (iter2.hasNext()) { DavProperty property = iter2.next(); if (!attributes.containsKey(property.getName().getName())) { property = getProperty(fileName, property.getName()); if (property != null) { final Object name = property.getName(); final Object value = property.getValue(); if (name != null && value != null) { attributes.put(name.toString(), value); } } } } return attributes; } catch (final Exception e) { throw new FileSystemException("vfs.provider.webdav/get-attributes.error", getName(), e); } }
Returns the size of the file content (in bytes).
/** * Returns the size of the file content (in bytes). */
@Override protected long doGetContentSize() throws Exception { final DavProperty property = getProperty((URLFileName) getName(), DavConstants.PROPERTY_GETCONTENTLENGTH); if (property != null) { final String value = (String) property.getValue(); return Long.parseLong(value); } return 0; }
Returns the last modified time of this file. Is only called if doGetType does not return FileType.IMAGINARY.
/** * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. */
@Override protected long doGetLastModifiedTime() throws Exception { final DavProperty property = getProperty((URLFileName) getName(), DavConstants.PROPERTY_GETLASTMODIFIED); if (property != null) { final String value = (String) property.getValue(); return DateUtil.parseDate(value).getTime(); } return 0; } @Override protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { return new WebdavOutputStream(this); }
Determines the type of this file. Must not return null. The return value of this method is cached, so the implementation can be expensive.
/** * Determines the type of this file. Must not return null. The return value of this method is cached, so the * implementation can be expensive. */
@Override protected FileType doGetType() throws Exception { try { return isDirectory((URLFileName) getName()) ? FileType.FOLDER : FileType.FILE; } catch (final FileNotFolderException fnfe) { return FileType.IMAGINARY; } catch (final FileNotFoundException fnfe) { return FileType.IMAGINARY; } }
Determines if this file can be written to. Is only called if doGetType does not return FileType.IMAGINARY.

This implementation always returns true.

Throws:
Returns:true if the file is writable.
/** * Determines if this file can be written to. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. * <p> * This implementation always returns true. * * @return true if the file is writable. * @throws Exception if an error occurs. */
@Override protected boolean doIsWriteable() throws Exception { return true; }
Lists the children of the file.
/** * Lists the children of the file. */
@Override protected String[] doListChildren() throws Exception { // use doListChildrenResolved for performance return null; }
Lists the children of the file.
/** * Lists the children of the file. */
@Override protected FileObject[] doListChildrenResolved() throws Exception { PropFindMethod method = null; try { final URLFileName name = (URLFileName) getName(); if (isDirectory(name)) { final DavPropertyNameSet nameSet = new DavPropertyNameSet(); nameSet.add(DavPropertyName.create(DavConstants.PROPERTY_DISPLAYNAME)); method = new PropFindMethod(toUrlString(name), nameSet, DavConstants.DEPTH_1); execute(method); final List<WebdavFileObject> vfs = new ArrayList<>(); if (method.succeeded()) { final MultiStatusResponse[] responses = method.getResponseBodyAsMultiStatus().getResponses(); for (final MultiStatusResponse response : responses) { if (isCurrentFile(response.getHref(), name)) { continue; } final String resourceName = resourceName(response.getHref()); if (resourceName != null && resourceName.length() > 0) { final WebdavFileObject fo = (WebdavFileObject) FileObjectUtils.getAbstractFileObject( getFileSystem().resolveFile(getFileSystem().getFileSystemManager() .resolveName(getName(), resourceName, NameScope.CHILD))); vfs.add(fo); } } } return vfs.toArray(new WebdavFileObject[vfs.size()]); } throw new FileNotFolderException(getName()); } catch (final FileNotFolderException fnfe) { throw fnfe; } catch (final DavException e) { throw new FileSystemException(e.getMessage(), e); } catch (final IOException e) { throw new FileSystemException(e.getMessage(), e); } finally { if (method != null) { method.releaseConnection(); } } }
Rename the file.
/** * Rename the file. */
@Override protected void doRename(final FileObject newFile) throws Exception { final String url = encodePath(toUrlString((URLFileName) getName())); final String dest = toUrlString((URLFileName) newFile.getName(), false); final DavMethod method = new MoveMethod(url, dest, false); setupMethod(method); execute(method); }
Sets an attribute of this file. Is only called if doGetType does not return FileType.IMAGINARY.
/** * Sets an attribute of this file. Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. */
@Override protected void doSetAttribute(final String attrName, final Object value) throws Exception { try { final URLFileName fileName = (URLFileName) getName(); final String urlStr = toUrlString(fileName); final DavPropertySet properties = new DavPropertySet(); final DavPropertyNameSet propertyNameSet = new DavPropertyNameSet(); final DavProperty property = new DefaultDavProperty(attrName, value, Namespace.EMPTY_NAMESPACE); if (value != null) { properties.add(property); } else { propertyNameSet.add(property.getName()); // remove property } final PropPatchMethod method = new PropPatchMethod(urlStr, properties, propertyNameSet); setupMethod(method); execute(method); if (!method.succeeded()) { throw new FileSystemException("Property '" + attrName + "' could not be set."); } } catch (final FileSystemException fse) { throw fse; } catch (final Exception e) { throw new FileSystemException("vfs.provider.webdav/set-attributes", e, getName(), attrName); } }
Execute a 'Workspace' operation.
Params:
  • method – The DavMethod to invoke.
Throws:
/** * Execute a 'Workspace' operation. * * @param method The DavMethod to invoke. * @throws FileSystemException If an error occurs. */
private void execute(final DavMethod method) throws FileSystemException { try { final int status = fileSystem.getClient().executeMethod(method); if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) { throw new FileNotFoundException(method.getURI()); } method.checkSuccess(); } catch (final FileSystemException fse) { throw fse; } catch (final IOException e) { throw new FileSystemException(e); } catch (final DavException e) { throw ExceptionConverter.generate(e); } finally { if (method != null) { method.releaseConnection(); } } } @Override protected FileContentInfoFactory getFileContentInfoFactory() { return new WebdavFileContentInfoFactory(); } DavPropertySet getProperties(final URLFileName name) throws FileSystemException { return getProperties(name, DavConstants.PROPFIND_ALL_PROP, new DavPropertyNameSet(), false); } DavPropertySet getProperties(final URLFileName name, final DavPropertyNameSet nameSet, final boolean addEncoding) throws FileSystemException { return getProperties(name, DavConstants.PROPFIND_BY_PROPERTY, nameSet, addEncoding); } DavPropertySet getProperties(final URLFileName name, final int type, final DavPropertyNameSet nameSet, final boolean addEncoding) throws FileSystemException { try { final String urlStr = toUrlString(name); final PropFindMethod method = new PropFindMethod(urlStr, type, nameSet, DavConstants.DEPTH_0); setupMethod(method); execute(method); if (method.succeeded()) { final MultiStatus multiStatus = method.getResponseBodyAsMultiStatus(); final MultiStatusResponse response = multiStatus.getResponses()[0]; final DavPropertySet props = response.getProperties(HttpStatus.SC_OK); if (addEncoding) { final DavProperty prop = new DefaultDavProperty(RESPONSE_CHARSET, method.getResponseCharSet()); props.add(prop); } return props; } return new DavPropertySet(); } catch (final FileSystemException fse) { throw fse; } catch (final Exception e) { throw new FileSystemException("vfs.provider.webdav/get-property.error", e, getName(), name, type, nameSet.getContent(), addEncoding); } } DavProperty getProperty(final URLFileName fileName, final DavPropertyName name) throws FileSystemException { final DavPropertyNameSet nameSet = new DavPropertyNameSet(); nameSet.add(name); final DavPropertySet propertySet = getProperties(fileName, nameSet, false); return propertySet.get(name); } DavProperty getProperty(final URLFileName fileName, final String property) throws FileSystemException { return getProperty(fileName, DavPropertyName.create(property)); } DavPropertySet getPropertyNames(final URLFileName name) throws FileSystemException { return getProperties(name, DavConstants.PROPFIND_PROPERTY_NAMES, new DavPropertyNameSet(), false); }
Convert the FileName to an encoded url String.
Params:
  • name – The FileName.
Returns:The encoded URL String.
/** * Convert the FileName to an encoded url String. * * @param name The FileName. * @return The encoded URL String. */
private String hrefString(final URLFileName name) { final URLFileName newFile = new URLFileName("http", name.getHostName(), name.getPort(), name.getDefaultPort(), null, null, name.getPath(), name.getType(), name.getQueryString()); try { return newFile.getURIEncoded(this.getUrlCharset()); } catch (final Exception e) { return name.getURI(); } } private boolean isCurrentFile(final String href, final URLFileName fileName) { String name = hrefString(fileName); if (href.endsWith("/") && !name.endsWith("/")) { name += "/"; } return href.equals(name) || href.equals(fileName.getPath()); } private boolean isDirectory(final URLFileName name) throws IOException { try { final DavProperty property = getProperty(name, DavConstants.PROPERTY_RESOURCETYPE); Node node; if (property != null && (node = (Node) property.getValue()) != null) { return node.getLocalName().equals(DavConstants.XML_COLLECTION); } return false; } catch (final FileNotFoundException fse) { throw new FileNotFolderException(name); } }
Returns the resource name from the path.
Params:
  • path – the path to the file.
Returns:The resource name
/** * Returns the resource name from the path. * * @param path the path to the file. * @return The resource name */
private String resourceName(String path) { if (path.endsWith("/")) { path = path.substring(0, path.length() - 1); } final int i = path.lastIndexOf("/"); return i >= 0 ? path.substring(i + 1) : path; }
Prepares a Method object.
Params:
  • method – the HttpMethod.
Throws:
/** * Prepares a Method object. * * @param method the HttpMethod. * @throws FileSystemException if an error occurs encoding the uri. * @throws URIException if the URI is in error. */
@Override protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException { final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset()); method.setPath(pathEncoded); method.setFollowRedirects(this.getFollowRedirect()); method.setRequestHeader("User-Agent", "Jakarta-Commons-VFS"); method.addRequestHeader("Cache-control", "no-cache"); method.addRequestHeader("Cache-store", "no-store"); method.addRequestHeader("Pragma", "no-cache"); method.addRequestHeader("Expires", "0"); } private String toUrlString(final URLFileName name) { return toUrlString(name, true); }
Converts the given URLFileName to an encoded URL String.
Params:
  • name – The FileName.
  • includeUserInfo – true if user information should be included.
Returns:The encoded URL String.
/** * Converts the given URLFileName to an encoded URL String. * * @param name The FileName. * @param includeUserInfo true if user information should be included. * @return The encoded URL String. */
private String toUrlString(final URLFileName name, final boolean includeUserInfo) { String user = null; String password = null; if (includeUserInfo) { user = name.getUserName(); password = name.getPassword(); } final URLFileName newFile = new URLFileName("http", name.getHostName(), name.getPort(), name.getDefaultPort(), user, password, name.getPath(), name.getType(), name.getQueryString()); try { return newFile.getURIEncoded(this.getUrlCharset()); } catch (final Exception e) { return name.getURI(); } } }