/* * 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.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Type; import javax.servlet.http.Cookie; import org.civilian.Request; import org.civilian.Response; import org.civilian.annotation.BeanParam; import org.civilian.annotation.CookieParam; import org.civilian.annotation.DefaultValue; import org.civilian.annotation.HeaderParam; import org.civilian.annotation.LocaleValue; import org.civilian.annotation.MatrixParam; import org.civilian.annotation.Parameter; import org.civilian.annotation.RequestContent; import org.civilian.internal.controller.arg.StringMethodArg; import org.civilian.internal.controller.arg.conv.ConvertingArg; import org.civilian.internal.controller.arg.misc.BeanParamArg; import org.civilian.internal.controller.arg.misc.RequestArg; import org.civilian.internal.controller.arg.misc.ResponseArg; import org.civilian.internal.controller.arg.misc.ResponseContentArg; import org.civilian.internal.controller.arg.reqcontent.ReqContentArgs; import org.civilian.internal.controller.arg.reqparam.CookieParamObjectArg; import org.civilian.internal.controller.arg.reqparam.CookieParamValueArg; import org.civilian.internal.controller.arg.reqparam.HeaderParamValueArg; import org.civilian.internal.controller.arg.reqparam.MatrixParamValueArg; import org.civilian.internal.controller.arg.reqparam.PathParamArg; import org.civilian.internal.controller.arg.reqparam.ParameterValueArg; import org.civilian.resource.PathParam; import org.civilian.resource.PathParamMap; import org.civilian.resource.PathScanner; import org.civilian.type.TypeLib; import org.civilian.util.ClassUtil; /** * MethodArgFactory is a factory for MethodArgs. */ public class MethodArgFactory { /** * Creates a new MethodArgFactory object. * @param pathParams the PathParamMap of the application * @param typeLib the TypeLib of the application */ public MethodArgFactory(PathParamMap pathParams, TypeLib typeLib) { pathParams_ = pathParams; typeLib_ = typeLib; } /** * Creates MethodArg objects for the parameters of a controller action method. * For each method parameter the parameter annotations and parameter type are * evaluated to generate the MethodArg. * @param method the action method of a controller * @return an array with MethodArg for the method or null, if the method * does not have parameters * @throws IllegalArgumentException if creation of MethodArg fails */ public MethodArg[] createParamArgs(Method method) throws IllegalArgumentException { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length == 0) return null; Type[] genericParamTypes = method.getGenericParameterTypes(); Annotation[][] paramAnnos = method.getParameterAnnotations(); MethodArg[] result = new MethodArg[paramTypes.length]; Info info = new Info(); for (int i=0; i<paramTypes.length; i++) { try { info.type = paramTypes[i]; info.genericType = genericParamTypes[i]; info.annotations = paramAnnos[i]; result[i] = parse(info, null, true); } catch(Exception e) { String message = "method '" + method.getDeclaringClass().getName() + '#' + method.getName() + ": " + "param nr " + (i+1) + " (type '" + info.type.getName() + "') : " + e.getMessage(); throw new IllegalArgumentException(message, e); } } return result; } /** * Creates an MethodArg object for the parameter of a setter method. * The method annotations and parameter type are * evaluated to generate the MethodArg. The MethodArg * defaults to the parameter with the name of the bean property. * @param method a setter method * @param beanProperty the name of beam property which is set by the method. */ public MethodArg createSetterMethodArg(Method method, String beanProperty, boolean mustExist) { Class<?>[] paramTypes = method.getParameterTypes(); if (paramTypes.length != 1) return null; Info info = new Info(paramTypes[0], method.getGenericParameterTypes()[0], method.getAnnotations()); try { return parse(info, beanProperty, mustExist); } catch(Exception e) { String message = "method '" + method.getDeclaringClass().getName() + '#' + method.getName() + ": parameter type '" + info.type.getName() + "' : " + e.getMessage(); throw new IllegalArgumentException(message, e); } } /** * Creates an MethodArg object for a field. * The field annotations and type are * evaluated to generate the MethodArg. * The MethodArg defaults to the request parameter with the field name. */ public MethodArg createFieldArg(Field field, boolean mustExist) { Info info = new Info(field.getType(), field.getGenericType(), field.getAnnotations()); try { return parse(info, field.getName(), mustExist); } catch(Exception e) { String message = "field '" + field.getDeclaringClass().getName() + '#' + field.getName() + ": type '" + field.getName() + "' : " + e.getMessage(); throw new IllegalArgumentException(message, e); } } private MethodArg parse(Info info, String parameterName, boolean mustExist) throws Exception { if (info.type == Response.class) return new ResponseArg(); if (info.type == Request.class) return new RequestArg(); if (info.findAnnotation(RequestContent.class) != null) return ReqContentArgs.create(info.type, info.genericType); if (org.civilian.response.ResponseContent.class.isAssignableFrom(info.type) || (info.findAnnotation(org.civilian.annotation.ResponseContent.class) != null)) return new ResponseContentArg(info.type); MethodArg arg = parsePathParamArg(info); if (arg != null) return arg; arg = parseRequestParamArgument(info); if (arg != null) return arg; arg = parseBeanParamArgument(info); if (arg != null) return arg; arg = parseCustomArgument(info); if (arg != null) return arg; if (parameterName != null) return parseRequestParamArg(info, new ParameterValueArg(parameterName), true); if (!mustExist) return null; throw new IllegalArgumentException("parameter has no recognized annotation and is not an injectable context variable"); } private MethodArg parseCustomArgument(Info info) throws Exception { for (Annotation a : info.annotations) { org.civilian.annotation.MethodArgProvider caf = a.annotationType().getAnnotation(org.civilian.annotation.MethodArgProvider.class); if (caf != null) { MethodArgProvider f = ClassUtil.createObject(caf.value(), MethodArgProvider.class, null); return f.create(this, a, info.type, info.genericType, info.annotations); } } return null; } private <T> MethodArg parsePathParamArg(Info info) { org.civilian.annotation.PathParam ppAnno = info.findAnnotation(org.civilian.annotation.PathParam.class); if (ppAnno == null) return null; @SuppressWarnings("unchecked") PathParam<T> pathParam = (PathParam<T>)pathParams_.get(ppAnno.value()); if (pathParam == null) throw new IllegalArgumentException("references unknown path parameter '" + ppAnno.value() + "'"); Class<?> pt = !info.type.isPrimitive() ? info.type : TypeLib.getObjectClassForPrimitiveType(info.type); if (!pt.isAssignableFrom(pathParam.getType())) { throw new IllegalArgumentException("wrong type for PathParam '" + pathParam.getName() + "' (expected '" + pathParam.getType().getName() + "')"); } return new PathParamArg<>(pathParam, getDefaultValue(info, pathParam)); } private<T> MethodArg parseBeanParamArgument(Info info) throws Exception { return info.findAnnotation(BeanParam.class) != null ? new BeanParamArg(this, info.type) : null; } /** * Returns a RequestParamArg if the method parameter is annotated * with a @Parameter, @MatrixParam, @HeaderParam annotation. */ private<T> MethodArg parseRequestParamArgument(Info info) throws Exception { Parameter qp = info.findAnnotation(Parameter.class); if (qp != null) return parseRequestParamArg(info, new ParameterValueArg(qp.value()), false); HeaderParam hp = info.findAnnotation(HeaderParam.class); if (hp != null) return parseRequestParamArg(info, new HeaderParamValueArg(hp.value()), false); MatrixParam mp = info.findAnnotation(MatrixParam.class); if (mp != null) return parseRequestParamArg(info, new MatrixParamValueArg(mp.value()), false); CookieParam cp = info.findAnnotation(CookieParam.class); if (cp != null) { if (info.genericType == Cookie.class) return new CookieParamObjectArg(cp.value()); else return new CookieParamValueArg(cp.value()); } return null; } private<T> MethodArg parseRequestParamArg(Info info, StringMethodArg arg, boolean ignoreUnsupportedTypes) throws Exception { DefaultValue dv = info.findAnnotation(DefaultValue.class); String defaultValue = dv != null ? dv.value() : null; LocaleValue lv = info.findAnnotation(LocaleValue.class); return ConvertingArg.create(arg, defaultValue, lv != null, typeLib_, info.type, info.genericType); } private <T> T getDefaultValue(Info info, PathParam<T> pathParam) { DefaultValue dv = info.findAnnotation(DefaultValue.class); if (dv == null) return null; else { PathScanner scanner = new PathScanner(dv.value()); T value = pathParam.parse(scanner); if (value == null) throw new IllegalArgumentException("default parameter value '" + dv.value() + "' is not a valid path parameter of " + pathParam); return value; } } private static class Info { public Info() { } public Info(Class<?> type, java.lang.reflect.Type genericType, Annotation[] annotations) { this.type = type; this.genericType = genericType; this.annotations = annotations; } public <A extends Annotation> A findAnnotation(Class<A> c) { return ClassUtil.findAnnotation(this.annotations, c); } public Class<?> type; public java.lang.reflect.Type genericType; public Annotation[] annotations; } private final PathParamMap pathParams_; private final TypeLib typeLib_; }