package com.mastfrog.acteur.preconditions; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.google.inject.Singleton; import com.mastfrog.acteur.Acteur; import com.mastfrog.acteur.Page; import com.mastfrog.giulius.Ordered; import com.mastfrog.util.ConfigurationError; import java.lang.annotation.Annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Mechanism for pluggable handling of annotations - this way an application can * create its own annotations which imply that some acteurs are to be added to a * page, and actually add them. * <p> * To use, implement your PageAnnotationHandler, and bind it as an eager * singleton so it is instantiated and registered on startup. * <p> * Note: If you need to guarantee your handlers run *after* the built in ones * (for example, your code assumes authentication has already happened and a User * object is available for injectio), annotate your implementation with &064;Ordered * with a value greater than 0. * * @author Tim Boudreau */ public abstract class PageAnnotationHandler { private final Set<Class<? extends Annotation>> types; @SuppressWarnings("unchecked") /** * Create a PageAnnotationHandler * * @param registry The registry, which this will automatically be registered * with * @param annotationTypes An array of annotation classes (while generic * arrays are illegal in java, an error will be thrown if a passed type is * not an annotation type) */ protected PageAnnotationHandler(Registry registry, Class<?>... annotationTypes) { Set<Class<? extends Annotation>> tps = new HashSet<>(); for (Class<?> type : annotationTypes) { if (!Annotation.class.isAssignableFrom(type)) { throw new ConfigurationError("Not an annotation type: " + type.getName()); } Retention retention = type.getAnnotation(Retention.class); if (retention == null) { throw new ConfigurationError("Not annotated with @Retention: " + type.getName()); } if (retention.value() != RetentionPolicy.RUNTIME) { throw new ConfigurationError("Attempting to use " + type.getName() + " as a page annotation, but it does not have @Retention(RUNTIME)"); } tps.add((Class<? extends Annotation>) type); } this.types = ImmutableSet.<Class<? extends Annotation>>copyOf(tps); registry.register(this); } public abstract <T extends Page> boolean processAnnotations(T page, List<? super Acteur> addTo); protected final Set<Class<? extends Annotation>> types() { return types; } /** * Registry of PageAnnotationHandlers */ @Singleton public static final class Registry { private final List<PageAnnotationHandler> handlers = new LinkedList<>(); private final Set<Class<? super Page>> annotatedPages = Sets.newConcurrentHashSet(); public <T extends Page> boolean processAnnotations(T page, List<? super Acteur> addTo) { boolean result = false; for (PageAnnotationHandler handler : handlers) { result |= handler.processAnnotations(page, addTo); } return result; } @SuppressWarnings({"element-type-mismatch", "unchecked"}) public <T extends Page> boolean hasAnnotations(T page) { Class<? super Page> c = (Class<? super Page>) page.getClass(); if (annotatedPages.contains(page.getClass())) { return true; } for (Class<? extends Annotation> type : types()) { if (c.getAnnotation(type) != null) { annotatedPages.add(c); return true; } } return false; } public void register(PageAnnotationHandler handler) { handlers.add(handler); Collections.sort(handlers, new Ordered.OrderedObjectComparator()); } private volatile Set<Class<? extends Annotation>> types; public Set<Class<? extends Annotation>> types() { if (types == null) { synchronized (this) { if (types == null) { ImmutableSet.Builder<Class<? extends Annotation>> builder = ImmutableSet.<Class<? extends Annotation>>builder(); for (PageAnnotationHandler h : handlers) { builder.addAll(h.types()); } this.types = builder.build(); } } } return types; } } }