io.soabase.core.SoaBundle.java Source code

Java tutorial

Introduction

Here is the source code for io.soabase.core.SoaBundle.java

Source

/**
 * Copyright 2014 Jordan Zimmerman
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.soabase.core;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.Metric;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import io.dropwizard.Configuration;
import io.dropwizard.ConfiguredBundle;
import io.dropwizard.jersey.DropwizardResourceConfig;
import io.dropwizard.jersey.jackson.JacksonMessageBodyProvider;
import io.dropwizard.jersey.setup.JerseyContainerHolder;
import io.dropwizard.jersey.setup.JerseyEnvironment;
import io.dropwizard.jetty.ConnectorFactory;
import io.dropwizard.jetty.HttpConnectorFactory;
import io.dropwizard.jetty.setup.ServletEnvironment;
import io.dropwizard.lifecycle.Managed;
import io.dropwizard.logging.AppenderFactory;
import io.dropwizard.logging.FileAppenderFactory;
import io.dropwizard.server.DefaultServerFactory;
import io.dropwizard.server.ServerFactory;
import io.dropwizard.server.SimpleServerFactory;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.soabase.core.config.ComposedConfigurationAccessor;
import io.soabase.core.features.ExecutorBuilder;
import io.soabase.core.features.attributes.SafeDynamicAttributes;
import io.soabase.core.features.attributes.SoaDynamicAttributes;
import io.soabase.core.features.attributes.SoaWritableDynamicAttributes;
import io.soabase.core.features.client.SoaClientFilter;
import io.soabase.core.features.discovery.HealthCheckIntegration;
import io.soabase.core.features.discovery.SafeSoaDiscovery;
import io.soabase.core.features.discovery.SoaDiscovery;
import io.soabase.core.features.discovery.SoaDiscoveryHealth;
import io.soabase.core.features.discovery.SoaExtendedDiscovery;
import io.soabase.core.features.logging.LoggingReader;
import io.soabase.core.rest.DiscoveryApis;
import io.soabase.core.rest.DynamicAttributeApis;
import io.soabase.core.rest.LoggingApis;
import io.soabase.core.rest.SoaApis;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.servlet.ServletContainer;
import javax.management.AttributeList;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * This is the main integration point for Soabase. This is a required bundle. In
 * general, you should add this bundle before any other Soabase bundle. However, the
 * Service Discovery and Dynamic Attributes bundles must be added before this one.
 *
 * @param <T> your application's configuration type
 */
public class SoaBundle<T extends Configuration> implements ConfiguredBundle<T> {
    private static final boolean hasAdminKey;

    static {
        boolean localHasAdminKey = false;
        try {
            // presence of this key allows mutable attributes
            Class.forName("io.soabase.admin.details.SoaAdminKey");
            localHasAdminKey = true;
        } catch (ClassNotFoundException e) {
            // ignore
        }
        hasAdminKey = localHasAdminKey;
    }

    @Override
    public void initialize(Bootstrap<?> bootstrap) {
    }

    /**
     * Return the SoaFeatures instance. Note: the instance is also
     * registered in Jersey's dependency injection framework.
     *
     * @param environment Dropwizard environment
     * @return SoaFeatures instance
     */
    public static SoaFeatures getFeatures(Environment environment) {
        SoaFeatures features = (SoaFeatures) environment.getApplicationContext()
                .getAttribute(SoaFeatures.class.getName());
        if (features == null) {
            features = new SoaFeaturesImpl(); // temp version so that named values can be set
            environment.getApplicationContext().setAttribute(SoaFeatures.class.getName(), features);
        }
        return features;
    }

    @Override
    public void run(final T configuration, final Environment environment) throws Exception {
        final SoaConfiguration soaConfiguration = ComposedConfigurationAccessor.access(configuration, environment,
                SoaConfiguration.class);

        environment.servlets().addFilter("SoaClientFilter", SoaClientFilter.class)
                .addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");

        updateInstanceName(soaConfiguration);
        List<String> scopes = Lists.newArrayList();
        scopes.add(soaConfiguration.getInstanceName());
        scopes.add(soaConfiguration.getServiceName());
        scopes.addAll(soaConfiguration.getScopes());

        Ports ports = getPorts(configuration);
        final SoaInfo soaInfo = new SoaInfo(scopes, ports.mainPort, ports.adminPort,
                soaConfiguration.getServiceName(), soaConfiguration.getInstanceName(),
                soaConfiguration.isRegisterInDiscovery());

        SoaDiscovery discovery = wrapDiscovery(
                checkManaged(environment, soaConfiguration.getDiscoveryFactory().build(environment, soaInfo)));
        SoaDynamicAttributes attributes = wrapAttributes(
                checkManaged(environment, soaConfiguration.getAttributesFactory().build(environment, scopes)));
        LoggingReader loggingReader = initLogging(configuration);

        final SoaFeaturesImpl features = new SoaFeaturesImpl(discovery, attributes, soaInfo,
                new ExecutorBuilder(environment.lifecycle()), loggingReader);
        AbstractBinder binder = new AbstractBinder() {
            @Override
            protected void configure() {
                bind(features).to(SoaFeatures.class);
                bind(environment.healthChecks()).to(HealthCheckRegistry.class);
                bind(environment.getObjectMapper()).to(ObjectMapper.class);
                bind(environment.metrics()).to(MetricRegistry.class);
            }
        };
        setFeaturesInContext(environment, features);

        checkCorsFilter(soaConfiguration, environment.servlets());
        initJerseyAdmin(soaConfiguration, features, ports, environment, binder);

        startDiscoveryHealth(discovery, soaConfiguration, environment);

        environment.jersey().register(binder);

        addMetrics(environment);
    }

