org.osaf.cosmo.dav.servlet.StandardRequestHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.osaf.cosmo.dav.servlet.StandardRequestHandler.java

Source

/*
 * Copyright 2007 Open Source Applications Foundation
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.osaf.cosmo.dav.servlet;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.abdera.util.EntityTag;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osaf.cosmo.dav.DavCollection;
import org.osaf.cosmo.dav.DavContent;
import org.osaf.cosmo.dav.DavException;
import org.osaf.cosmo.dav.DavRequest;
import org.osaf.cosmo.dav.DavResource;
import org.osaf.cosmo.dav.DavResourceFactory;
import org.osaf.cosmo.dav.DavResourceLocatorFactory;
import org.osaf.cosmo.dav.DavResponse;
import org.osaf.cosmo.dav.MethodNotAllowedException;
import org.osaf.cosmo.dav.NotModifiedException;
import org.osaf.cosmo.dav.PreconditionFailedException;
import org.osaf.cosmo.dav.acl.DavPrivilege;
import org.osaf.cosmo.dav.acl.NeedsPrivilegesException;
import org.osaf.cosmo.dav.acl.resource.DavUserPrincipal;
import org.osaf.cosmo.dav.acl.resource.DavUserPrincipalCollection;
import org.osaf.cosmo.dav.impl.DavCalendarCollection;
import org.osaf.cosmo.dav.impl.DavCalendarResource;
import org.osaf.cosmo.dav.impl.DavCollectionBase;
import org.osaf.cosmo.dav.impl.DavHomeCollection;
import org.osaf.cosmo.dav.impl.DavOutboxCollection;
import org.osaf.cosmo.dav.impl.StandardDavRequest;
import org.osaf.cosmo.dav.impl.StandardDavResponse;
import org.osaf.cosmo.dav.provider.CalendarCollectionProvider;
import org.osaf.cosmo.dav.provider.CalendarResourceProvider;
import org.osaf.cosmo.dav.provider.CollectionProvider;
import org.osaf.cosmo.dav.provider.DavProvider;
import org.osaf.cosmo.dav.provider.FileProvider;
import org.osaf.cosmo.dav.provider.HomeCollectionProvider;
import org.osaf.cosmo.dav.provider.OutboxCollectionProvider;
import org.osaf.cosmo.dav.provider.UserPrincipalCollectionProvider;
import org.osaf.cosmo.dav.provider.UserPrincipalProvider;
import org.osaf.cosmo.model.EntityFactory;
import org.osaf.cosmo.model.ItemSecurityException;
import org.osaf.cosmo.security.CosmoSecurityException;
import org.osaf.cosmo.security.Permission;
import org.osaf.cosmo.server.ServerConstants;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.web.HttpRequestHandler;

/**
 * <p>
 * An implementation of the Spring {@link HttpRequestHandler} that
 * services WebDAV requests. Finds the resource being acted upon, checks that
 * conditions are right for the request and resource, chooses a provider
 * based on the resource type, and then delegates to a specific provider
 * method based on the request method.
 * </p>
 */
public class StandardRequestHandler implements HttpRequestHandler, ServerConstants {
    private static final Log log = LogFactory.getLog(StandardRequestHandler.class);

    private DavResourceLocatorFactory locatorFactory;
    private DavResourceFactory resourceFactory;
    private EntityFactory entityFactory;

    // RequestHandler methods

    /**
     * <p>
     * Processes the request and returns a response. Calls
     * {@link DavResourceFactory.createResource(DavResourceLocator, DavRequest, DavResponse)}
     * to find the targeted resource. Calls {@link #preconditions(DavRequest, DavResponse, DavResource)}
     * to verify preconditions. Calls {@link #process(DavRequest, DavResponse, DavResource)}
     * to execute the verified request.
     * </p>
     * <p>
     * Invalid preconditions and processing exceptions are handled by
     * sending a response with the appropriate error status and message and
     * an entity describing the error.
     * </p>
     */
    public void handleRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        dumpRequest(request);
        DavRequest wreq = null;
        DavResponse wres = null;

