org.mayocat.application.AbstractService.java Source code

Java tutorial

Introduction

Here is the source code for org.mayocat.application.AbstractService.java

Source

/*
 * Copyright (c) 2012, Mayocat <hello@mayocat.org>
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */
package org.mayocat.application;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;

import org.mayocat.Module;
import org.mayocat.accounts.AccountsModule;
import org.mayocat.configuration.AbstractSettings;
import org.mayocat.configuration.ExposedSettings;
import org.mayocat.configuration.jackson.NIOModule;
import org.mayocat.configuration.jackson.TimeZoneModule;
import org.mayocat.context.FlashScopeCookieContainerFilter;
import org.mayocat.context.SessionScopeCookieContainerFilter;
import org.mayocat.event.ApplicationStartedEvent;
import org.mayocat.event.EventListener;
import org.mayocat.health.HealthCheck;
import org.mayocat.internal.meta.DefaultEntityMetaRegistry;
import org.mayocat.jackson.PermissiveFuzzyEnumModule;
import org.mayocat.jersey.MayocatFullContextRequestFilter;
import org.mayocat.lifecycle.Managed;
import org.mayocat.localization.LocalizationContainerFilter;
import org.mayocat.meta.EntityMeta;
import org.mayocat.meta.EntityMetaRegistry;
import org.mayocat.multitenancy.MultitenancyContainerFilter;
import org.mayocat.rest.Provider;
import org.mayocat.rest.Resource;
import org.mayocat.rest.jackson.MayocatGroovyModule;
import org.mayocat.rest.jackson.MayocatJodaModule;
import org.mayocat.rest.jackson.MayocatLocaleBCP47LanguageTagModule;
import org.mayocat.rest.jersey.CorsResponseFilter;
import org.mayocat.servlet.ServletFilter;
import org.mayocat.task.Task;
import org.mayocat.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xwiki.component.descriptor.DefaultComponentDescriptor;
import org.xwiki.component.embed.EmbeddableComponentManager;
import org.xwiki.component.manager.ComponentLookupException;
import org.xwiki.component.manager.ComponentManager;
import org.xwiki.observation.ObservationManager;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.fasterxml.jackson.datatype.guava.GuavaModule;
import com.fasterxml.jackson.datatype.joda.JodaModule;
import com.fasterxml.jackson.module.afterburner.AfterburnerModule;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.core.ResourceConfig;

import io.dropwizard.Application;
import io.dropwizard.client.JerseyClientBuilder;
import io.dropwizard.jackson.GuavaExtrasModule;
import io.dropwizard.jackson.LogbackModule;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;

/**
 * @version $Id: 8e7aa259e34077ade733c679d0af55ee6e49cd2c $
 */
public abstract class AbstractService<C extends AbstractSettings> extends Application<C> {
    protected static Set<String> staticPaths = new HashSet<>();

    private static final Logger LOGGER = (Logger) LoggerFactory.getLogger(AbstractService.class);

    protected abstract void registerComponents(C configuration, Environment environment);

    private EmbeddableComponentManager componentManager;

    private Map<String, Module> modules = Maps.newHashMap();

    private List<Class> requestFilters = Lists.newArrayList();

    private List<Class> responseFilters = Lists.newArrayList();

    private ObjectMapper objectMapper;

    public static Set<String> getStaticPaths() {
        return staticPaths;
    }

    @Override
    public void initialize(Bootstrap<C> bootstrap) {
        this.addModule(new AccountsModule());

        bootstrap.getObjectMapper().registerModule(new NIOModule());
        bootstrap.getObjectMapper().registerModule(new MayocatJodaModule());
        bootstrap.getObjectMapper().registerModule(new MayocatLocaleBCP47LanguageTagModule());
        bootstrap.getObjectMapper().registerModule(new TimeZoneModule());

        bootstrap.getObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

        // TODO: remove when upgrading DW to 0.8
        bootstrap.getObjectMapper().registerModule(new PermissiveFuzzyEnumModule());
    }

