/* * 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.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import org.civilian.Controller; import org.civilian.Request; import org.civilian.annotation.Consumes; import org.civilian.annotation.Delete; import org.civilian.annotation.Get; import org.civilian.annotation.Head; import org.civilian.annotation.Options; import org.civilian.annotation.Post; import org.civilian.annotation.Produces; import org.civilian.annotation.Put; import org.civilian.annotation.RequestMethod; import org.civilian.content.ContentNegotiation; import org.civilian.content.ContentType; import org.civilian.content.ContentTypeList; import org.civilian.internal.controller.MethodAnnotations; import org.civilian.util.Iterators; import org.civilian.util.StringUtil; /** * ControllerMethod represents a Java method in a controller class which can * be called to produce a response to a resource request. These methods * are also called action methods * A controller method qualifies as action method: * <ul> * <li>The method is public and not static * <li>Its return type is void * <li>It either has a {@link Get @Get}, {@link Post @Post}, {@link Put @Put}, {@link Delete @Delete}, * {@link Head @Head}, {@link Options @Options} or {@link RequestMethod @RequestMethod} annotation * or it overrides an inherited action method. * </ul> * A controller can publish multiple action methods. Content negotiation is used * to select the method which finally handles the request. */ public class ControllerMethod { /** * Creates a ControllerMethod. * @param argFactory an MethodArgFactory instance which can help to create standard MethodArgs. * @param javaMethod a method of a controller class * @return the ControllerMethod or null, if the method is not a valid action method. */ public static ControllerMethod create(MethodArgFactory argFactory, Method javaMethod) { MethodAnnotations ma = MethodAnnotations.create(javaMethod); return ma != null ? new ControllerMethod(argFactory, javaMethod, ma) : null; } private ControllerMethod(MethodArgFactory argFactory, Method javaMethod, MethodAnnotations annotations) { javaMethod_ = javaMethod; args_ = argFactory.createParamArgs(javaMethod); requestMethods_ = annotations.getRequestMethods(); produces_ = annotations.getProduces(); consumes_ = annotations.getConsumes(); javaMethod_.setAccessible(true); } /** * Returns name of the Java method represented by this action. */ public String getName() { return javaMethod_.getName(); } /** * Returns the Java method represented by this action. */ public Method getJavaMethod() { return javaMethod_; } /** * Returns the controller class which declared the method. */ @SuppressWarnings("unchecked") public Class<? extends Controller> getDeclaringClass() { return (Class<? extends Controller>)javaMethod_.getDeclaringClass(); } /** * Returns an iterator for all supported HTTP request methods. * @see Request#getMethod() */ public Iterator<String> getRequestMethods() { return Iterators.forValues(requestMethods_); } /** * Returns if the ControllerMethod can be inherited by the given derived controller class. * For this the derived controller class must not override the method. */ public boolean canInherit(Class<? extends Controller> controllerClass) { if ((controllerClass != null) && javaMethod_.getDeclaringClass().isAssignableFrom(controllerClass)) { // inherit annotation was set: now test if the // derived resourceClass does not shadow our method Method m; try { m = controllerClass.getMethod(javaMethod_.getName(), javaMethod_.getParameterTypes()); if (m.getDeclaringClass() != controllerClass) return true; } catch (Exception e) { } } return false; } /** * Returns if this ControllerMethod can handle requests with * the given content type. Returns true if either no {@link Consumes} annotation * was set on the method or the {@link Consumes} annotation * matches that content type. */ public boolean canConsume(ContentType contentType) { if (consumes_ == null) { // if we don't have a @Consumes definition we accept all return true; } else return consumes_.matchesSome(contentType != null ? contentType : ContentType.ANY); } /** * Returns a ContentTypeList for all content types defined by a {@link Consumes} annotation * on the action method. The list is empty if no {@link Consumes} annotation was set. */ public ContentTypeList getConsumesContentTypes() { return consumes_ != null ? consumes_ : ContentTypeList.EMPTY; } /** * Returns if this action can produce a ContentType which is more than suitable * than the previously best type found by the ContentNegotiation. */ public boolean canProduce(ContentNegotiation conneg) { return produces_ == null ? conneg.evaluate(ContentType.ANY) : conneg.evaluate(produces_); } /** * Returns a ContentTypeList for all content types defined by a {@link Produces} annotation * on the action method. The list is empty if no {@link Produces} annotation was set. */ public ContentTypeList getProducedContentTypes() { return produces_ != null ? produces_ : ContentTypeList.EMPTY; } /** * Returns the number of arguments injected into the method. */ int getArgCount() { return args_ == null ? 0 : args_.length; } MethodArg getArgument(int i) { return args_[i]; } /** * Invokes the action method on the controller. */ public void invoke(Controller controller) throws Exception { Request request = controller.getRequest(); Object[] argValues = null; if (args_ != null) argValues = buildArgValues(request); try { javaMethod_.invoke(controller, argValues); } catch(InvocationTargetException e) { if (e.getCause() instanceof Error) throw (Error)e.getCause(); if (e.getCause() instanceof Exception) throw (Exception)e.getCause(); else throw e; } if (argValues != null) { for (int i=0; i<argValues.length; i++) args_[i].postProcess(request, argValues[i]); } } private Object[] buildArgValues(Request request) throws Exception { Object[] argValues = new Object[args_.length]; for (int i=0; i<argValues.length; i++) argValues[i] = args_[i].getValue(request); return argValues; } /** * Returns an information string of the Action for debug purposes. */ public String getInfo() { StringBuilder s = new StringBuilder(); for (String rm : requestMethods_) { s.append('@'); s.append(StringUtil.startUpperCase(rm.toLowerCase())); } getInfo(consumes_, "Consumes", s); getInfo(produces_, "Produces", s); return s.toString(); } private void getInfo(ContentTypeList contentTypes, String what, StringBuilder s) { if (contentTypes != null) { s.append(" @"); s.append(what); s.append('('); for (int i=0; i<contentTypes.size(); i++) { if (i > 0) s.append(", "); s.append(contentTypes.get(i).getValue()); } s.append(')'); } } /** * Returns the name of the java method. */ @Override public String toString() { return getJavaMethod().getName(); } private Method javaMethod_; private String[] requestMethods_; private ContentTypeList produces_; private ContentTypeList consumes_; private MethodArg[] args_; }