        try {
            wreq = createDavRequest(request);
            wres = createDavResponse(response);

            DavResource resource = resolveTarget(wreq);
            preconditions(wreq, wres, resource);
            process(wreq, wres, resource);
        } catch (CosmoSecurityException e) {
            // handle security errors
            NeedsPrivilegesException npe = null;
            // Determine required privilege if we can and include
            // in response
            if (e instanceof ItemSecurityException) {
                ItemSecurityException ise = (ItemSecurityException) e;
                DavPrivilege priv = ise.getPermission() == Permission.READ ? DavPrivilege.READ : DavPrivilege.WRITE;
                npe = new NeedsPrivilegesException(wreq.getRequestURI(), priv);
            } else {
                // otherwise send generic response
                npe = new NeedsPrivilegesException(e.getMessage());
            }
            wres.sendDavError(npe);
        } catch (Throwable e) {
            DavException de = null;
            if (e instanceof DavException) {
                de = (DavException) e;
            } else {
                de = new DavException(e);
                // stuff the exception into a request attribute so that
                // filters can examine it
                request.setAttribute(ATTR_SERVICE_EXCEPTION, e);
            }

            // We need a way to differentiate exceptions that are "expected" so that the
            // logs don't get too polluted with errors.  For example, OptimisticLockingFailureException
            // is expected and should be handled by the retry logic that is one layer above.
            // Although not ideal, for now simply check for this type and log at a different level.
            if (e instanceof OptimisticLockingFailureException) {
                log.info("Internal dav error", e);
            } else if (de.getErrorCode() >= 500) {
                log.error("Internal dav error", e);
            } else if (de.getErrorCode() >= 400 && de.getMessage() != null) {
                log.info("Client error (" + de.getErrorCode() + "): " + de.getMessage());
            }

            wres.sendDavError(de);
        }
    }

    // our methods

    /**
     * <p>
     * Validates preconditions that must exist before the request may be
     * executed. If a precondition is specified but is not met, the
     * appropriate response is set and <code>false</code> is returned.
     * </p>
     * <p>
     * These preconditions are checked:
     * </p>
     * <ul>
     * <li>The <code>If-Match</code> request header</li>
     * <li>The <code>If-None-Match</code> request header</li>
     * <li>The <code>If-Modified-Since</code> request header</li>
     * <li>The <code>If-Unmodified-Since</code> request header</li>
     * </ul>
     */
    protected void preconditions(DavRequest request, DavResponse response, DavResource resource)
            throws DavException, IOException {
        ifMatch(request, response, resource);
        ifNoneMatch(request, response, resource);
        ifModifiedSince(request, response, resource);
        ifUnmodifiedSince(request, response, resource);
    }

    @SuppressWarnings("unchecked")
    private void dumpRequest(HttpServletRequest req) {
        if (!log.isTraceEnabled())
            return;

        StringBuffer sb = new StringBuffer("\n------------------------ Dump of request -------------------\n");
        try {
            Enumeration names = req.getHeaderNames();

            sb.append("Request headers:\n");
            while (names.hasMoreElements()) {
                String key = (String) names.nextElement();
                String val = req.getHeader(key);
                sb.append("  ").append(key).append(" = \"").append(val).append("\"\n");
            }

            names = req.getParameterNames();
            String title = "Request parameters";

            sb.append(title).append(" - global info and uris:").append("\n");
            sb.append("getMethod = ").append(req.getMethod()).append("\n");
            sb.append("getRemoteAddr = ").append(req.getRemoteAddr()).append("\n");
            sb.append("getRequestURI = ").append(req.getRequestURI()).append("\n");
            sb.append("getRemoteUser = ").append(req.getRemoteUser()).append("\n");
            sb.append("getRequestedSessionId = ").append(req.getRequestedSessionId()).append("\n");
            sb.append("HttpUtils.getRequestURL(req) = ").append(req.getRequestURL()).append("\n");
            sb.append("contextPath=").append(req.getContextPath()).append("\n");
            sb.append("query=").append(req.getQueryString()).append("\n");
            sb.append("contentlen=").append(req.getContentLength()).append("\n");
            sb.append("request=").append(req).append("\n");
            sb.append(title).append(":\n");

            while (names.hasMoreElements()) {
                String key = (String) names.nextElement();
                String val = req.getParameter(key);
                sb.append("  ").append(key).append(" = \"").append(val).append("\"").append("\n");
                ;
            }
            sb.append("Request attributes:\n");
            for (Enumeration<String> e = req.getAttributeNames(); e.hasMoreElements();) {
                String key = (String) e.nextElement();
                Object val = req.getAttribute(key);
                sb.append("  ").append(key).append(" = \"").append(val).append("\"").append("\n");
                ;
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
        sb.append("------------------------ End dump of request -------------------");
        log.trace(sb);
    }

    /**
     * <p>
     * Hands the request off to a provider method for handling. The provider
     * is created by calling {@link #createProvider(DavResource)}. The
     * specific provider method is chosen by examining the request method.
     * </p>
     */
    protected void process(DavRequest request, DavResponse response, DavResource resource)
            throws IOException, DavException {
        DavProvider provider = createProvider(resource);

        if (request.getMethod().equals("OPTIONS"))
            options(request, response, resource);
        else if (request.getMethod().equals("GET"))
            provider.get(request, response, resource);
        else if (request.getMethod().equals("HEAD"))
            provider.head(request, response, resource);
        else if (request.getMethod().equals("POST"))
            provider.post(request, response, resource);
        else if (request.getMethod().equals("PROPFIND"))
            provider.propfind(request, response, resource);
        else if (request.getMethod().equals("PROPPATCH"))
            provider.proppatch(request, response, resource);
        else if (request.getMethod().equals("DELETE"))
            provider.delete(request, response, resource);
        else if (request.getMethod().equals("COPY"))
            provider.copy(request, response, resource);
        else if (request.getMethod().equals("MOVE"))
            provider.move(request, response, resource);
        else if (request.getMethod().equals("REPORT"))
            provider.report(request, response, resource);
        else if (request.getMethod().equals("MKTICKET"))
            provider.mkticket(request, response, resource);
        else if (request.getMethod().equals("DELTICKET"))
            provider.delticket(request, response, resource);
        else if (request.getMethod().equals("ACL"))
            provider.acl(request, response, resource);
        else {
            if (resource.isCollection()) {
                if (request.getMethod().equals("MKCOL"))
                    provider.mkcol(request, response, (DavCollection) resource);
                else if (request.getMethod().equals("MKCALENDAR"))
                    provider.mkcalendar(request, response, (DavCollection) resource);
                else
                    throw new MethodNotAllowedException(request.getMethod() + " not allowed for a collection");
            } else {
                if (request.getMethod().equals("PUT"))
                    provider.put(request, response, (DavContent) resource);
                else
                    throw new MethodNotAllowedException(
                            request.getMethod() + " not allowed for a non-collection resource");
            }
        }
    }

    /**
     * <p>
     * Creates an instance of @{link Provider}. The specific provider class
     * is chosen based on the type of resource:
     * </p>
     * <ul>
     * <li> home collection: {@link HomeCollectionProvider}</li>
     * <li> calendar collection: {@link CalendarCollectionProvider}</li>
     * <li> collection: {@link CollectionProvider}</li>
     * <li> calendar resource: {@link CalendarResourceProvider}</li>
     * <li> file resource: {@link FileProvider}</li>
     * </ul>
     */
    protected DavProvider createProvider(DavResource resource) {
        if (resource instanceof DavHomeCollection)
            return new HomeCollectionProvider(resourceFactory, entityFactory);
        if (resource instanceof DavOutboxCollection)
            return new OutboxCollectionProvider(resourceFactory, entityFactory);
        if (resource instanceof DavCalendarCollection)
            return new CalendarCollectionProvider(resourceFactory, entityFactory);
        if (resource instanceof DavCollectionBase)
            return new CollectionProvider(resourceFactory, entityFactory);
        if (resource instanceof DavCalendarResource)
            return new CalendarResourceProvider(resourceFactory, entityFactory);
        if (resource instanceof DavUserPrincipalCollection)
            return new UserPrincipalCollectionProvider(resourceFactory, entityFactory);
        if (resource instanceof DavUserPrincipal)
            return new UserPrincipalProvider(resourceFactory, entityFactory);
        return new FileProvider(resourceFactory, entityFactory);
    }

    /**
     * <p>
     * Creates an instance of <code>DavRequest</code> based on the
     * provided <code>HttpServletRequest</code>.
     * </p>
     */
    protected DavRequest createDavRequest(HttpServletRequest request) {
        // Create buffered request if method is PUT so we can retry
        // on concurrency exceptions
        if (request.getMethod().equals("PUT"))
            return new StandardDavRequest(request, locatorFactory, entityFactory, true);
        else
            return new StandardDavRequest(request, locatorFactory, entityFactory);
    }

    /**
     * <p>
     * Creates an instance of <code>DavResponse</code> based on the
     * provided <code>HttpServletResponse</code>.
     * </p>
     */
    protected DavResponse createDavResponse(HttpServletResponse response) {
        return new StandardDavResponse(response);
    }

    /**
     * <p>
     * Creates an instance of <code>DavResource</code> representing the
     * resource targeted by the request.
     * </p>
     */
    protected DavResource resolveTarget(DavRequest request) throws DavException {
        return resourceFactory.resolve(request.getResourceLocator(), request);
    }

    public void init() {
        if (locatorFactory == null)
            throw new RuntimeException("locatorFactory must not be null");
        if (resourceFactory == null)
            throw new RuntimeException("resourceFactory must not be null");
    }

    public DavResourceLocatorFactory getResourceLocatorFactory() {
        return locatorFactory;
    }

    public void setResourceLocatorFactory(DavResourceLocatorFactory factory) {
        locatorFactory = factory;
    }

    public DavResourceFactory getResourceFactory() {
        return resourceFactory;
    }

    public void setResourceFactory(DavResourceFactory factory) {
        resourceFactory = factory;
    }

    public EntityFactory getEntityFactory() {
        return entityFactory;
    }

    public void setEntityFactory(EntityFactory entityFactory) {
        this.entityFactory = entityFactory;
    }

    private void ifMatch(DavRequest request, DavResponse response, DavResource resource)
            throws DavException, IOException {
        EntityTag[] requestEtags = request.getIfMatch();
        if (requestEtags.length == 0)
            return;

        EntityTag resourceEtag = etag(resource);
        if (resourceEtag == null)
            return;

        if (EntityTag.matchesAny(resourceEtag, requestEtags))
            return;

        if (resourceEtag != null)
            response.setHeader("ETag", resourceEtag.toString());

        throw new PreconditionFailedException("If-Match disallows conditional request");
    }

    private void ifNoneMatch(DavRequest request, DavResponse response, DavResource resource)
            throws DavException, IOException {
        EntityTag[] requestEtags = request.getIfNoneMatch();
        if (requestEtags.length == 0)
            return;

        EntityTag resourceEtag = etag(resource);
        if (resourceEtag == null)
            return;

        if (!EntityTag.matchesAny(resourceEtag, requestEtags))
            return;

        if (resourceEtag != null)
            response.addHeader("ETag", resourceEtag.toString());

        if (deservesNotModified(request))
            throw new NotModifiedException();
        else
            throw new PreconditionFailedException("If-None-Match disallows conditional request");
    }

    private void ifModifiedSince(DavRequest request, DavResponse response, DavResource resource)
            throws DavException, IOException {
        if (resource == null)
            return;

        long mod = resource.getModificationTime();
        if (mod == -1)
            return;
        mod = mod / 1000 * 1000;

        long since = request.getDateHeader("If-Modified-Since");
        if (since == -1)
            return;

        if (mod > since)
            return;

        throw new NotModifiedException();
    }

    private void ifUnmodifiedSince(DavRequest request, DavResponse response, DavResource resource)
            throws DavException, IOException {
        if (resource == null)
            return;

        long mod = resource.getModificationTime();
        if (mod == -1)
            return;
        mod = mod / 1000 * 1000;

        long since = request.getDateHeader("If-Unmodified-Since");
        if (since == -1)
            return;

        if (mod <= since)
            return;

        throw new PreconditionFailedException("If-Unmodified-Since disallows conditional request");
    }

    private EntityTag etag(DavResource resource) {
        if (resource == null)
            return null;
        String etag = resource.getETag();
        if (etag == null)
            return null;
        // resource etags have doublequotes wrapped around them
        if (etag.startsWith("\""))
            etag = etag.substring(1, etag.length() - 1);
        return new EntityTag(etag);
    }

    private boolean deservesNotModified(DavRequest request) {
        return (request.getMethod().equals("GET") || request.getMethod().equals("HEAD"));
    }

    private void options(DavRequest request, DavResponse response, DavResource resource) {
        response.setStatus(200);
        response.addHeader("Allow", resource.getSupportedMethods());
        response.addHeader("DAV", resource.getComplianceClass());
    }
}