io.milton.http.annotated.AnnotationResourceFactory.java Source code

Java tutorial

Introduction

Here is the source code for io.milton.http.annotated.AnnotationResourceFactory.java

Source

/*
 * 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 io.milton.http.annotated;

import io.milton.annotations.AccessControlList;
import io.milton.annotations.AddressBooks;
import io.milton.annotations.Authenticate;
import io.milton.annotations.CTag;
import io.milton.annotations.CalendarColor;
import io.milton.annotations.CalendarDateRangeQuery;
import io.milton.annotations.CalendarInvitations;
import io.milton.annotations.CalendarInvitationsCTag;
import io.milton.annotations.CalendarOrder;
import io.milton.annotations.CalendarUserType;
import io.milton.annotations.Calendars;
import io.milton.annotations.ChildOf;
import io.milton.annotations.ChildrenOf;
import io.milton.annotations.ContactData;
import io.milton.annotations.ContentLength;
import io.milton.annotations.ContentType;
import io.milton.annotations.Copy;
import io.milton.annotations.CreatedDate;
import io.milton.annotations.Delete;
import io.milton.annotations.Email;
import io.milton.annotations.FreeBusyQuery;
import io.milton.annotations.Get;
import io.milton.annotations.ICalData;
import io.milton.annotations.MakeCollection;
import io.milton.annotations.MaxAge;
import io.milton.annotations.ModifiedDate;
import io.milton.annotations.Move;
import io.milton.annotations.Name;
import io.milton.annotations.Post;
import io.milton.annotations.PutChild;
import io.milton.annotations.Realm;
import io.milton.annotations.Root;
import io.milton.annotations.SupportedComponentSets;
import io.milton.annotations.UniqueId;
import io.milton.annotations.Users;
import io.milton.common.Path;
import io.milton.http.Auth;
import io.milton.http.AuthenticationService;
import io.milton.http.HttpManager;
import io.milton.http.LockInfo;
import io.milton.http.LockManager;
import io.milton.http.LockTimeout;
import io.milton.http.Request;
import io.milton.http.Request.Method;
import io.milton.http.ResourceFactory;
import io.milton.http.Response;
import io.milton.http.caldav.CalendarSearchService;
import io.milton.http.exceptions.BadRequestException;
import io.milton.http.exceptions.NotAuthorizedException;
import io.milton.http.template.ViewResolver;
import io.milton.http.values.SupportedCalendarComponentListsSet;
import io.milton.http.webdav.DisplayNameFormatter;
import io.milton.resource.CollectionResource;
import io.milton.resource.PropFindableResource;
import io.milton.resource.Resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A resource factory which provides access to files in a file system.
 *
 * Using this with milton is equivalent to using the dav servlet in tomcat
 *
 */
public final class AnnotationResourceFactory implements ResourceFactory {

