package com.mastfrog.acteur.annotations; import com.google.common.collect.ImmutableSet; import com.google.inject.Module; import com.google.inject.TypeLiteral; import com.google.inject.name.Names; import com.mastfrog.acteur.server.ServerModule; import com.mastfrog.guicy.scope.ReentrantScope; import com.mastfrog.settings.Settings; import com.mastfrog.util.Exceptions; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Acteur ServerModule which uses GenericApplication. Page that have the * HttpCall annotation which are on the classpath will automatically be added, * and any bindings they specify will be bound in request scope. Any modules * with the @GuiceModule annotation will be automatically installed. * <p/> * An exclusion list can be passed to * * @author Tim Boudreau */ public class GenericApplicationModule<T extends GenericApplication> extends ServerModule<T> { // non final for unit tests that need to hide arguments private final Settings settings; private final Class<?>[] exclude; public static final String EXCLUDED_CLASSES = "excluded"; /** * Constructor which just takes a Settings, for use with giulius-tests * * @param settings */ @SuppressWarnings("unchecked") public GenericApplicationModule(Settings settings) { this(settings, (Class<T>) GenericApplication.class, new Class<?>[0]); } /** * Create a new GenericApplicationModule using the passed settings and the * specified class exclusion list. * * @param settings * @param exclude A list of Page, Module or implicit binding classes which * should be ignored */ @SuppressWarnings("unchecked") public GenericApplicationModule(Settings settings, Class<?>... exclude) { this(settings, (Class<T>) GenericApplication.class, exclude); } /** * Create a new GenericApplicationModule with a specific subtype of * GenericApplication. * * @param settings Settings * @param appType The application type * @param exclude A list of Page, Module or implicit binding classes which * should be ignored */ public GenericApplicationModule(Settings settings, Class<T> appType, Class<?>... exclude) { super(appType); this.settings = settings; this.exclude = exclude; } /** * Create a new GenericApplicationModule with a specific subtype of * GenericApplication, passing in the scope to be used for request scope. * * @param scope The scope to bind types in * @param settings Settings * @param appType The application type * @param exclude A list of Page, Module or implicit binding classes which * should be ignored */ public GenericApplicationModule(ReentrantScope scope, Settings settings, Class<T> appType, Class<?>... exclude) { super(scope, appType, -1, -1, -1); this.settings = settings; this.exclude = exclude; } public static <T extends Module> T instantiateModule(Class<T> m, Settings settings, ReentrantScope scope) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { try { try { Constructor<T> c = m.getDeclaredConstructor(); c.setAccessible(true); return c.newInstance(); } catch (NoSuchMethodException e) { try { Constructor<T> c = m.getDeclaredConstructor(Settings.class); c.setAccessible(true); return c.newInstance(settings); } catch (NoSuchMethodException e1) { try { Constructor<T> c = m.getDeclaredConstructor(Settings.class, ReentrantScope.class); c.setAccessible(true); return c.newInstance(settings, scope); } catch (NoSuchMethodException e2) { try { Constructor<T> c = m.getDeclaredConstructor(ReentrantScope.class, Settings.class); c.setAccessible(true); return c.newInstance(scope, settings); } catch (NoSuchMethodException e3) { Constructor<T> c = m.getDeclaredConstructor(ReentrantScope.class); c.setAccessible(true); return c.newInstance(scope); } } } } } catch (NoSuchMethodException e) { return m.newInstance(); } } @Override protected void configure() { try { super.configure(); HttpCallRegistryLoader ldr = new HttpCallRegistryLoader(appType); Set<Class<?>> toExclude = ImmutableSet.copyOf(new HashSet<>(Arrays.asList(exclude))); bind(Class[].class).annotatedWith(Names.named(EXCLUDED_CLASSES)).toInstance(exclude); bind(new GenericArrayOfClasses()).annotatedWith(Names.named(EXCLUDED_CLASSES)).toInstance(exclude); bind(new SetOfClasses()).annotatedWith(Names.named(EXCLUDED_CLASSES)).toInstance(toExclude); for (Class<?> c : ldr.implicitBindings()) { if (!toExclude.contains(c)) { scope.bindTypes(binder(), c); } } for (Class<? extends Module> module : ldr.modules()) { if (!toExclude.contains(module)) { install(instantiateModule(module, settings, scope)); } } } catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { Exceptions.chuck(ex); } } private static final class GenericArrayOfClasses extends TypeLiteral<Class<?>[]> { } private static final class SetOfClasses extends TypeLiteral<Set<Class<?>>> { } }