/**
* 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.element.DecoratedTypeElement;
import com.webcohesion.enunciate.javac.decorations.type.TypeVariableContext;
import com.webcohesion.enunciate.modules.spring_web.EnunciateSpringWebContext;
import org.springframework.web.bind.annotation.ControllerAdvice;
import javax.lang.model.element.*;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;
import java.util.*;
/**
* A JAX-RS resource.
*
* @author Ryan Heaton
*/
public class SpringControllerAdvice extends DecoratedTypeElement {
private final EnunciateSpringWebContext context;
public SpringControllerAdvice(TypeElement delegate, EnunciateSpringWebContext context) {
super(delegate, context.getContext().getProcessingEnvironment());
this.context = context;
}
/**
* Whether the specified method is overridden by any of the methods in the specified list.
*
* @param method The method.
* @param resourceMethods The method list.
* @return If the methdo is overridden by any of the methods in the list.
*/
protected boolean isOverridden(ExecutableElement method, ArrayList<? extends ExecutableElement> resourceMethods) {
Elements decls = this.env.getElementUtils();
for (ExecutableElement resourceMethod : resourceMethods) {
if (decls.overrides(resourceMethod, method, (TypeElement) resourceMethod.getEnclosingElement())) {
return true;
}
}
return false;
}
public EnunciateSpringWebContext getContext() {
return context;
}
public List<RequestMappingAdvice> findRequestMappingAdvice(RequestMapping requestMapping) {
List<AdviceScope> scope = new ArrayList<AdviceScope>();
ControllerAdvice adviceInfo = getAnnotation(ControllerAdvice.class);
if (adviceInfo != null) {
Set<String> allPackages = new TreeSet<String>();
allPackages.addAll(Arrays.asList(adviceInfo.value()));
allPackages.addAll(Arrays.asList(adviceInfo.basePackages()));
try {
Class<?>[] classes = adviceInfo.basePackageClasses();
for (Class<?> clazz : classes) {
allPackages.add(clazz.getPackage().getName());
}
}
catch (MirroredTypesException e) {
List<? extends TypeMirror> mirrors = e.getTypeMirrors();
for (TypeMirror mirror : mirrors) {
if (mirror instanceof DeclaredType) {
Element element = ((DeclaredType) mirror).asElement();
while (element != null && (!(element instanceof PackageElement))) {
element = element.getEnclosingElement();
}
if (element != null) {
allPackages.add(((PackageElement) element).getQualifiedName().toString());
}
}
}
}
scope.add(new PackageAdviceScope(allPackages));
Set<String> allClasses = new TreeSet<String>();
try {
Class<?>[] classes = adviceInfo.assignableTypes();
for (Class<?> clazz : classes) {
allClasses.add(clazz.getName());
}
}
catch (MirroredTypesException e) {
List<? extends TypeMirror> mirrors = e.getTypeMirrors();
for (TypeMirror mirror : mirrors) {
if (mirror instanceof DeclaredType) {
Element element = ((DeclaredType) mirror).asElement();
if (element instanceof TypeElement) {
allClasses.add(((TypeElement) element).getQualifiedName().toString());
}
}
}
}
scope.add(new ClassAdviceScope(allClasses));
Set<String> allAnnotations = new TreeSet<String>();
try {
Class<?>[] classes = adviceInfo.annotations();
for (Class<?> clazz : classes) {
allAnnotations.add(clazz.getName());
}
}
catch (MirroredTypesException e) {
List<? extends TypeMirror> mirrors = e.getTypeMirrors();
for (TypeMirror mirror : mirrors) {
if (mirror instanceof DeclaredType) {
Element element = ((DeclaredType) mirror).asElement();
if (element instanceof TypeElement) {
allAnnotations.add(((TypeElement) element).getQualifiedName().toString());
}
}
}
}
scope.add(new AnnotationAdviceScope(allAnnotations));
if (allPackages.isEmpty() && allClasses.isEmpty() && allAnnotations.isEmpty()) {
scope.clear();
scope.add(new GlobalScope());
}
}
else {
scope.add(new ClassAdviceScope(new TreeSet<String>(Collections.singletonList(getQualifiedName().toString()))));
}
return findRequestMappingAdvice(requestMapping, this, scope, new TypeVariableContext());
}
protected List<RequestMappingAdvice> findRequestMappingAdvice(RequestMapping requestMapping, TypeElement controllerAdvice, List<AdviceScope> scope, TypeVariableContext variableContext) {
if (controllerAdvice == null || controllerAdvice.getQualifiedName().toString().equals(Object.class.getName())) {
return Collections.emptyList();
}
ArrayList<RequestMappingAdvice> advice = new ArrayList<RequestMappingAdvice>();
for (ExecutableElement method : ElementFilter.methodsIn(controllerAdvice.getEnclosedElements())) {
boolean applies = false;
for (AdviceScope adviceScope : scope) {
if (adviceScope.applies(requestMapping)) {
applies = true;
break;
}
}
if (applies) {
org.springframework.web.bind.annotation.ModelAttribute modelAttribute = method.getAnnotation(org.springframework.web.bind.annotation.ModelAttribute.class);
if (modelAttribute != null) {
advice.add(new RequestMappingAdvice(requestMapping, modelAttribute, method, this, variableContext, this.context));
}
}
}
//some methods may be specified by a superclass and/or implemented interface. But the annotations on the current class take precedence.
for (TypeMirror interfaceType : controllerAdvice.getInterfaces()) {
if (interfaceType instanceof DeclaredType) {
DeclaredType declared = (DeclaredType) interfaceType;
TypeElement element = (TypeElement) declared.asElement();
List<RequestMappingAdvice> interfaceMethods = findRequestMappingAdvice(requestMapping, element, scope, variableContext.push(element.getTypeParameters(), declared.getTypeArguments()));
for (RequestMappingAdvice interfaceMethod : interfaceMethods) {
if (!isOverridden(interfaceMethod, advice)) {
advice.add(interfaceMethod);
}
}
}
}
if (controllerAdvice.getKind() == ElementKind.CLASS) {
TypeMirror superclass = controllerAdvice.getSuperclass();
if (superclass instanceof DeclaredType && ((DeclaredType)superclass).asElement() != null) {
DeclaredType declared = (DeclaredType) superclass;
TypeElement element = (TypeElement) declared.asElement();
List<RequestMappingAdvice> superMethods = findRequestMappingAdvice(requestMapping, element, scope, variableContext.push(element.getTypeParameters(), declared.getTypeArguments()));
for (RequestMappingAdvice superMethod : superMethods) {
if (!isOverridden(superMethod, advice)) {
advice.add(superMethod);
}
}
}
}
return advice;
}
}