    private static final Logger log = LoggerFactory.getLogger(AnnotationResourceFactory.class);
    private AuthenticationService authenticationService;
    private CalendarSearchService calendarSearchService;
    private boolean doEarlyAuth = true;
    private io.milton.http.SecurityManager securityManager;
    private LockManager lockManager;
    private String contextPath;
    private Collection<Object> controllers;
    private ViewResolver viewResolver;
    /**
     * Replace with a suitable cluster enabled Map for cluster support
     */
    private Map<String, List<LockHolder>> mapOfTempResources = new ConcurrentHashMap<String, List<LockHolder>>();
    private final Map<Class, AnnotationHandler> mapOfAnnotationHandlers = new HashMap<Class, AnnotationHandler>(); // keyed on annotation class
    private final Map<Method, AnnotationHandler> mapOfAnnotationHandlersByMethod = new EnumMap<Method, AnnotationHandler>(
            Method.class); // keyed on http method
    RootAnnotationHandler rootAnnotationHandler = new RootAnnotationHandler(this);
    GetAnnotationHandler getAnnotationHandler = new GetAnnotationHandler(this);
    PostAnnotationHandler postAnnotationHandler = new PostAnnotationHandler(this);
    ChildrenOfAnnotationHandler childrenOfAnnotationHandler = new ChildrenOfAnnotationHandler(this);
    ChildOfAnnotationHandler childOfAnnotationHandler = new ChildOfAnnotationHandler(this);
    DisplayNameAnnotationHandler displayNameAnnotationHandler = new DisplayNameAnnotationHandler(this);
    DisplayNameSetterAnnotationHandler displayNameSetterAnnotationHandler = new DisplayNameSetterAnnotationHandler(
            this);
    MakeCollectionAnnotationHandler makCollectionAnnotationHandler = new MakeCollectionAnnotationHandler(this);
    MakeCalendarAnnotationHandler makeCalendarAnnotationHandler = new MakeCalendarAnnotationHandler(this);
    MoveAnnotationHandler moveAnnotationHandler = new MoveAnnotationHandler(this);
    DeleteAnnotationHandler deleteAnnotationHandler = new DeleteAnnotationHandler(this);
    CopyAnnotationHandler copyAnnotationHandler = new CopyAnnotationHandler(this);
    PutChildAnnotationHandler putChildAnnotationHandler = new PutChildAnnotationHandler(this);
    UsersAnnotationHandler usersAnnotationHandler = new UsersAnnotationHandler(this);
    AuthenticateAnnotationHandler authenticateAnnotationHandler = new AuthenticateAnnotationHandler(this);
    AccessControlListAnnotationHandler accessControlListAnnotationHandler = new AccessControlListAnnotationHandler(
            this);
    CTagAnnotationHandler cTagAnnotationHandler = new CTagAnnotationHandler(this);
    ICalDataAnnotationHandler iCalDataAnnotationHandler = new ICalDataAnnotationHandler(this);
    CalendarsAnnotationHandler calendarsAnnotationHandler = new CalendarsAnnotationHandler(this);
    AddressBooksAnnotationHandler addressBooksAnnotationHandler = new AddressBooksAnnotationHandler(this);
    ContactDataAnnotationHandler contactDataAnnotationHandler = new ContactDataAnnotationHandler(this);
    CommonPropertyAnnotationHandler<String> nameAnnotationHandler = new CommonPropertyAnnotationHandler(Name.class,
            this, "name", "fileName");
    CommonPropertyAnnotationHandler<String> emailAnnotationHandler = new CommonPropertyAnnotationHandler(
            Email.class, this, "email");
    CommonPropertyAnnotationHandler<SupportedCalendarComponentListsSet> supportedComponentSets = new CommonPropertyAnnotationHandler(
            SupportedComponentSets.class, this, "supportedComponentSets");
    CommonPropertyAnnotationHandler<String> realmAnnotationHandler = new CommonPropertyAnnotationHandler(
            Realm.class, this, "realm");
    CommonPropertyAnnotationHandler<Date> modifiedDateAnnotationHandler = new CommonPropertyAnnotationHandler<Date>(
            ModifiedDate.class, this, "modifiedDate");
    CommonPropertyAnnotationHandler<Date> createdDateAnnotationHandler = new CommonPropertyAnnotationHandler<Date>(
            CreatedDate.class, this);
    ContentTypeAnnotationHandler contentTypeAnnotationHandler = new ContentTypeAnnotationHandler(this,
            "contentType");
    CommonPropertyAnnotationHandler<Long> contentLengthAnnotationHandler = new CommonPropertyAnnotationHandler<Long>(
            ContentLength.class, this, "contentLength");
    CommonPropertyAnnotationHandler<Long> maxAgeAnnotationHandler = new CommonPropertyAnnotationHandler<Long>(
            MaxAge.class, this, "maxAge");
    CommonPropertyAnnotationHandler<String> uniqueIdAnnotationHandler = new CommonPropertyAnnotationHandler<String>(
            UniqueId.class, this, "id");
    CommonPropertyAnnotationHandler<String> calendarColorAnnotationHandler = new CommonPropertyAnnotationHandler<String>(
            CalendarColor.class, this, "color");
    CommonPropertyAnnotationHandler<String> calendarOrderAnnotationHandler = new CommonPropertyAnnotationHandler<String>(
            CalendarOrder.class, this, "order");
    CommonPropertyAnnotationHandler<String> calendarUserTypeAnnotationHandler = new CommonPropertyAnnotationHandler<String>(
            CalendarUserType.class, this, "calendarUserType", "cuType");
    CalendarDateRangeQueryAnnotationHandler calendarDateRangeQueryAnnotationHandler = new CalendarDateRangeQueryAnnotationHandler(
            this);
    FreeBusyQueryAnnotationHandler freeBusyQueryAnnotationHandler = new FreeBusyQueryAnnotationHandler(this);
    CalendarInvitationsAnnotationHandler calendarInvitationsAnnotationHandler = new CalendarInvitationsAnnotationHandler(
            this);
    CalendarInvitationsCTagAnnotationHandler calendarInvitationsCTagAnnotationHandler = new CalendarInvitationsCTagAnnotationHandler(
            this);

