org.cast.cwm.dav.DavResource.java Source code

Java tutorial

Introduction

Here is the source code for org.cast.cwm.dav.DavResource.java

Source

/*
 * Copyright 2011-2016 CAST, Inc.
 *
 * This file is part of the CAST Wicket Modules:
 * see <http://code.google.com/p/cast-wicket-modules>.
 *
 * The CAST Wicket Modules are free software: you can redistribute and/or
 * modify them under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * The CAST Wicket Modules are distributed in the hope that they will be
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.cast.cwm.dav;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.wicket.Application;
import org.apache.wicket.request.resource.AbstractResource;
import org.apache.wicket.request.resource.IResource;
import org.apache.wicket.request.resource.ResourceReference;
import org.apache.wicket.util.io.IOUtils;
import org.apache.wicket.util.lang.Bytes;
import org.apache.wicket.util.resource.AbstractResourceStream;
import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
import org.apache.wicket.util.time.Time;
import org.cast.cwm.IInputStreamProvider;
import org.cast.cwm.IRelativeLinkSource;
import org.cast.cwm.InputStreamNotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xmlmind.davclient.Constants;
import com.xmlmind.davclient.Content;
import com.xmlmind.davclient.DAVClient;
import com.xmlmind.davclient.DAVException;
import com.xmlmind.davclient.Property;
import com.xmlmind.davclient.PropertyList;

/**
 * A Resource for a file on a DAV server.
 * 
 * @author bgoldowsky
 *
 */
public class DavResource extends AbstractResource implements IInputStreamProvider, IRelativeLinkSource {

    protected final String clientName;
    protected final String path;
    protected String contentType = null;
    protected Time lastModified = null; // our view of when file last changed
    protected Time serverLastModified = null; // what server is reporting about last modification time
    protected Long fileSize = null;

    protected Long serverCheckInterval = 30L; // how many seconds between checks of the DAV server for modifications
    protected Long lastServerCheck = null;

    // Map of related resources that have been found before - so we return the same one for the same query each time.
    protected Map<String, DavResource> resourceMap = new HashMap<String, DavResource>();

    protected static final QName[] interestingProperties = { DAVClient.GETLASTMODIFIED_PROP,
            DAVClient.GETCONTENTLENGTH_PROP };

    private static final Logger log = LoggerFactory.getLogger(DavResource.class);
    private static final long serialVersionUID = 1L;

    public DavResource(String clientName, String path) {
        super();
        this.clientName = clientName;
        this.path = path;
        if (!path.startsWith("/"))
            throw new IllegalArgumentException(
                    "DAV Resource path name must be absolute; provided path was " + path);
    }