    private void setFeaturesInContext(Environment environment, SoaFeaturesImpl features) {
        SoaFeaturesImpl tempFeatures = (SoaFeaturesImpl) environment.getApplicationContext()
                .getAttribute(SoaFeatures.class.getName());
        if (tempFeatures != null) {
            features.setNamed(tempFeatures);
        }
        environment.getApplicationContext().setAttribute(SoaFeatures.class.getName(), features);
    }

    private SoaDiscovery wrapDiscovery(SoaDiscovery discovery) {
        if ((discovery instanceof SoaExtendedDiscovery) && !hasAdminKey) {
            return new SafeSoaDiscovery(discovery);
        }
        return discovery;
    }

    private SoaDynamicAttributes wrapAttributes(SoaDynamicAttributes attributes) {
        if ((attributes instanceof SoaWritableDynamicAttributes) && !hasAdminKey) {
            return new SafeDynamicAttributes(attributes);
        }
        return attributes;
    }

    private static class Ports {
        final int mainPort;
        final int adminPort;

        Ports(int mainPort, int adminPort) {
            this.mainPort = mainPort;
            this.adminPort = adminPort;
        }
    }

    private Ports getPorts(Configuration configuration) {
        if (SoaMainPortAccessor.class.isAssignableFrom(configuration.getClass())) {
            SoaMainPortAccessor accessor = (SoaMainPortAccessor) configuration;
            return new Ports(accessor.getMainPort(configuration), accessor.getAdminPort(configuration));
        }

        ServerFactory serverFactory = configuration.getServerFactory();
        if (SoaMainPortAccessor.class.isAssignableFrom(serverFactory.getClass())) {
            SoaMainPortAccessor accessor = (SoaMainPortAccessor) serverFactory;
            return new Ports(accessor.getMainPort(configuration), accessor.getAdminPort(configuration));
        }

        int mainPort = 0;
        int adminPort = 0;
        if (DefaultServerFactory.class.isAssignableFrom(serverFactory.getClass())) {
            mainPort = portFromConnectorFactories(
                    ((DefaultServerFactory) serverFactory).getApplicationConnectors());
            adminPort = portFromConnectorFactories(((DefaultServerFactory) serverFactory).getAdminConnectors());
        }

        if (SimpleServerFactory.class.isAssignableFrom(serverFactory.getClass())) {
            mainPort = adminPort = portFromConnectorFactory(((SimpleServerFactory) serverFactory).getConnector());
        }

        if ((mainPort == 0) && (adminPort == 0)) {
            throw new RuntimeException("Cannot determine the main server ports");
        }
        return new Ports(mainPort, adminPort);
    }

    private int portFromConnectorFactories(List<ConnectorFactory> applicationConnectors) {
        if (applicationConnectors.size() > 0) {
            return portFromConnectorFactory(applicationConnectors.get(0));
        }
        return 0;
    }

    private int portFromConnectorFactory(ConnectorFactory connectorFactory) {
        if (HttpConnectorFactory.class.isAssignableFrom(connectorFactory.getClass())) {
            HttpConnectorFactory factory = (HttpConnectorFactory) connectorFactory;
            return factory.getPort();
        }
        return 0;
    }

    private void startDiscoveryHealth(SoaDiscovery discovery, SoaConfiguration soaConfiguration,
            Environment environment) {
        SoaDiscoveryHealth discoveryHealth = checkManaged(environment,
                soaConfiguration.getDiscoveryHealthFactory().build(soaConfiguration, environment));
        ScheduledExecutorService service = environment.lifecycle()
                .scheduledExecutorService("DiscoveryHealthChecker-%d").build();
        service.scheduleAtFixedRate(
                new HealthCheckIntegration(environment.healthChecks(), discovery, discoveryHealth),
                soaConfiguration.getDiscoveryHealthCheckPeriodMs(),
                soaConfiguration.getDiscoveryHealthCheckPeriodMs(), TimeUnit.MILLISECONDS);
    }

