package org.fenixedu.bennu.core.bootstrap; import static com.google.common.base.Preconditions.checkArgument; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; import java.util.Set; import javax.servlet.DispatcherType; import javax.servlet.FilterRegistration; import javax.servlet.ServletContext; import org.fenixedu.bennu.core.bootstrap.annotations.Bootstrap; import org.fenixedu.bennu.core.bootstrap.annotations.Bootstrapper; import org.fenixedu.bennu.core.bootstrap.annotations.Field; import org.fenixedu.bennu.core.bootstrap.annotations.Section; import org.fenixedu.bennu.core.domain.Bennu; import pt.ist.fenixframework.Atomic; import pt.ist.fenixframework.Atomic.TxMode; import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** * Central place for {@link Bootstrapper} to register themselves. * * Upon application startup, all the Bootstrappers should be registered here. * */ public class BootstrapperRegistry { private static final String UNDECLARED_SECTION_ERROR = "The parameters for '%s' must be declared as a @Bootstrapper sections"; private static final String BOOTSTRAP_PARAMETER_ERROR = "All the parameters for '%s' must have a @Section annotation"; private static final String STATIC_METHOD_ERROR = "Bootstrap method '%s' must be static."; private static Set<Class<?>> bootstrappers = Sets.newConcurrentHashSet(); /** * Registers the {@link BootstrapFilter} on a given {@link ServletContext}. * * @param servletContext * The servletContext where the filter will be registered */ @Atomic(mode = TxMode.READ) public static void registerBootstrapFilter(ServletContext servletContext) { if (Bennu.getInstance().getUserSet().isEmpty()) { FilterRegistration registration = servletContext.addFilter("BootstrapFilter", BootstrapFilter.class); registration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*"); } } /** * Registers a new {@link Bootstrapper}. * * @param bootstrapper * The bootstrapper to register * @throws IllegalArgumentException * If the bootstrapper method is not static or the sections are not declared as such. */ public static void register(Class<?> bootstrapper) { validateBootstrapper(bootstrapper); bootstrappers.add(bootstrapper); } /** * Returns all classes registered with a {@link Bootstrapper} annotation. * * @return all the registered bootstrapper classes. */ public static List<Class<?>> getBootstrappers() { return new BootstrappersSorter(bootstrappers).sort(); } /** * Returns all methods registered with a {@link Bootstrap} annotation. * * @return all the registered bootstrap methods. */ public static List<Method> getBootstrapMethods() { List<Method> methods = Lists.newArrayList(); for (Class<?> bootstrapper : getBootstrappers()) { for (Method bootstrapMethod : bootstrapper.getMethods()) { if (bootstrapMethod.isAnnotationPresent(Bootstrap.class)) { methods.add(bootstrapMethod); } } } return methods; } /** * Returns all classes registered with a {@link Section} annotation. * * @return all the registered section classes. */ public static List<Class<?>> getSections() { List<Class<?>> sections = Lists.newArrayList(); for (Class<?> bootstrapper : getBootstrappers()) { for (Class<?> section : getSections(bootstrapper)) { if (!sections.contains(section)) { sections.add(section); } } } return sections; } /** * Returns all the section classes of a given {@link Bootstrapper}. * * @param bootstrapper * the bootstrapper class * * @return all the registered section classes of a the bootstrapper. */ public static List<Class<?>> getSections(Class<?> bootstrapper) { return Lists.newArrayList(bootstrapper.getAnnotation(Bootstrapper.class).sections()); } /** * Returns all the section methods with a {@link Field} annotation * * @param section * The section class. * * @return all the registered fields for a given section. */ public static List<Method> getSectionFields(Class<?> section) { List<Method> fields = Lists.newArrayList(); for (Method method : section.getMethods()) { if (method.isAnnotationPresent(Field.class)) { fields.add(method); } } Collections.sort(fields, FIELDS_COMPARATOR); return fields; } private static void validateBootstrapper(Class<?> bootstrapperClass) { for (Method bootstrapMethod : bootstrapperClass.getMethods()) { if (bootstrapMethod.isAnnotationPresent(Bootstrap.class)) { boolean isStaticMethod = Modifier.isStatic(bootstrapMethod.getModifiers()); String methodName = bootstrapMethod.getDeclaringClass().getName() + "." + bootstrapMethod.getName(); checkArgument(isStaticMethod, String.format(STATIC_METHOD_ERROR, methodName)); Bootstrapper bootstrapper = bootstrapMethod.getDeclaringClass().getAnnotation(Bootstrapper.class); for (Class<?> section : bootstrapper.sections()) { checkArgument(section.isAnnotationPresent(Section.class), String.format(BOOTSTRAP_PARAMETER_ERROR, methodName)); } boolean allDeclaredSections = Lists.newArrayList(bootstrapper.sections()).containsAll( Lists.newArrayList(bootstrapMethod.getParameterTypes())); checkArgument(allDeclaredSections, String.format(UNDECLARED_SECTION_ERROR, methodName)); } } } private static final Comparator<Method> FIELDS_COMPARATOR = new Comparator<Method>() { @Override public int compare(Method method1, Method method2) { return method1.getAnnotation(Field.class).order() - method2.getAnnotation(Field.class).order(); } }; private static class BootstrappersSorter { private Set<Class<?>> visited = Sets.newHashSet(); private List<Class<?>> ordered = Lists.newArrayList(); private Set<Class<?>> unnordered; public BootstrappersSorter(Set<Class<?>> unnorderedBootstrappers) { this.unnordered = Sets.newHashSet(unnorderedBootstrappers); } private void dfs(Class<?> node) { unnordered.remove(node); visited.add(node); for (Class<?> neighboorClass : node.getAnnotation(Bootstrapper.class).after()) { if (!visited.contains(neighboorClass) && !ordered.contains(neighboorClass)) { dfs(neighboorClass); } } visited.remove(node); ordered.add(node); } public List<Class<?>> sort() { while (!unnordered.isEmpty()) { dfs(unnordered.iterator().next()); } return ordered; } } }