All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.milton.http.annotated.AnnotationResourceFactory Maven / Gradle / Ivy

/*
 * 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.DirectoryGateway;
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.PrincipalSearch;
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 controllers;
	private ViewResolver viewResolver;
	/**
	 * Replace with a suitable cluster enabled Map for cluster support
	 */
	private Map> mapOfTempResources = new ConcurrentHashMap<>();
	private final Map mapOfAnnotationHandlers = new HashMap<>(); // keyed on annotation class
	private final Map mapOfAnnotationHandlersByMethod = new EnumMap<>(Method.class); // keyed on http method
	final RootAnnotationHandler rootAnnotationHandler = new RootAnnotationHandler(this);
	final GetAnnotationHandler getAnnotationHandler = new GetAnnotationHandler(this);
	final PostAnnotationHandler postAnnotationHandler = new PostAnnotationHandler(this);
	final ChildrenOfAnnotationHandler childrenOfAnnotationHandler = new ChildrenOfAnnotationHandler(this);
	final ChildOfAnnotationHandler childOfAnnotationHandler = new ChildOfAnnotationHandler(this);
	final DisplayNameAnnotationHandler displayNameAnnotationHandler = new DisplayNameAnnotationHandler(this);
	final DisplayNameSetterAnnotationHandler displayNameSetterAnnotationHandler = new DisplayNameSetterAnnotationHandler(this);
	final MakeCollectionAnnotationHandler makCollectionAnnotationHandler = new MakeCollectionAnnotationHandler(this);
	final MakeCalendarAnnotationHandler makeCalendarAnnotationHandler = new MakeCalendarAnnotationHandler(this);
	final MoveAnnotationHandler moveAnnotationHandler = new MoveAnnotationHandler(this);
	final DeleteAnnotationHandler deleteAnnotationHandler = new DeleteAnnotationHandler(this);
	final CopyAnnotationHandler copyAnnotationHandler = new CopyAnnotationHandler(this);
	final PutChildAnnotationHandler putChildAnnotationHandler = new PutChildAnnotationHandler(this);
	final UsersAnnotationHandler usersAnnotationHandler = new UsersAnnotationHandler(this);
	final AuthenticateAnnotationHandler authenticateAnnotationHandler = new AuthenticateAnnotationHandler(this);
	final AccessControlListAnnotationHandler accessControlListAnnotationHandler = new AccessControlListAnnotationHandler(this);
	final CTagAnnotationHandler cTagAnnotationHandler = new CTagAnnotationHandler(this);
	final ICalDataAnnotationHandler iCalDataAnnotationHandler = new ICalDataAnnotationHandler(this);
	final CalendarsAnnotationHandler calendarsAnnotationHandler = new CalendarsAnnotationHandler(this);
	final AddressBooksAnnotationHandler addressBooksAnnotationHandler = new AddressBooksAnnotationHandler(this);
	final ContactDataAnnotationHandler contactDataAnnotationHandler = new ContactDataAnnotationHandler(this);
	final PrincipalSearchAnnotationHandler principalSearchAnnotationHandler = new PrincipalSearchAnnotationHandler(this);
	final CommonPropertyAnnotationHandler nameAnnotationHandler = new CommonPropertyAnnotationHandler(Name.class, this, "name", "fileName");
	final CommonPropertyAnnotationHandler emailAnnotationHandler = new CommonPropertyAnnotationHandler(Email.class, this, "email");
	final CommonPropertyAnnotationHandler supportedComponentSets = new CommonPropertyAnnotationHandler(SupportedComponentSets.class, this, "supportedComponentSets");
	final CommonPropertyAnnotationHandler realmAnnotationHandler = new CommonPropertyAnnotationHandler(Realm.class, this, "realm");
	final CommonPropertyAnnotationHandler modifiedDateAnnotationHandler = new CommonPropertyAnnotationHandler<>(ModifiedDate.class, this, "modifiedDate");
	final CommonPropertyAnnotationHandler createdDateAnnotationHandler = new CommonPropertyAnnotationHandler<>(CreatedDate.class, this);
	final ContentTypeAnnotationHandler contentTypeAnnotationHandler = new ContentTypeAnnotationHandler(this, "contentType");
	final CommonPropertyAnnotationHandler contentLengthAnnotationHandler = new CommonPropertyAnnotationHandler<>(ContentLength.class, this, "contentLength");
	final CommonPropertyAnnotationHandler maxAgeAnnotationHandler = new CommonPropertyAnnotationHandler<>(MaxAge.class, this, "maxAge");
	final CommonPropertyAnnotationHandler uniqueIdAnnotationHandler = new CommonPropertyAnnotationHandler<>(UniqueId.class, this, "id");
	final CommonPropertyAnnotationHandler calendarColorAnnotationHandler = new CommonPropertyAnnotationHandler<>(CalendarColor.class, this, "color");
	final CommonPropertyAnnotationHandler calendarOrderAnnotationHandler = new CommonPropertyAnnotationHandler<>(CalendarOrder.class, this, "order");
	final CommonPropertyAnnotationHandler calendarUserTypeAnnotationHandler = new CommonPropertyAnnotationHandler<>(CalendarUserType.class, this, "calendarUserType", "cuType");
	final CalendarDateRangeQueryAnnotationHandler calendarDateRangeQueryAnnotationHandler = new CalendarDateRangeQueryAnnotationHandler(this);
	final FreeBusyQueryAnnotationHandler freeBusyQueryAnnotationHandler = new FreeBusyQueryAnnotationHandler(this);
	final CalendarInvitationsAnnotationHandler calendarInvitationsAnnotationHandler = new CalendarInvitationsAnnotationHandler(this);
	final CalendarInvitationsCTagAnnotationHandler calendarInvitationsCTagAnnotationHandler = new CalendarInvitationsCTagAnnotationHandler(this);
	final CommonPropertyAnnotationHandler directoryGatewayAnnotationHandler = new CommonPropertyAnnotationHandler(DirectoryGateway.class, this, "directoryGateway");

	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(PrincipalSearch.class, principalSearchAnnotationHandler);		

		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;
	}

	public Collection getControllers() {
		return controllers;
	}

	public void setControllers(Collection 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 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 list = mapOfTempResources.get(parentKey);
			if (list == null) {
				list = new CopyOnWriteArrayList<>();
				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 getTempResourcesForParent(AnnoCollectionResource parent) {
		String parentKey = parent.getUniqueId();
		if (parentKey == null) {
			return Collections.emptyList();
		}
		return getTempResourcesForParent(parentKey);
	}

	public List getTempResourcesForParent(String parentKey) {
		List list = mapOfTempResources.get(parentKey);
		if (list == null) {
			return Collections.emptyList();
		} 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 list = getTempResourcesForParent(parent);
		Iterator it = list.iterator();
		List toRemove = new ArrayList<>();
		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> getMapOfTempResources() {
		return mapOfTempResources;
	}

	public void setMapOfTempResources(Map> 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 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 static 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;
	}
	
	
}