    public AnnotationResourceFactory() {

        supportedComponentSets.setDefaultValue(SupportedCalendarComponentListsSet.EVENTS_ONLY);

        mapOfAnnotationHandlers.put(Root.class, rootAnnotationHandler);
        mapOfAnnotationHandlers.put(Get.class, getAnnotationHandler);
        mapOfAnnotationHandlers.put(Post.class, postAnnotationHandler);
        mapOfAnnotationHandlers.put(ChildrenOf.class, childrenOfAnnotationHandler);
        mapOfAnnotationHandlers.put(ChildOf.class, childOfAnnotationHandler);
        mapOfAnnotationHandlers.put(Name.class, nameAnnotationHandler);
        mapOfAnnotationHandlers.put(DisplayNameAnnotationHandler.class, displayNameAnnotationHandler);
        mapOfAnnotationHandlers.put(DisplayNameSetterAnnotationHandler.class, displayNameSetterAnnotationHandler);
        mapOfAnnotationHandlers.put(MakeCollection.class, makCollectionAnnotationHandler);
        mapOfAnnotationHandlers.put(Move.class, moveAnnotationHandler);
        mapOfAnnotationHandlers.put(Delete.class, deleteAnnotationHandler);
        mapOfAnnotationHandlers.put(Copy.class, copyAnnotationHandler);
        mapOfAnnotationHandlers.put(PutChild.class, putChildAnnotationHandler);

        mapOfAnnotationHandlers.put(Users.class, usersAnnotationHandler);
        mapOfAnnotationHandlers.put(Authenticate.class, authenticateAnnotationHandler);
        mapOfAnnotationHandlers.put(AccessControlList.class, accessControlListAnnotationHandler);
        mapOfAnnotationHandlers.put(AddressBooks.class, addressBooksAnnotationHandler);
        mapOfAnnotationHandlers.put(Calendars.class, calendarsAnnotationHandler);
        mapOfAnnotationHandlers.put(MakeCalendarAnnotationHandler.class, makeCalendarAnnotationHandler);

        mapOfAnnotationHandlers.put(ModifiedDate.class, modifiedDateAnnotationHandler);
        mapOfAnnotationHandlers.put(CreatedDate.class, createdDateAnnotationHandler);
        mapOfAnnotationHandlers.put(ContentType.class, contentTypeAnnotationHandler);
        mapOfAnnotationHandlers.put(MaxAge.class, maxAgeAnnotationHandler);
        mapOfAnnotationHandlers.put(ContentLength.class, contentLengthAnnotationHandler);
        mapOfAnnotationHandlers.put(UniqueId.class, uniqueIdAnnotationHandler);
        mapOfAnnotationHandlers.put(CTag.class, cTagAnnotationHandler);
        mapOfAnnotationHandlers.put(ICalData.class, iCalDataAnnotationHandler);
        mapOfAnnotationHandlers.put(CalendarColor.class, calendarColorAnnotationHandler);
        mapOfAnnotationHandlers.put(CalendarOrder.class, calendarOrderAnnotationHandler);
        mapOfAnnotationHandlers.put(CalendarUserType.class, calendarUserTypeAnnotationHandler);
        mapOfAnnotationHandlers.put(ContactData.class, contactDataAnnotationHandler);

        mapOfAnnotationHandlers.put(CalendarDateRangeQuery.class, calendarDateRangeQueryAnnotationHandler);
        mapOfAnnotationHandlers.put(FreeBusyQuery.class, freeBusyQueryAnnotationHandler);
        mapOfAnnotationHandlers.put(CalendarInvitations.class, calendarInvitationsAnnotationHandler);
        mapOfAnnotationHandlers.put(CalendarInvitationsCTag.class, calendarInvitationsCTagAnnotationHandler);
        mapOfAnnotationHandlers.put(Email.class, emailAnnotationHandler);
        mapOfAnnotationHandlers.put(SupportedComponentSets.class, supportedComponentSets);

        for (AnnotationHandler ah : mapOfAnnotationHandlers.values()) {
            Method[] methods = ah.getSupportedMethods();
            if (methods != null) {
                for (Method m : methods) {
                    mapOfAnnotationHandlersByMethod.put(m, ah);
                }
            }
        }
    }

