package ru.vyarus.dropwizard.guice.module.installer.util; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import io.dropwizard.Application; import io.dropwizard.Configuration; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import ru.vyarus.dropwizard.guice.module.context.ConfigurationContext; import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBootstrap; import ru.vyarus.dropwizard.guice.module.installer.bundle.GuiceyBundle; import java.lang.reflect.Field; import java.util.Collections; import java.util.Iterator; import java.util.List; /** * Utility class to work with registered {@link io.dropwizard.Bundle} and {@link io.dropwizard.ConfiguredBundle} * objects within dropwizard {@link Bootstrap} object. * * @author Vyacheslav Rusakov * @since 01.08.2015 */ public final class BundleSupport { private BundleSupport() { } /** * Process initially registered and all transitive bundles. * <ul> * <li>Executing initial bundles (registered in {@link ru.vyarus.dropwizard.guice.GuiceBundle} * and by bundle lookup)</li> * <li>During execution bundles may register other bundles (through {@link GuiceyBootstrap})</li> * <li>Execute registered bundles and repeat from previous step until no new bundles registered</li> * </ul> * Bundles duplicates are checked by type: only one bundle instance may be registered. * * @param context bundles context * @param configuration configuration object * @param environment environment object * @param application application instance */ public static void processBundles(final ConfigurationContext context, final Configuration configuration, final Environment environment, final Application application) { final List<GuiceyBundle> bundles = context.getBundles(); final List<Class<? extends GuiceyBundle>> installedBundles = Lists.newArrayList(); final GuiceyBootstrap guiceyBootstrap = new GuiceyBootstrap( context, bundles, configuration, environment, application); // iterating while no new bundles registered while (!bundles.isEmpty()) { final List<GuiceyBundle> processingBundles = Lists.newArrayList(BundleSupport.removeDuplicates(bundles)); bundles.clear(); for (GuiceyBundle bundle : removeTypes(processingBundles, installedBundles)) { final Class<? extends GuiceyBundle> bundleType = bundle.getClass(); Preconditions.checkState(!installedBundles.contains(bundleType), "State error: duplicate bundle '%s' registration", bundleType.getName()); context.setScope(bundleType); bundle.initialize(guiceyBootstrap); installedBundles.add(bundleType); context.closeScope(); } } } /** * Remove duplicates in list by rule: only one instance of type must be present in list. * * @param list bundles list * @param <T> required bundle type * @return list cleared from duplicates */ public static <T> List<T> removeDuplicates(final List<T> list) { final List<Class> registered = Lists.newArrayList(); final Iterator it = list.iterator(); while (it.hasNext()) { final Class type = it.next().getClass(); if (registered.contains(type)) { it.remove(); } else { registered.add(type); } } return list; } /** * Filter list from objects of type present in filter list. * * @param list list to filter * @param filter types to filter * @param <T> required type * @return filtered list */ public static <T> List<T> removeTypes(final List<T> list, final List<Class<? extends T>> filter) { final Iterator it = list.iterator(); while (it.hasNext()) { final Class type = it.next().getClass(); if (filter.contains(type)) { it.remove(); } } return list; } /** * @param bootstrap dropwizard bootstrap instance * @param type required bundle type (or marker interface) * @param <T> required bundle type * @return list of bundles of specified type */ @SuppressWarnings("unchecked") public static <T> List<T> findBundles(final Bootstrap bootstrap, final Class<T> type) { final List bundles = Lists.newArrayList(resolveBundles(bootstrap, "bundles")); bundles.addAll(resolveBundles(bootstrap, "configuredBundles")); final Iterator it = bundles.iterator(); while (it.hasNext()) { if (!type.isAssignableFrom(it.next().getClass())) { it.remove(); } } return bundles; } @SuppressWarnings("unchecked") private static <T> List<T> resolveBundles(final Bootstrap bootstrap, final String field) { try { final Field declaredField = Bootstrap.class.getDeclaredField(field); declaredField.setAccessible(true); final List<T> res = (List<T>) declaredField.get(bootstrap); declaredField.setAccessible(false); // in case of mock bootstrap (tests) return MoreObjects.firstNonNull(res, Collections.<T>emptyList()); } catch (Exception e) { throw new IllegalStateException("Failed to resolve bootstrap filed " + field, e); } } }