/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * Licensed 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 com.webcohesion.enunciate.modules.spring_web.model; import com.webcohesion.enunciate.javac.decorations.DecoratedProcessingEnvironment; import com.webcohesion.enunciate.javac.decorations.ElementDecorator; import com.webcohesion.enunciate.javac.decorations.element.DecoratedTypeElement; import com.webcohesion.enunciate.javac.decorations.element.DecoratedVariableElement; import com.webcohesion.enunciate.javac.decorations.element.PropertyElement; import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror; import com.webcohesion.enunciate.javac.decorations.type.TypeVariableContext; import com.webcohesion.enunciate.util.IgnoreUtils; import org.springframework.web.bind.annotation.*; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.ElementFilter; import java.util.*; /** * @author Ryan Heaton */ public class RequestParameterFactory { public static final Set<String> KNOWN_SYSTEM_MANAGED_PARAMETER_TYPES = new TreeSet<String>(Arrays.asList( //list of valid request mapping argument types that are supplied by the system, and not by the user. //see http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-arguments "javax.servlet.ServletContext", "javax.servlet.ServletRequest", "javax.servlet.ServletResponse", "javax.servlet.http.HttpSession", "org.springframework.web.context.request.WebRequest", "java.util.Locale", "java.util.TimeZone", "java.time.ZoneId", "java.io.Writer", "java.io.OutputStream", "org.springframework.http.HttpMethod", "java.security.Principal", "org.springframework.ui.Model", "org.springframework.ui.ModelMap", "java.util.Map", "org.springframework.web.servlet.mvc.support.RedirectAttributes", "org.springframework.validation.Errors", "org.springframework.validation.BindingResult", "org.springframework.web.bind.support.SessionStatus", "org.springframework.web.util.UriComponentsBuilder" )); public static List<RequestParameter> getRequestParameters(ExecutableElement mapping, VariableElement candidate, RequestMapping context) { ArrayList<RequestParameter> parameters = new ArrayList<RequestParameter>(); if (!gatherAnnotatedRequestParameters(mapping, candidate, parameters, context) && !isSystemManagedParameter(candidate)) { gatherFormObjectParameters(candidate.asType(), parameters, context); } return parameters; } private static boolean isSystemManagedParameter(VariableElement candidate) { if (candidate.getAnnotation(ModelAttribute.class) != null) { //model attribute parameters are system-managed. //see http://docs.spring.io/spring/docs/3.1.x/spring-framework-reference/html/mvc.html#mvc-ann-modelattrib-methods return true; } TypeMirror parameterType = candidate.asType(); while (parameterType instanceof DeclaredType) { Element element = ((DeclaredType) parameterType).asElement(); if (element instanceof TypeElement) { String fqn = ((TypeElement) element).getQualifiedName().toString(); if (KNOWN_SYSTEM_MANAGED_PARAMETER_TYPES.contains(fqn)) { //if it's a system parameter, it's not a request parameter. return true; } parameterType = ((TypeElement) element).getSuperclass(); } else { parameterType = null; } } return false; } private static boolean gatherAnnotatedRequestParameters(ExecutableElement mapping, VariableElement candidate, List<RequestParameter> parameters, PathContext context) { List<? extends AnnotationMirror> annotations = candidate.getAnnotationMirrors(); boolean success = false; Boolean isMapStringString = null; for (AnnotationMirror annotation : annotations) { TypeElement declaration = (TypeElement) annotation.getAnnotationType().asElement(); if (declaration != null) { String fqn = declaration.getQualifiedName().toString(); if (IgnoreUtils.isIgnored(declaration)) { parameters.clear(); //we're told to ignore this, so clear it. return true;//and indicate successful gathering. } if (PathVariable.class.getName().equals(fqn)) { if ((isMapStringString == null && (isMapStringString = isMapStringString(candidate.asType(), new TypeVariableContext(), context.getContext().getContext().getProcessingEnvironment()))) || isMapStringString) { for (PathSegment segment : context.getPathSegments()) { if (segment.getVariable() != null) { parameters.add(new ExplicitRequestParameter(mapping, null, segment.getVariable(), ResourceParameterType.PATH, false, segment.getRegex() == null ? new ResourceParameterConstraints.UnboundString() : new ResourceParameterConstraints.Regex(segment.getRegex()), context.getContext())); } } } else { parameters.add(new SimpleRequestParameter(candidate, context)); } success = true; } else if (MatrixVariable.class.getName().equals(fqn) || RequestParam.class.getName().equals(fqn) || RequestHeader.class.getName().equals(fqn)) { if ((isMapStringString == null && (!(isMapStringString = isMapStringString(candidate.asType(), new TypeVariableContext(), context.getContext().getContext().getProcessingEnvironment())))) || !isMapStringString) { //only add it if it's _not_ a map-string-string because otherwise we can't name the request parameters. parameters.add(new SimpleRequestParameter(candidate, context)); } success = true; } else if (CookieValue.class.getName().equals(fqn) || RequestPart.class.getName().equals(fqn)) { parameters.add(new SimpleRequestParameter(candidate, context)); success = true; } } } return success; } private static boolean isMapStringString(TypeMirror candidate, TypeVariableContext variableContext, DecoratedProcessingEnvironment env) { Element el = candidate instanceof DeclaredType ? ((DeclaredType)candidate).asElement() : candidate instanceof TypeVariable ? ((TypeVariable)candidate).asElement() : null; if (el instanceof TypeElement) { TypeElement element = (TypeElement) el; String fqn = element.getQualifiedName().toString(); if (Object.class.getName().equals(fqn)) { return false; } else if ("org.springframework.util.MultiValueMap".equals(fqn) || Map.class.getName().equalsIgnoreCase(fqn)) { TypeMirror resolvedType = variableContext.resolveTypeVariables(candidate, env); if (resolvedType instanceof DeclaredType) { List<? extends TypeMirror> typeArgs = ((DeclaredType) resolvedType).getTypeArguments(); if (typeArgs.size() == 2 && ((DecoratedTypeMirror<?>) typeArgs.get(0)).isInstanceOf(String.class) && ((DecoratedTypeMirror<?>) typeArgs.get(1)).isInstanceOf(String.class)) { return true; } } } else { TypeMirror superclass = element.getSuperclass(); if (superclass != null && superclass.getKind() != TypeKind.NONE) { return isMapStringString(superclass, variableContext.push(element.getTypeParameters(), candidate instanceof DeclaredType ? ((DeclaredType) candidate).getTypeArguments() : new ArrayList<TypeMirror>()), env); } } } return false; } private static void gatherFormObjectParameters(TypeMirror type, ArrayList<RequestParameter> params, RequestMapping context) { if (type instanceof DeclaredType) { Set<String> methods = context.getHttpMethods(); ResourceParameterType defaultType = methods.contains("POST") ? ResourceParameterType.FORM : ResourceParameterType.QUERY; DecoratedTypeElement typeDeclaration = (DecoratedTypeElement) ElementDecorator.decorate(((DeclaredType) type).asElement(), context.getContext().getContext().getProcessingEnvironment()); for (VariableElement field : ElementFilter.fieldsIn(typeDeclaration.getEnclosedElements())) { DecoratedVariableElement decorated = (DecoratedVariableElement) field; if (!decorated.isFinal() && !decorated.isTransient() && decorated.isPublic()) { params.add(new SimpleRequestParameter(decorated, context, defaultType)); } } for (PropertyElement property : typeDeclaration.getProperties()) { if (property.getSetter() != null) { params.add(new SimpleRequestParameter(property, context, defaultType)); } } if (typeDeclaration.getKind() == ElementKind.CLASS) { gatherFormObjectParameters(typeDeclaration.getSuperclass(), params, context); } } } }