package com.softwaremill.common.cdi.security;
import org.jboss.solder.reflection.annotated.AnnotatedTypeBuilder;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.util.AnnotationLiteral;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author Adam Warski (adam at warski dot org)
*/
public class SecurityExtension implements Extension, Serializable {
private final Map<Method, InterceptSecure> interceptSecureForMethods = new HashMap<Method, InterceptSecure>();
public InterceptSecure getInterceptSecure(Method m) {
return interceptSecureForMethods.get(m);
}
public <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> event) {
// A flag indicating if the builder was used to modify the annotations
boolean used = false;
AnnotatedTypeBuilder<T> builder = new AnnotatedTypeBuilder<T>().readFromType(event.getAnnotatedType());
// We need to read the values of the @Secure annotation that are present on:
// 1. types (classes)
// 2. methods
// 3. arbitrarily nested on @SecureBinding annotations
// Gathering the initial secure values from the type
// TODO: look in superclasses?
List<Secure> initialSecureAnnotations = new ArrayList<Secure>();
List<AllowWithFlag> initialAllowWithFlagsAnnotations = new ArrayList<AllowWithFlag>();
for (Annotation annotation : event.getAnnotatedType().getAnnotations()) {
collectAnnotations(Secure.class, annotation, initialSecureAnnotations);
collectAnnotations(AllowWithFlag.class, annotation, initialAllowWithFlagsAnnotations);
}
for (AnnotatedMethod<?> m : event.getAnnotatedType().getMethods()) {
// Gathering the secure annotations from the method
final List<Secure> annotations = new ArrayList<Secure>(initialSecureAnnotations);
final List<AllowWithFlag> allowWithFlagsAnnotations = new ArrayList<AllowWithFlag>(initialAllowWithFlagsAnnotations);
collectAnnotations(Secure.class, m, annotations);
collectAnnotations(AllowWithFlag.class, m, allowWithFlagsAnnotations);
// If any annotations have been gathered, adding the annotation to the method and storing it
// in the map.
if (annotations.size() > 0) {
InterceptSecure is = new InterceptSecureImpl(
annotations.toArray(new Secure[annotations.size()]),
allowWithFlagsAnnotations.toArray(new AllowWithFlag[allowWithFlagsAnnotations.size()]));
builder.addToMethod(m.getJavaMember(), is);
used = true;
interceptSecureForMethods.put(m.getJavaMember(), is);
}
}
// Setting the new annotated type, if any changes were made
if (used) {
event.setAnnotatedType(builder.create());
}
}
private <T> void collectAnnotations(Class<T> annotationType, AnnotatedMethod m, List<T> values) {
for (Annotation annotation : m.getAnnotations()) {
collectAnnotations(annotationType, annotation, values);
}
}
private <T> void collectAnnotations(Class<T> annotationType, Annotation annotation, List<T> values) {
if (annotationType.isAssignableFrom(annotation.annotationType())) {
//noinspection unchecked
values.add((T) annotation);
} else {
if (annotation.annotationType().getAnnotation(SecureBinding.class) != null) {
for (Annotation nestedAnnotation : annotation.annotationType().getAnnotations()) {
collectAnnotations(annotationType, nestedAnnotation, values);
}
}
}
}
private static class InterceptSecureImpl extends AnnotationLiteral<InterceptSecure> implements InterceptSecure {
private final Secure[] values;
private final AllowWithFlag[] flags;
private InterceptSecureImpl(Secure[] values, AllowWithFlag[] flags) {
this.values = values;
this.flags = flags;
}
@Override
public Secure[] value() {
return values;
}
@Override
public AllowWithFlag[] flags() {
return flags;
}
}
}