    @Override
    public Resource getResource(String host, String url) throws NotAuthorizedException, BadRequestException {
        if (log.isTraceEnabled()) {
            log.trace("getResource: host: " + host + " - url:" + url);
        }

        AnnoCollectionResource hostRoot = locateHostRoot(host, HttpManager.request());
        if (hostRoot == null) {
            if (rootAnnotationHandler.getControllerMethods().isEmpty()) {
                log.warn(
                        "No @Root methods were found, so i cant find a root resource. Note that controller methods are displayed on startup");
            } else {
                log.warn("Could not find a root resource for host: " + host + " Using "
                        + rootAnnotationHandler.getControllerMethods().size() + " root methods");
            }
            return null;
        }

        Resource r;
        url = stripContext(url);
        if (url.equals("/") || url.isEmpty()) {
            r = hostRoot;
        } else {
            Path path = Path.path(url);
            r = findFromRoot(hostRoot, path);
            if (log.isTraceEnabled()) {
                if (r == null) {
                    log.trace("Resource not found: host=" + host + " path=" + path);
                } else {
                    if (r instanceof AnnoResource) {
                        AnnoResource ar = (AnnoResource) r;
                        log.trace("Found AnnoResource: " + r.getClass() + "  for path=" + path + "  with source: "
                                + ar.getSource());
                    } else {
                        log.trace("Found resource: " + r.getClass() + "  for path=" + path);
                    }
                }
            }
        }
        return r;
    }

    public Resource findFromRoot(AnnoCollectionResource rootFolder, Path p)
            throws NotAuthorizedException, BadRequestException {
        CollectionResource col = rootFolder;
        Resource r = null;
        for (String s : p.getParts()) {
            if (col == null) {
                if (log.isTraceEnabled()) {
                    log.trace("findFromRoot: collection is null, can't look for child: " + s);
                }
                return null;
            }
            r = col.child(s);
            if (r == null) {
                if (log.isTraceEnabled()) {
                    log.trace("findFromRoot: Couldnt find child: " + s + " of parent: " + col.getName()
                            + " with type: " + col.getClass());
                }
                return null;
            } else {
                if (log.isTraceEnabled()) {
                    if (r instanceof AnnoResource) {
                        AnnoResource ar = (AnnoResource) r;
                        log.trace("findFromRoot: found a child: " + r.getName() + " with source type: "
                                + ar.getSource().getClass());
                    } else {
                        log.trace("findFromRoot: found a child: " + r.getName() + " of type: " + r.getClass());
                    }
                }
            }
            if (r instanceof CollectionResource) {
                col = (CollectionResource) r;
            } else {
                col = null;
            }
        }
        return r;
    }

    public String getRealm(String host) {
        return securityManager.getRealm(host);
    }

    public void setAuthenticationService(AuthenticationService authenticationService) {
        this.authenticationService = authenticationService;
    }

    public AuthenticationService getAuthenticationService() {
        return authenticationService;
    }

    public CalendarSearchService getCalendarSearchService() {
        return calendarSearchService;
    }

    public void setCalendarSearchService(CalendarSearchService calendarSearchService) {
        this.calendarSearchService = calendarSearchService;
    }

    /**
     * If true authentication will be attempted as soon as the root resource is
     * located
     *
     * @return
     */
    public boolean isDoEarlyAuth() {
        return doEarlyAuth;
    }

    public void setDoEarlyAuth(boolean doEarlyAuth) {
        this.doEarlyAuth = doEarlyAuth;
    }

    public void setSecurityManager(io.milton.http.SecurityManager securityManager) {
        if (securityManager != null) {
            log.debug("securityManager: " + securityManager.getClass());
        } else {
            log.warn("Setting null FsSecurityManager. This WILL cause null pointer exceptions");
        }
        this.securityManager = securityManager;
    }

    public io.milton.http.SecurityManager getSecurityManager() {
        return securityManager;
    }

    public void setMaxAgeSeconds(Long maxAgeSeconds) {
        maxAgeAnnotationHandler.setDefaultValue(maxAgeSeconds);
    }