    /**
     * @see org.apache.wicket.request.resource.AbstractResource#newResourceResponse(org.apache.wicket.request.resource.IResource.Attributes)
     */
    @Override
    protected ResourceResponse newResourceResponse(final Attributes attributes) {
        final ResourceResponse response = new ResourceResponse();

        // Set last modified time so that response can determine if data needs to be written.
        response.setLastModified(lastModifiedTime());

        if (response.dataNeedsToBeWritten(attributes)) {
            DavResourceStream resourceStream = new DavResourceStream();
            final byte[] bytes;
            try {
                bytes = IOUtils.toByteArray(resourceStream.getInputStream());
                response.setContentLength(bytes.length);
                response.setLastModified(lastModified);
                if (Application.exists())
                    response.setContentType(Application.get().getMimeType(path));

                response.setWriteCallback(new WriteCallback() {
                    @Override
                    public void writeData(Attributes attributes) {
                        attributes.getResponse().write(bytes);
                    }
                });
            } catch (IOException e) {
                log.error("I/O Exception while trying to read DAV resource");
                e.printStackTrace();
                response.setError(404);
            } catch (ResourceStreamNotFoundException e) {
                log.error("DAV resource not found: " + path);
                response.setError(404);
            } finally {
                try {
                    resourceStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return response;
    }

    public DAVClient getClient() {
        return DavClientManager.get().getClient(clientName);
    }

    @Override
    public Time lastModifiedTime() {
        retrieveProperties();
        return lastModified;
    }

    public Long getFileSize() {
        retrieveProperties();
        return fileSize;
    }

    /**
     * Return an InputStream for this resource.
     */
    @Override
    public InputStream getInputStream() throws InputStreamNotFoundException {
        try {
            return new DavResourceStream().getInputStream();
        } catch (ResourceStreamNotFoundException e) {
            throw new InputStreamNotFoundException(e);
        }
    }

    /**
     * Return an InputStream for a resource relative to this one.
     */
    @Override
    public InputStream getInputStream(String relativePath) throws InputStreamNotFoundException {
        return getRelativeResource(relativePath).getInputStream();
    }

    /** Return a ResourceReference to a relatively-addressed item
     * 
     * @param relativePath path relative to the path of this Resource
     * @return a ResourceReference that will resolve to {@link #getRelative(relativePath)}
     */
    @Override
    public ResourceReference getRelativeReference(final String relativePath) {
        String childPath = path.substring(1, path.lastIndexOf('/') + 1) + relativePath;
        return new ResourceReference(DavResource.class, childPath) {
            private static final long serialVersionUID = 1L;

            @Override
            public IResource getResource() {
                return getRelativeResource(relativePath);
            }
        };
    }

    /**
     * Return a Resource accessed via a relative path from this Resource.
     * Each Resource created for such queries will be remembered, and
     * the same Resource will be returned again for a later query.  This allows for
     * the child Resources to do caching.
     */
    public DavResource getRelativeResource(String relativePath) {
        if (resourceMap.containsKey(relativePath)) {
            return resourceMap.get(relativePath);
        } else {
            DavResource relative = new DavResource(clientName,
                    path.substring(0, path.lastIndexOf('/') + 1) + relativePath);
            resourceMap.put(relativePath, relative);
            return relative;
        }
    }

    protected void setMetadata(Time newLastModified, long newFileSize) {
        if (serverLastModified == null || newLastModified.after(serverLastModified)) {
            // Server's update time has changed
            fileSize = newFileSize;
            serverLastModified = newLastModified;
            lastModified = newLastModified;
        } else if (newFileSize != fileSize) {
            // Odd situation -- last modified time reported the same, but size has changed.
            // XDR actually gets into this state if updates have been stored without creating a new "version"
            // Update our information on modification time so that the webapp can see the changes immediately.
            log.debug("File size changed: setting current time as last modification");
            fileSize = newFileSize;
            lastModified = Time.now();
            // serverLastModified left as is, so that we can continue to recognize further stealthy updates.
        }
    }

    // Look up properties of this content object's file on the server if we haven't recently done so.
    synchronized protected void retrieveProperties() {
        long time = new Date().getTime();
        if (lastServerCheck == null || (time > lastServerCheck + serverCheckInterval * 1000L)) {
            Property[] props = getProperties(path);
            if (props != null) {
                setMetadata(getLastModifiedValue(props), getLengthValue(props));
            } else {
                log.warn("Server returned no properties for {}", path);
            }
            lastServerCheck = time;
        }
    }

    /**
     * Get interesting properties of a given filePath.  Returns null if the file doesn't exist.
     * @param filePath
     * @return
     */
    protected Property[] getProperties(String filePath) {
        PropertyList[] props = null;
        try {
            props = getClient().propfind(filePath, interestingProperties, 0);
            log.debug("PROPFIND on {}", filePath);
        } catch (DAVException e) {
            // There is one error type that we can actually handle: a 404 indicating the object was not found.
            if (e.errors[0].statusCode == 404)
                return null;
            throw new RuntimeException("Can't get properties", e);
        } catch (IOException e) {
            throw new RuntimeException("Can't get properties", e);
        }
        if (props.length < 1)
            throw new RuntimeException("No properties returned");

        // We're only interested in props[0] since we've only requested info about a single resource.
        return props[0].properties;
    }

    protected Time getLastModifiedValue(Property[] properties) {
        return Time.valueOf((Date) getValue(Constants.GETLASTMODIFIED_PROP, properties));
    }

    protected Long getLengthValue(Property[] properties) {
        return (Long) getValue(Constants.GETCONTENTLENGTH_PROP, properties);
    }

    // Method adapted from src/com/xmlmind/davclient/DavClientTool.java
    // for each property, name has getNamespaceURI() and getLocalPart(); then there's a value which can be cast 
    // (Long)getValue(GETCONTENTLENGTH_PROP, properties)
    // (Date)getValue(GETLASTMODIFIED_PROP, properties)
    protected Object getValue(QName name, Property[] properties) {
        String namespace = name.getNamespaceURI();
        String localName = name.getLocalPart();
        for (Property property : properties) {
            if (property.name.getNamespaceURI().equals(namespace) && property.name.getLocalPart().equals(localName))
                return property.value;
        }
        return null;
    }

    protected class DavResourceStream extends AbstractResourceStream {

        protected InputStream stream = null;

        private static final long serialVersionUID = 1L;

        @Override
        public void close() throws IOException {
            if (stream != null)
                stream.close();
        }

        @Override
        public InputStream getInputStream() throws ResourceStreamNotFoundException {
            Content content = null;
            try {
                content = getClient().get(path);
                log.debug("GET file content for {}", path);
            } catch (IOException e) {
                throw new ResourceStreamNotFoundException(e);
            } catch (DAVException e) {
                throw new ResourceStreamNotFoundException(e);
            }
            // Update file size and mod date based on the Content object
            setMetadata(Time.millis(content.getContentDate()), content.getContentLength());
            contentType = content.getContentType();
            try {
                stream = content.openContent();
                return stream;
            } catch (IOException e) {
                e.printStackTrace();
                throw new ResourceStreamNotFoundException(e);
            }
        }

        @Override
        public Bytes length() {
            return Bytes.bytes(getFileSize());
        }

    }

}