    private void checkCorsFilter(SoaConfiguration configuration, ServletEnvironment servlets) {
        if (configuration.isAddCorsFilter()) {
            // from http://jitterted.com/tidbits/2014/09/12/cors-for-dropwizard-0-7-x/

            FilterRegistration.Dynamic filter = servlets.addFilter("CORS", CrossOriginFilter.class);
            filter.addMappingForUrlPatterns(EnumSet.allOf(DispatcherType.class), true, "/*");
            filter.setInitParameter(CrossOriginFilter.ALLOWED_METHODS_PARAM, "GET,PUT,POST,DELETE,OPTIONS");
            filter.setInitParameter(CrossOriginFilter.ALLOWED_ORIGINS_PARAM, "*");
            filter.setInitParameter(CrossOriginFilter.ACCESS_CONTROL_ALLOW_ORIGIN_HEADER, "*");
            filter.setInitParameter("allowedHeaders",
                    "Content-Type,Authorization,X-Requested-With,Content-Length,Accept,Origin");
            filter.setInitParameter("allowCredentials", "true");
        }
    }

    private void updateInstanceName(SoaConfiguration configuration) throws UnknownHostException {
        if (configuration.getInstanceName() == null) {
            configuration.setInstanceName(InetAddress.getLocalHost().getHostName());
        }
    }

    private static <T> T checkManaged(Environment environment, T obj) {
        if (obj instanceof Managed) {
            environment.lifecycle().manage((Managed) obj);
        }
        return obj;
    }

    private void initJerseyAdmin(SoaConfiguration configuration, SoaFeaturesImpl features, Ports ports,
            Environment environment, AbstractBinder binder) {
        if ((configuration.getAdminJerseyPath() == null) || (ports.adminPort == 0)) {
            return;
        }

        String jerseyRootPath = configuration.getAdminJerseyPath();
        if (!jerseyRootPath.endsWith("/*")) {
            if (jerseyRootPath.endsWith("/")) {
                jerseyRootPath += "*";
            } else {
                jerseyRootPath += "/*";
            }
        }

        DropwizardResourceConfig jerseyConfig = new DropwizardResourceConfig(environment.metrics());
        JerseyContainerHolder jerseyServletContainer = new JerseyContainerHolder(
                new ServletContainer(jerseyConfig));
        environment.admin().addServlet("soa-admin-jersey", jerseyServletContainer.getContainer())
                .addMapping(jerseyRootPath);

        JerseyEnvironment jerseyEnvironment = new JerseyEnvironment(jerseyServletContainer, jerseyConfig);
        features.putNamed(jerseyEnvironment, JerseyEnvironment.class, SoaFeatures.ADMIN_NAME);
        jerseyEnvironment.register(SoaApis.class);
        jerseyEnvironment.register(DiscoveryApis.class);
        jerseyEnvironment.register(DynamicAttributeApis.class);
        jerseyEnvironment.register(LoggingApis.class);
        jerseyEnvironment.register(binder);
        jerseyEnvironment.setUrlPattern(jerseyConfig.getUrlPattern());
        jerseyEnvironment.register(
                new JacksonMessageBodyProvider(environment.getObjectMapper(), environment.getValidator()));

        checkCorsFilter(configuration, environment.admin());
    }

    private LoggingReader initLogging(Configuration configuration) throws IOException {
        Set<File> mainFiles = Sets.newHashSet();
        Set<File> archiveDirectories = Sets.newHashSet();
        for (AppenderFactory appenderFactory : configuration.getLoggingFactory().getAppenders()) {
            if (appenderFactory instanceof FileAppenderFactory) {
                FileAppenderFactory fileAppenderFactory = (FileAppenderFactory) appenderFactory;
                if (fileAppenderFactory.getCurrentLogFilename() != null) {
                    mainFiles.add(new File(fileAppenderFactory.getCurrentLogFilename()).getCanonicalFile());
                }

                if (fileAppenderFactory.getArchivedLogFilenamePattern() != null) {
                    File archive = new File(fileAppenderFactory.getArchivedLogFilenamePattern()).getParentFile()
                            .getCanonicalFile();
                    archiveDirectories.add(archive);
                }
            }
        }

        // TODO log if no main files or archives

        return new LoggingReader(mainFiles, archiveDirectories);
    }

    private void addMetrics(Environment environment) {
        Metric metric = new Gauge<Double>() {
            private double lastValue = 0.0;

            @Override
            public Double getValue() {
                try {
                    MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
                    ObjectName name = ObjectName.getInstance("java.lang:type=OperatingSystem");
                    AttributeList list = mbs.getAttributes(name, new String[] { "SystemCpuLoad" });
                    if ((list != null) && (list.size() > 0)) {
                        // unfortunately, this bean reports bad values occasionally. Filter them out.
                        Object value = list.asList().get(0).getValue();
                        double d = (value instanceof Number) ? ((Number) value).doubleValue() : 0.0;
                        d = ((d > 0.0) && (d < 1.0)) ? d : lastValue;
                        lastValue = d;
                        return d;
                    }
                } catch (Exception ignore) {
                    // ignore
                }
                return lastValue;
            }
        };
        environment.metrics().register("system.cpu.load", metric);
    }
}