    public Long getMaxAgeSeconds() {
        return maxAgeAnnotationHandler.getDefaultValue();
    }

    public LockManager getLockManager() {
        return lockManager;
    }

    public void setLockManager(LockManager lockManager) {
        this.lockManager = lockManager;
    }

    public void setContextPath(String contextPath) {
        this.contextPath = contextPath;
    }

    public String getContextPath() {
        return contextPath;
    }

    /**
     * Get a context path which is definitely valid as a path. Ie it always
     * begins with a slash, and ends with a slash, and is a single slash if
     * representing the root
     *
     * @return
     */
    public String getValidContextPath() {
        String s = getContextPath();
        if (s == null || s.isEmpty()) {
            return "/";
        }
        if (!s.endsWith("/")) {
            s += "/";
        }
        return s;
    }

    public String stripContext(String url) {
        if (this.contextPath != null && contextPath.length() > 0 && !contextPath.equals("/")) {
            String c;
            if (!contextPath.startsWith("/")) {
                c = "/" + contextPath;
            } else {
                c = contextPath;
            }
            url = url.replaceFirst(c, "");
            //log.debug("stripped context: " + url);
            return url;
        } else {
            return url;
        }
    }

    public Collection<Object> getControllers() {
        return controllers;
    }

    public void setControllers(Collection<Object> controllers) {
        this.controllers = Collections.unmodifiableCollection(controllers);
        log.info("setControllers: " + controllers.size() + " parsing controllers...");
        for (Object controller : controllers) {
            log.info("Parse controller: " + controller.getClass());
            for (AnnotationHandler ah : mapOfAnnotationHandlers.values()) {
                log.info(" - controller: " + controller.getClass() + " handler: " + ah.getAnnoClass());
                ah.parseController(controller);
            }
        }
        log.info("Controller parsing complete. Listing found methods..");
        for (AnnotationHandler ah : mapOfAnnotationHandlers.values()) {
            log.info("Annotation: " + ah.getAnnoClass());
            List<ControllerMethod> list = ah.getControllerMethods();
            if (list == null || list.isEmpty()) {
                log.info("  No methods found");
            } else {
                for (ControllerMethod cm : ah.getControllerMethods()) {
                    log.info("  method: " + cm.method);
                }
            }
        }
    }

    public ViewResolver getViewResolver() {
        return viewResolver;
    }

    public void setViewResolver(ViewResolver viewResolver) {
        this.viewResolver = viewResolver;
    }

    private AnnoCollectionResource locateHostRoot(String host, Request request) {
        AnnoCollectionResource rootRes;
        if (request != null) {
            // attempt to find one cached in the request
            rootRes = (AnnoCollectionResource) request.getAttributes().get("RootRes_" + host);
            if (rootRes != null) {
                return rootRes;
            }
        }
        Object root = rootAnnotationHandler.execute(host);
        if (root == null) {
            return null;
        }
        rootRes = new AnnoCollectionResource(this, root, null);
        if (request != null) {
            request.getAttributes().put("RootRes_" + host, rootRes);
        }
        return rootRes;
    }

    public boolean isCompatible(Object source, Method m) {
        if (in(m, Method.REPORT, Method.LOCK, Method.UNLOCK, Method.HEAD, Method.OPTIONS, Method.PROPPATCH,
                Method.ACL)) {
            return true;
        }
        AnnotationHandler ah = mapOfAnnotationHandlersByMethod.get(m);
        if (ah != null) {
            boolean b = ah.isCompatible(source);
            if (log.isTraceEnabled()) {
                log.trace("isCompatible: " + source + " - " + m + " = " + b);
            }
            return b;
        }
        log.warn("No annotation handler is configured for http method: " + m);
        return false;
    }

    /**
     *
     * @param sourceRes
     * @param m
     * @param otherValues - any other values to be provided which can be mapped
     * onto method arguments
     * @return
     * @throws Exception
     */
    public Object[] buildInvokeArgs(AnnoResource sourceRes, java.lang.reflect.Method m, Object... otherValues)
            throws Exception {
        return buildInvokeArgsExt(sourceRes, null, false, m, otherValues);
    }