    @Override
    public void run(C configuration, Environment environment) throws Exception {
        configureObjectMapper();

        this.initializeComponentManager(configuration, environment);
        registerServletFilters(environment);
        registerProviders(environment);
        registerResources(environment);
        registerEventListeners(environment);
        registerHealthChecks(environment);
        registerTasks(environment);
        registerManagedServices(environment);

        // Default Jersey filters
        addRequestFilter(SessionScopeCookieContainerFilter.class);
        addRequestFilter(FlashScopeCookieContainerFilter.class);
        addRequestFilter(LocalizationContainerFilter.class);
        addRequestFilter(MultitenancyContainerFilter.class);
        addResponseFilter(SessionScopeCookieContainerFilter.class);
        addResponseFilter(FlashScopeCookieContainerFilter.class);
        addResponseFilter(CorsResponseFilter.class);
        addResponseFilter(MayocatFullContextRequestFilter.class);

        // Register Jersey container response filters
        Map<String, Object> jerseyPropertiesAndFeatures = Maps.newHashMap();
        jerseyPropertiesAndFeatures.put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
                Joiner.on(",").join(Collections2.transform(this.requestFilters, new Function<Class, String>() {
                    public String apply(Class string) {
                        return string.getCanonicalName();
                    }
                })));
        jerseyPropertiesAndFeatures.put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
                Joiner.on(",").join(Collections2.transform(this.responseFilters, new Function<Class, String>() {
                    public String apply(Class string) {
                        return string.getCanonicalName();
                    }
                })));

        // Register Jersey container request filters
        environment.jersey().getResourceConfig().setPropertiesAndFeatures(jerseyPropertiesAndFeatures);

        ObservationManager observationManager = getComponentManager().getInstance(ObservationManager.class);
        observationManager.notify(new ApplicationStartedEvent(), this);
    }

    public final void addRequestFilter(Class clazz) {
        this.requestFilters.add(clazz);
    }

    public final void addResponseFilter(Class clazz) {
        this.responseFilters.add(clazz);
    }

    protected void configureObjectMapper() {
        // Initialize our own object mapper. We don't want to use Dropwizard's one (environment.getObjectMapper) because
        // we don't have full control over its initialization, and we don't necessarily want mayocat's one to be
        // configured identically as the one used by DW.

        objectMapper = new ObjectMapper(new YAMLFactory());
        // Standard modules
        objectMapper.registerModule(new GuavaModule());
        objectMapper.registerModule(new JodaModule());
        objectMapper.registerModule(new AfterburnerModule());
        // Dropwizard modules
        objectMapper.registerModule(new GuavaExtrasModule());
        objectMapper.registerModule(new LogbackModule());
        // Mayocat modules
        objectMapper.registerModule(new TimeZoneModule());
        objectMapper.registerModule(new NIOModule());
        objectMapper.registerModule(new MayocatJodaModule());
        objectMapper.registerModule(new MayocatLocaleBCP47LanguageTagModule());
        objectMapper.registerModule(new MayocatGroovyModule());

        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    protected void addModule(Module module) {
        if (this.modules.containsKey(module.getName())) {
            LOGGER.error("Module with name [" + module.getName() + "] already exists. Refusing to start.");
            System.exit(1);
        }
        this.modules.put(module.getName(), module);
    }

    protected ComponentManager getComponentManager() {
        return this.componentManager;
    }

    protected void initializeComponentManager(C configuration, Environment environment) {
        componentManager = new EmbeddableComponentManager();

        this.registerSettingsAsComponents(configuration);
        this.registerObjectMapper(objectMapper);
        this.registerJerseyClient(configuration, environment);
        this.registerEntityMetaRegistryAsComponent();
        this.registerComponents(configuration, environment);

        componentManager.initialize(this.getClass().getClassLoader());

        Utils.setComponentManager(componentManager);
    }

    private void registerServletFilters(Environment environment) throws ComponentLookupException {
        Map<String, ServletFilter> servletFilters = componentManager.getInstanceMap(ServletFilter.class);
        for (Map.Entry<String, ServletFilter> filter : servletFilters.entrySet()) {
            if (!Filter.class.isAssignableFrom(filter.getValue().getClass())) {
                LOGGER.warn("Ignoring servlet filter of class {} which does not implement Filter");
            } else {
                environment.servlets()
                        .addFilter(filter.getValue().getClass().getSimpleName(), (Filter) filter.getValue())
                        .addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), true,
                                filter.getValue().urlPattern());
            }
        }
    }

    private void registerManagedServices(Environment environment) throws ComponentLookupException {
        // Managed services that show a managed lifecycle
        Map<String, Managed> managedServices = componentManager.getInstanceMap(Managed.class);
        for (Map.Entry<String, Managed> managed : managedServices.entrySet()) {
            environment.lifecycle().manage(managed.getValue());
        }
    }

    private void registerTasks(Environment environment) throws ComponentLookupException {
        // Registering tasks implementations against the environment
        Map<String, Task> tasks = componentManager.getInstanceMap(Task.class);
        for (Map.Entry<String, Task> task : tasks.entrySet()) {
            if (io.dropwizard.servlets.tasks.Task.class.isAssignableFrom(task.getValue().getClass())) {
                environment.admin().addTask((io.dropwizard.servlets.tasks.Task) task.getValue());
            }
        }
    }

    private void registerHealthChecks(Environment environment) throws ComponentLookupException {
        // Registering health checks implementations against the environment
        Map<String, HealthCheck> healthChecks = componentManager.getInstanceMap(HealthCheck.class);
        for (Map.Entry<String, HealthCheck> check : healthChecks.entrySet()) {
            if (com.codahale.metrics.health.HealthCheck.class.isAssignableFrom(check.getValue().getClass())) {
                environment.healthChecks().register(check.getKey(),
                        (com.codahale.metrics.health.HealthCheck) check.getValue());
            }
        }
    }

    private void registerEventListeners(Environment environment) throws ComponentLookupException {
        // Registering revent listeners implementations against the environment
        Map<String, EventListener> eventListeners = componentManager.getInstanceMap(EventListener.class);
        for (Map.Entry<String, EventListener> listener : eventListeners.entrySet()) {
            environment.servlets().addServletListeners(listener.getValue());
        }
    }

    private void registerResources(Environment environment) throws ComponentLookupException {
        // Registering resources component implementations against the environment...
        Map<String, Resource> restResources = componentManager.getInstanceMap(Resource.class);
        for (Map.Entry<String, Resource> resource : restResources.entrySet()) {
            environment.jersey().register(resource.getValue());
        }
    }

    private void registerProviders(Environment environment) throws ComponentLookupException {
        // Registering provider component implementations against the environment...
        Map<String, Resource> providers = componentManager.getInstanceMap(Provider.class);
        for (Map.Entry<String, Resource> provider : providers.entrySet()) {
            environment.jersey().register(provider.getValue());
        }
    }

    private void registerEntityMetaRegistryAsComponent() {
        Map<String, EntityMeta> entities = Maps.newHashMap();

        for (Module module : modules.values()) {
            for (EntityMeta meta : module.getEntities()) {
                if (entities.containsKey(meta.getEntityName())) {
                    LOGGER.error("Entity with name [" + meta.getEntityName()
                            + "] already registered. Refusing to start.");
                    System.exit(1);
                }
                entities.put(meta.getEntityName(), meta);
            }
        }

        EntityMetaRegistry registry = new DefaultEntityMetaRegistry(new ArrayList(entities.values()));

        DefaultComponentDescriptor<EntityMetaRegistry> cd = new DefaultComponentDescriptor<EntityMetaRegistry>();
        cd.setRoleType(EntityMetaRegistry.class);

        componentManager.registerComponent(cd, registry);
    }

    private void registerJerseyClient(AbstractSettings settings, Environment environment) {
        final Client client = new JerseyClientBuilder(environment).using(settings.getJerseyClientConfiguration())
                .build(getName());
        DefaultComponentDescriptor<Client> cd = new DefaultComponentDescriptor<Client>();
        cd.setRoleType(Client.class);
        componentManager.registerComponent(cd, client);
    }

    private void registerObjectMapper(ObjectMapper mapper) {
        DefaultComponentDescriptor<ObjectMapper> cd = new DefaultComponentDescriptor<ObjectMapper>();
        cd.setRoleType(ObjectMapper.class);
        componentManager.registerComponent(cd, mapper);
    }

    private void registerSettingsAsComponents(C settings) {
        List<Field> settingsFields = getAllFields(settings.getClass());
        for (Field field : settingsFields) {
            boolean isAccessible = field.isAccessible();
            try {
                try {
                    field.setAccessible(true);
                    Object value = field.get(settings);

                    // Inject "as is" for components that only need an individual settings
                    DefaultComponentDescriptor cd = new DefaultComponentDescriptor();
                    cd.setRoleType(value.getClass());
                    componentManager.registerComponent(cd, value);

                    if (ExposedSettings.class.isAssignableFrom(value.getClass())) {

                        // Inject as settings
                        ExposedSettings exposedSettings = (ExposedSettings) value;
                        DefaultComponentDescriptor cd2 = new DefaultComponentDescriptor();
                        cd2.setRoleType(ExposedSettings.class);
                        cd2.setRoleHint(exposedSettings.getKey());
                        componentManager.registerComponent(cd2, value);
                    }
                } finally {
                    field.setAccessible(isAccessible);
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        DefaultComponentDescriptor<C> cd = new DefaultComponentDescriptor<C>();
        cd.setRoleType(settings.getClass());
        componentManager.registerComponent(cd, settings);
    }

    private static List<Field> getAllFields(Class<?> type) {
        List<Field> fields = new ArrayList<Field>();

        fields.addAll(Arrays.asList(type.getDeclaredFields()));

        if (type.getSuperclass() != null) {
            fields.addAll(getAllFields(type.getSuperclass()));
        }

        return fields;
    }
}