/* * Copyright (C) 2014 Civilian Framework. * * Licensed under the Civilian License (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.civilian-framework.org/license.txt * * 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.civilian.controller; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import org.civilian.Controller; import org.civilian.Request; import org.civilian.Response; import org.civilian.content.ContentNegotiation; import org.civilian.content.ContentType; import org.civilian.content.ContentTypeList; import org.civilian.util.ArrayUtil; import org.civilian.util.Check; import org.civilian.util.Iterators; /** * ControllerType provides meta-information about a controller class. * It provides access to all the ControllerMethods of the controller. * Whereas a Controller object is instantiated to process a single request, and then discarded, * ControllerTypes can be reused and cached. */ public class ControllerType implements Iterable<ControllerMethod> { /** * Creates a new ControllerType. * @param controllerClass the controller class * @param methods the ControllerMethods. The order of the method is obeyed during * content-negotiation. */ public ControllerType(Class<? extends Controller> controllerClass, String methodPath, ControllerFactory factory, ControllerMethod... methods) { controllerClass_ = Check.notNull(controllerClass, "controllerClass"); methods_ = Check.notNull(methods, "methods"); // we accept empty arrays, even if the controller is then useless methodPath_ = methodPath; factory_ = factory; // build the map request-method -> controller-method[] for (ControllerMethod ctrlMethod : methods) { for (Iterator<String> reqMethods = ctrlMethod.getRequestMethods(); reqMethods.hasNext(); ) addMethod(ctrlMethod, reqMethods.next()); } } private void addMethod(ControllerMethod ctrlMethod, String requestMethod) { ControllerMethod[] ctrlMethods = reqMethod2ctrlMethod_.get(requestMethod); ctrlMethods = ctrlMethods == null ? new ControllerMethod[] { ctrlMethod } : ArrayUtil.addLast(ctrlMethods, ctrlMethod); reqMethod2ctrlMethod_.put(requestMethod, ctrlMethods); } /** * Returns the ControllerClass of the controller. */ public Class<? extends Controller> getControllerClass() { return controllerClass_; } public boolean contains(ControllerMethod method) { for (ControllerMethod m : methods_) { if (m == method) return true; } return false; } public String getMethodPath() { return methodPath_; } public String getSignature() { return ControllerSignature.build(controllerClass_.getName(), methodPath_); } /** * Creates a Controller and initializes the type. * @see Controller#setControllerType(ControllerType) */ public Controller createController() { try { Controller controller = factory_ != null ? factory_.createController(controllerClass_) : controllerClass_.newInstance(); controller.setControllerType(this); return controller; } catch (Exception e) { throw new IllegalArgumentException("could not instantiate controller " + getControllerClass().getName(), e); } } /** * Returns an iterator for all controller methods. */ @Override public Iterator<ControllerMethod> iterator() { return Iterators.forValues(methods_); } /** * Returns the number of ControllerMethods. */ public int getMethodCount() { return methods_.length; } /** * Returns the i-th ControllerMethod. */ public ControllerMethod getMethod(int i) { return methods_[i]; } /** * Returns a ControllerMethod for a request. * @return a NegotiatedMethod object containing the method + selected response content-type * or an error code */ public NegotiatedMethod getMethod(Request request) { ControllerMethod[] methods = reqMethod2ctrlMethod_.get(request.getMethod()); return methods == null ? new NegotiatedMethod(Response.Status.SC405_METHOD_NOT_ALLOWED) : negotiate(methods, request.getContentType(), request.getAcceptedContentTypes()); } /** * Selects a ControllerMethod which matches the request method, request content type * and accepted response content-types. * @return a NegotiatedMethod object containing the method + selected response content-type * or an error code */ public NegotiatedMethod getMethod(String requestMethod, ContentType requestContentType, ContentTypeList acceptedResponseTypes) { ControllerMethod[] methods = reqMethod2ctrlMethod_.get(requestMethod); if (methods == null) return new NegotiatedMethod(Response.Status.SC405_METHOD_NOT_ALLOWED); else return negotiate(methods, requestContentType, acceptedResponseTypes); } private NegotiatedMethod negotiate(ControllerMethod[] methods, ContentType requestContentType, ContentTypeList acceptedResponseTypes) { ControllerMethod bestMatch = null; ContentNegotiation conneg = new ContentNegotiation(acceptedResponseTypes); boolean canConsume = false; for (ControllerMethod m : methods) { if (m.canConsume(requestContentType)) { canConsume = true; if (m.canProduce(conneg)) bestMatch = m; } } return bestMatch != null ? new NegotiatedMethod(bestMatch, conneg.bestType) : new NegotiatedMethod(canConsume ? Response.Status.SC406_NOT_ACCEPTABLE : Response.Status.SC415_UNSUPPORTED_MEDIA_TYPE); } /** * Returns the controller method for the given Java method. */ public ControllerMethod getMethod(Method method) { for (ControllerMethod m : methods_) { if (m.getJavaMethod() == method) return m; } return null; } /** * Returns the controller method with the given Java method name. * @param javaName the Java method name */ public ControllerMethod getMethod(String javaName) { return getMethod(javaName, (Class<?>[])null); } /** * Returns the controller method with the given Java method name and argument types. * @param javaName the Java method name * @param paramTypes the method parameter types. Pass an explicit null if you * want to compare the method name only. */ public ControllerMethod getMethod(String javaName, Class<?>... paramTypes) { for (ControllerMethod m : methods_) { Method method = m.getJavaMethod(); if (method.getName().equals(javaName) && ((paramTypes == null) || matchesArgTypes(method, paramTypes))) return m; } return null; } private boolean matchesArgTypes(Method method, Class<?>[] paramTypes) { Class<?>[] actualTypes = method.getParameterTypes(); if (actualTypes.length != paramTypes.length) return false; for (int i=0; i<actualTypes.length; i++) { if (!actualTypes[i].equals(paramTypes[i])) return false; } return true; } /** * Returns a debug string. */ @Override public String toString() { return "Type:" + getControllerClass().getName(); } private Class<? extends Controller> controllerClass_; private HashMap<String, ControllerMethod[]> reqMethod2ctrlMethod_ = new HashMap<>(); private ControllerMethod[] methods_; private String methodPath_; private ControllerFactory factory_; }