    /**
     *
     * @param sourceRes
     * @param mandatorySecondArg - if present will be used as second arg. Used
     * by AccessControlListAnnotationHandler to always provide user to second
     * arg, even when null
     * @param forceUseSecondArg
     * @param m
     * @param otherValues
     * @return
     * @throws Exception
     */
    public Object[] buildInvokeArgsExt(AnnoResource sourceRes, Object mandatorySecondArg, boolean forceUseSecondArg,
            java.lang.reflect.Method m, Object... otherValues) throws Exception {
        if (log.isTraceEnabled()) {
            log.trace("buildInvokeArgsExt: source=" + sourceRes.getSource() + " on method: " + m);
        }
        Request request = HttpManager.request();
        Response response = HttpManager.response();
        Auth auth = request.getAuthorization();
        AnnoPrincipalResource principal = null;
        if (auth != null) {
            if (auth.getTag() instanceof AnnoPrincipalResource) {
                principal = (AnnoPrincipalResource) auth.getTag();
            }
        }

        Object[] args = new Object[m.getParameterTypes().length];
        List list = new ArrayList();

        list.add(sourceRes.getSource()); // First argument MUST be the source object!!!

        // put otherValues on. Note these are more specific then parents so must be added first
        for (Object s : otherValues) {
            list.add(s);
            if (s instanceof AnnoResource) {
                AnnoResource otherRes = (AnnoResource) s;
                list.add(otherRes.getSource());
            }
        }

        // put this resource's parents on the stack
        AnnoResource r = sourceRes.getParent();
        while (r != null) {
            list.add(r.getSource());
            list.add(r);
            r = r.getParent();
        }

        for (int i = 0; i < m.getParameterTypes().length; i++) {
            if (i == 1 && forceUseSecondArg) {
                args[i] = mandatorySecondArg; // hack for methods which can have a null 2nd arg. Without this any other matching object would be provided
            } else {
                if (isPrincipalArg(m, i)) {
                    principal = checkAuthentication(sourceRes, principal);
                    if (principal != null) {
                        args[i] = principal.source;
                    } else {
                        log.warn("Null principal provided for method: " + m);
                        args[i] = null;
                    }
                } else {
                    Class type = m.getParameterTypes()[i];
                    Object argValue;
                    try {
                        argValue = findArgValue(type, request, response, list);
                    } catch (UnresolvableParameterException e) {
                        log.warn("Could not resolve parameter: " + i + "  in method: " + m.getName());
                        argValue = null;
                    }
                    args[i] = argValue;
                }
            }
        }
        return args;
    }

    public java.lang.reflect.Method findMethodForAnno(Class sourceClass, Class annoClass) {
        for (java.lang.reflect.Method m : sourceClass.getMethods()) {
            Annotation a = m.getAnnotation(annoClass);
            if (a != null) {
                return m;
            }
        }
        return null;
    }

    private Object findArgValue(Class type, Request request, Response response, List otherAvailValues)
            throws Exception {
        if (type == Request.class) {
            return request;
        } else if (type == Response.class) {
            return response;
        } else if (type == byte[].class) {
            InputStream in = (InputStream) findArgValue(InputStream.class, request, response, otherAvailValues);
            return toBytes(in);
        } else {
            for (Object o : otherAvailValues) {
                if (o != null && type.isAssignableFrom(o.getClass())) {
                    otherAvailValues.remove(o); // remove it so that we dont use same value for next param of same type
                    return o;
                }
            }
        }
        if (log.isInfoEnabled()) {
            if (log.isDebugEnabled()) {
                log.info("Unknown parameter type: " + type);
                log.debug("Available types are:");
                log.debug(" - " + Request.class);
                log.debug(" - " + Response.class);
                for (Object o : otherAvailValues) {
                    if (o != null) {
                        log.debug(" - " + o.getClass());
                    } else {
                        log.debug(" - null");
                    }
                }
            } else {
                log.info(
                        "Unknown parameter type: " + type + " Enable DEBUG level logging to see available objects");
            }
        }

        throw new UnresolvableParameterException("Couldnt find parameter of type: " + type);
    }

    private byte[] toBytes(InputStream inputStream) throws IOException {
        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        IOUtils.copy(inputStream, bout);
        return bout.toByteArray();
    }

    /**
     * Create a Resource to wrap a source pojo object.
     *
     * @param childSource
     * @param parent
     * @param m
     * @return
     */
    public AnnoResource instantiate(Object childSource, AnnoCollectionResource parent, java.lang.reflect.Method m) {
        if (authenticateAnnotationHandler.canAuthenticate(childSource)) {
            return new AnnoPrincipalResource(this, childSource, parent);
        }
        if (parent instanceof AnnoPrincipalResource) {
            // Check for a Calendars method which takes this source as first arg
            if (calendarsAnnotationHandler.isCompatible(childSource)) {
                AnnoPrincipalResource p = (AnnoPrincipalResource) parent;
                return new AnnoCalendarHomeResource(this, childSource, p, calendarSearchService);
            }
        }
        if (m.getAnnotation(Calendars.class) != null) {
            return new AnnoCalendarResource(this, childSource, parent);
        }
        if (parent instanceof AnnoCalendarResource) {
            if (childrenOfAnnotationHandler.isCompatible(childSource)
                    || childOfAnnotationHandler.isCompatible(childSource)) {
                // This is an edge case, shouldnt really have collections inside calendars
                return new AnnoCollectionResource(this, childSource, parent);
            } else {
                return new AnnoEventResource(this, childSource, parent);
            }
        }
        if (m.getAnnotation(AddressBooks.class) != null) {
            return new AnnoAddressBookResource(this, childSource, parent);
        }
        if (parent instanceof AnnoAddressBookResource) {
            return new AnnoContactResource(this, childSource, parent);
        }

        if (childrenOfAnnotationHandler.isCompatible(childSource)
                || childOfAnnotationHandler.isCompatible(childSource)) {
            return new AnnoCollectionResource(this, childSource, parent);
        } else {
            return new AnnoFileResource(this, childSource, parent);
        }
    }

    public CommonResource instantiate(LockHolder r, AnnoCollectionResource parent) {
        return new LockNullResource(this, parent, r);
    }

    /**
     * Create an in-memory resource with the given timeout. The resource will
     * not be persisted, but may be distributed among the cluster if configured
     * as such.
     *
     * The resource may be flushed from the in-memory list after the given
     * timeout if not-null
     *
     * The type of the object returned is not intended to have any significance
     * and does not contain any meaningful content
     *
     * @param parentCollection
     * @param name
     * @param timeout - optional. The resource will be removed after this
     * timeout expires
     * @param lockInfo
     * @return - a temporary (not persistent) resource of an indeterminate type
     */
    public LockHolder createLockHolder(AnnoCollectionResource parentCollection, String name, LockTimeout timeout,
            LockInfo lockInfo) {
        String parentKey = parentCollection.getUniqueId();
        if (parentKey == null) {
            throw new RuntimeException(
                    "Cant create temp resource because parent's uniqueID is null. Please add the @UniqueID for class: "
                            + parentCollection.getSource().getClass());
        }
        LockHolder r = new LockHolder(UUID.randomUUID());
        r.setParentCollectionId(parentKey);
        r.setName(name);
        r.setLockTimeout(timeout);
        r.setLockInfo(lockInfo);
        synchronized (this) {
            List<LockHolder> list = mapOfTempResources.get(parentKey);
            if (list == null) {
                list = new CopyOnWriteArrayList<LockHolder>();
                mapOfTempResources.put(parentKey, list);
            }
            list.add(r);
        }
        return r;
    }

    /**
     * Null-safe method to get the list of lock holders for the given parent.
     * These are resources created by a LOCK-null request, where resources are
     * locked prior to being created. The lock-null resource is replaced
     * following a PUT to that resource
     *
     * @param parent
     * @return
     */
    public List<LockHolder> getTempResourcesForParent(AnnoCollectionResource parent) {
        String parentKey = parent.getUniqueId();
        if (parentKey == null) {
            return Collections.EMPTY_LIST;
        }
        return getTempResourcesForParent(parentKey);
    }

    public List<LockHolder> getTempResourcesForParent(String parentKey) {
        List<LockHolder> list = mapOfTempResources.get(parentKey);
        if (list == null) {
            return Collections.EMPTY_LIST;
        } else {
            return list;
        }
    }

    /**
     * Removes the LockHolder from memory and also from the parent which
     * contains it (if loaded)
     *
     * @param parent
     * @param name
     */
    public void removeLockHolder(AnnoCollectionResource parent, String name) {
        List<LockHolder> list = getTempResourcesForParent(parent);
        Iterator<LockHolder> it = list.iterator();
        List<LockHolder> toRemove = new ArrayList<LockHolder>();
        while (it.hasNext()) {
            LockHolder o = it.next();
            if (o.getName().equals(name)) {
                toRemove.add(o);
                //it.remove();
            }
        }
        list.removeAll(toRemove);
        parent.removeLockHolder(name);
    }

    public Map<String, List<LockHolder>> getMapOfTempResources() {
        return mapOfTempResources;
    }

    public void setMapOfTempResources(Map<String, List<LockHolder>> mapOfTempResources) {
        this.mapOfTempResources = mapOfTempResources;
    }

    private boolean in(Method m, Method... methods) {
        for (Method listMethod : methods) {
            if (m.equals(listMethod)) {
                return true;
            }
        }
        return false;
    }

    private boolean isPrincipalArg(java.lang.reflect.Method m, int i) {
        Annotation[] arr = m.getParameterAnnotations()[i];
        Class annoType = io.milton.annotations.Principal.class;
        for (Annotation a : arr) {
            if (a.annotationType().equals(annoType)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Process the source object (which may be a Collection of source objects),
     * and for each one instantiate an AnnoResource and append it to the result
     * set
     *
     *
     * @param result - to append to
     * @param sources - single source object, or multiple source objects in a
     * Collection
     * @param parent - the parent collection of these resource(s)
     * @param cm - the controller method they were found by
     */
    public void createAndAppend(Collection<AnnoResource> result, Object sources, AnnoCollectionResource parent,
            ControllerMethod cm) {
        if (sources == null) {
            // ignore
        } else if (sources instanceof Collection) {
            Collection l = (Collection) sources;
            for (Object item : l) {
                result.add(instantiate(item, parent, cm.method));
            }
        } else if (sources.getClass().isArray()) {
            Object[] arr = (Object[]) sources;
            for (Object item : arr) {
                result.add(instantiate(item, parent, cm.method));
            }
        } else {
            result.add(instantiate(sources, parent, cm.method));
        }
    }

    private AnnoPrincipalResource checkAuthentication(AnnoResource res, AnnoPrincipalResource principal)
            throws NotAuthorizedException {
        if (principal != null) {
            return principal;
        }
        if (doEarlyAuth) {
            if (authenticationService != null) {
                Request request = HttpManager.request();

                // Note that authentication will usually result in a call to getResource to find the principal..
                AuthenticationService.AuthStatus authStatus = authenticationService.authenticate(res, request);
                if (authStatus == null) {
                    log.trace("Authentication not attempted");
                    throw new NotAuthorizedException(res);
                } else {
                    if (authStatus.loginFailed) {
                        log.warn("Early authentication failed");
                        throw new NotAuthorizedException(res);
                    } else {
                        log.trace("Early authentication succeeded");
                        Auth auth = authStatus.auth;
                        if (auth != null) {
                            if (auth.getTag() instanceof AnnoPrincipalResource) {
                                principal = (AnnoPrincipalResource) auth.getTag();
                            }
                        }
                        return principal;
                    }
                }
            }
        }
        return null;
    }

    public class AnnotationsDisplayNameFormatter implements DisplayNameFormatter {

        private final DisplayNameFormatter wrapped;

        public AnnotationsDisplayNameFormatter(DisplayNameFormatter wrapper) {
            this.wrapped = wrapper;
        }

        @Override
        public String formatDisplayName(PropFindableResource res) {
            if (res instanceof AnnoResource) {
                AnnoResource r = (AnnoResource) res;
                return r.getDisplayName();
            }
            return wrapped.formatDisplayName(res);
        }
    }

    public CalendarDateRangeQueryAnnotationHandler getCalendarDateRangeQueryAnnotationHandler() {
        return calendarDateRangeQueryAnnotationHandler;
    }

    public CalendarInvitationsAnnotationHandler getCalendarInvitationsAnnotationHandler() {
        return calendarInvitationsAnnotationHandler;
    }

    public CalendarInvitationsCTagAnnotationHandler getCalendarInvitationsCTagAnnotationHandler() {
        return calendarInvitationsCTagAnnotationHandler;
    }

    public FreeBusyQueryAnnotationHandler getFreeBusyQueryAnnotationHandler() {
        return freeBusyQueryAnnotationHandler;
    }

    public UsersAnnotationHandler getUsersAnnotationHandler() {
        return usersAnnotationHandler;
    }

}