Java tutorial
/* * Copyright 2016 Phaneesh Nagaraja <phaneesh.n@gmail.com>. * * 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.dropwizard.revolver; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.jsontype.NamedType; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.netflix.hystrix.contrib.codahalemetricspublisher.HystrixCodaHaleMetricsPublisher; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import com.netflix.hystrix.strategy.HystrixPlugins; import io.dropwizard.Configuration; import io.dropwizard.ConfiguredBundle; import io.dropwizard.assets.AssetsBundle; import io.dropwizard.msgpack.MsgPackBundle; import io.dropwizard.revolver.aeroapike.AerospikeConnectionManager; import io.dropwizard.revolver.callback.CallbackHandler; import io.dropwizard.revolver.core.RevolverExecutionException; import io.dropwizard.revolver.core.config.AerospikeMailBoxConfig; import io.dropwizard.revolver.core.config.InMemoryMailBoxConfig; import io.dropwizard.revolver.core.config.RevolverConfig; import io.dropwizard.revolver.core.config.RevolverServiceConfig; import io.dropwizard.revolver.discovery.RevolverServiceResolver; import io.dropwizard.revolver.discovery.model.RangerEndpointSpec; import io.dropwizard.revolver.discovery.model.SimpleEndpointSpec; import io.dropwizard.revolver.exception.RevolverExceptionMapper; import io.dropwizard.revolver.exception.TimeoutExceptionMapper; import io.dropwizard.revolver.filters.RevolverRequestFilter; import io.dropwizard.revolver.http.RevolverHttpCommand; import io.dropwizard.revolver.http.auth.BasicAuthConfig; import io.dropwizard.revolver.http.auth.TokenAuthConfig; import io.dropwizard.revolver.http.config.RevolverHttpApiConfig; import io.dropwizard.revolver.http.config.RevolverHttpServiceConfig; import io.dropwizard.revolver.http.config.RevolverHttpsServiceConfig; import io.dropwizard.revolver.http.model.ApiPathMap; import io.dropwizard.revolver.persistence.AeroSpikePersistenceProvider; import io.dropwizard.revolver.persistence.InMemoryPersistenceProvider; import io.dropwizard.revolver.persistence.PersistenceProvider; import io.dropwizard.revolver.resource.RevolverCallbackResource; import io.dropwizard.revolver.resource.RevolverMailboxResource; import io.dropwizard.revolver.resource.RevolverMetadataResource; import io.dropwizard.revolver.resource.RevolverRequestResource; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; import io.dropwizard.xml.XmlBundle; import lombok.extern.slf4j.Slf4j; import lombok.val; import org.apache.curator.framework.CuratorFramework; import org.msgpack.jackson.dataformat.MessagePackFactory; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.regex.Pattern; import java.util.stream.Collectors; /** * @author phaneesh */ @Slf4j public abstract class RevolverBundle<T extends Configuration> implements ConfiguredBundle<T> { private static Map<String, RevolverHttpCommand> httpCommands = new HashMap<>(); private static MultivaluedMap<String, ApiPathMap> serviceToPathMap = new MultivaluedHashMap<>(); public static final ObjectMapper msgPackObjectMapper = new ObjectMapper(new MessagePackFactory()); public static final XmlMapper xmlObjectMapper = new XmlMapper(); private static RevolverServiceResolver serviceNameResolver = null; @Override public void initialize(final Bootstrap<?> bootstrap) { //Reset everything before configuration HystrixPlugins.reset(); registerTypes(bootstrap); configureXmlMapper(); bootstrap.addBundle(new XmlBundle()); bootstrap.addBundle(new MsgPackBundle()); bootstrap.addBundle(new AssetsBundle("/revolver/dashboard/", "/revolver/dashboard/", "index.html")); } @Override public void run(final T configuration, final Environment environment) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException { //Add metrics publisher final HystrixCodaHaleMetricsPublisher metricsPublisher = new HystrixCodaHaleMetricsPublisher( environment.metrics()); HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); initializeRevolver(configuration, environment); final RevolverConfig revolverConfig = getRevolverConfig(configuration); if (Strings.isNullOrEmpty(revolverConfig.getHystrixStreamPath())) { environment.getApplicationContext().addServlet(HystrixMetricsStreamServlet.class, "/hystrix.stream"); } else { environment.getApplicationContext().addServlet(HystrixMetricsStreamServlet.class, revolverConfig.getHystrixStreamPath()); } environment.jersey().register(new RevolverExceptionMapper()); environment.jersey().register(new TimeoutExceptionMapper()); final PersistenceProvider persistenceProvider = getPersistenceProvider(configuration, environment); final CallbackHandler callbackHandler = CallbackHandler.builder().persistenceProvider(persistenceProvider) .revolverConfig(revolverConfig).build(); environment.jersey().register(new RevolverRequestFilter(revolverConfig)); environment.jersey().register(new RevolverRequestResource(environment.getObjectMapper(), msgPackObjectMapper, xmlObjectMapper, persistenceProvider, callbackHandler)); environment.jersey().register(new RevolverCallbackResource(persistenceProvider, callbackHandler)); environment.jersey().register(new RevolverMailboxResource(persistenceProvider)); environment.jersey().register(new RevolverMetadataResource(revolverConfig)); } private void registerTypes(final Bootstrap<?> bootstrap) { bootstrap.getObjectMapper().registerSubtypes(new NamedType(RevolverHttpServiceConfig.class, "http")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(RevolverHttpsServiceConfig.class, "https")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(BasicAuthConfig.class, "basic")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(TokenAuthConfig.class, "token")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(SimpleEndpointSpec.class, "simple")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(RangerEndpointSpec.class, "ranger_sharded")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(InMemoryMailBoxConfig.class, "in_memory")); bootstrap.getObjectMapper().registerSubtypes(new NamedType(AerospikeMailBoxConfig.class, "aerospike")); } private void configureXmlMapper() { xmlObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); xmlObjectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); xmlObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); xmlObjectMapper.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true); xmlObjectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); xmlObjectMapper.configure(ToXmlGenerator.Feature.WRITE_XML_1_1, true); } private static Map<String, RevolverHttpApiConfig> generateApiConfigMap( final RevolverHttpServiceConfig serviceConfiguration) { val tokenMatch = Pattern.compile("\\{(([^/])+\\})"); List<RevolverHttpApiConfig> apis = serviceConfiguration.getApis().stream().collect(Collectors.toList()); Collections.sort(apis, (o1, o2) -> { String o1Expr = generatePathExpression(o1.getPath()); String o2Expr = generatePathExpression(o2.getPath()); return tokenMatch.matcher(o2Expr).groupCount() - tokenMatch.matcher(o1Expr).groupCount(); }); Collections.sort(apis, (o1, o2) -> o1.getPath().compareTo(o2.getPath())); apis.forEach(apiConfig -> serviceToPathMap.add(serviceConfiguration.getService(), ApiPathMap.builder().api(apiConfig).path(generatePathExpression(apiConfig.getPath())).build())); final ImmutableMap.Builder<String, RevolverHttpApiConfig> configMapBuilder = ImmutableMap.builder(); apis.forEach(apiConfig -> configMapBuilder.put(apiConfig.getApi(), apiConfig)); return configMapBuilder.build(); } private static String generatePathExpression(final String path) { return path.replaceAll("\\{(([^/])+\\})", "(([^/])+)"); } public static ApiPathMap matchPath(final String service, final String path) { if (serviceToPathMap.containsKey(service)) { final val apiMap = serviceToPathMap.get(service).stream().filter(api -> path.matches(api.getPath())) .findFirst(); return apiMap.orElse(null); } else { return null; } } public static RevolverHttpCommand getHttpCommand(final String service) { val command = httpCommands.get(service); if (null == command) { throw new RevolverExecutionException(RevolverExecutionException.Type.BAD_REQUEST, "No service spec defined for service: " + service); } return command; } public static RevolverServiceResolver getServiceNameResolver() { return serviceNameResolver; } public abstract RevolverConfig getRevolverConfig(final T configuration); PersistenceProvider getPersistenceProvider(final T configuration, final Environment environment) { final RevolverConfig revolverConfig = getRevolverConfig(configuration); //Default for avoiding no mailbox config NPE if (revolverConfig.getMailBox() == null) { return new InMemoryPersistenceProvider(); } switch (revolverConfig.getMailBox().getType()) { case "in_memory": return new InMemoryPersistenceProvider(); case "aerospike": AerospikeConnectionManager.init((AerospikeMailBoxConfig) revolverConfig.getMailBox()); return new AeroSpikePersistenceProvider((AerospikeMailBoxConfig) revolverConfig.getMailBox(), environment.getObjectMapper()); } throw new IllegalArgumentException("Invalid mailbox configuration"); } public CuratorFramework getCurator() { return null; } private void initializeRevolver(final T configuration, final Environment environment) throws CertificateException, UnrecoverableKeyException, NoSuchAlgorithmException, KeyStoreException, KeyManagementException, IOException { final RevolverConfig revolverConfig = getRevolverConfig(configuration); if (revolverConfig.getServiceResolverConfig() != null) { serviceNameResolver = revolverConfig.getServiceResolverConfig().isUseCurator() ? RevolverServiceResolver.usingCurator().curatorFramework(getCurator()) .objectMapper(environment.getObjectMapper()) .resolverConfig(revolverConfig.getServiceResolverConfig()).build() : RevolverServiceResolver.builder().resolverConfig(revolverConfig.getServiceResolverConfig()) .objectMapper(environment.getObjectMapper()).build(); } else { serviceNameResolver = RevolverServiceResolver.builder().objectMapper(environment.getObjectMapper()) .build(); } for (final RevolverServiceConfig config : revolverConfig.getServices()) { final String type = config.getType(); switch (type) { case "http": registerHttpCommand(revolverConfig, config); break; case "https": registerHttpsCommand(revolverConfig, config); break; default: log.warn("Unsupported Service type: " + type); } } System.out.println( "***************************************************************************************************"); System.out.println("Revolver Service Map"); System.out.println( "***************************************************************************************************"); serviceToPathMap.forEach((k, v) -> { System.out.println("\tService: " + k); v.forEach(a -> a.getApi().getMethods().forEach( b -> System.out.println("\t\t[" + b.name() + "] " + a.getApi().getApi() + ": " + a.getPath()))); }); System.out.println( "***************************************************************************************************"); } private static void registerHttpsCommand(RevolverConfig revolverConfig, RevolverServiceConfig config) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException, UnrecoverableKeyException { final RevolverHttpsServiceConfig httpsConfig = (RevolverHttpsServiceConfig) config; final RevolverHttpServiceConfig revolverHttpServiceConfig = RevolverHttpServiceConfig.builder() .apis(httpsConfig.getApis()).auth(httpsConfig.getAuth()).authEnabled(httpsConfig.isAuthEnabled()) .compression(httpsConfig.isCompression()) .connectionKeepAliveInMillis(httpsConfig.getConnectionKeepAliveInMillis()) .connectionPoolSize(httpsConfig.getConnectionPoolSize()).enpoint(httpsConfig.getEndpoint()) .keystorePassword(httpsConfig.getKeystorePassword()).keyStorePath(httpsConfig.getKeyStorePath()) .secured(true).service(httpsConfig.getService()).trackingHeaders(httpsConfig.isTrackingHeaders()) .type(httpsConfig.getType()).build(); try { httpCommands.put(config.getService(), RevolverHttpCommand.builder().clientConfiguration(revolverConfig.getClientConfig()) .runtimeConfig(revolverConfig.getGlobal()) .serviceConfiguration(revolverHttpServiceConfig) .apiConfigurations(generateApiConfigMap(revolverHttpServiceConfig)) .serviceResolver(serviceNameResolver).traceCollector(trace -> { //TODO: Put in a publisher if required }).build()); } catch (ExecutionException e) { log.error("Error creating http command: {}", config.getService(), e); } } private static void registerHttpCommand(RevolverConfig revolverConfig, RevolverServiceConfig config) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException, UnrecoverableKeyException { final RevolverHttpServiceConfig httpConfig = (RevolverHttpServiceConfig) config; try { httpConfig.setSecured(false); httpCommands.put(config.getService(), RevolverHttpCommand.builder().clientConfiguration(revolverConfig.getClientConfig()) .runtimeConfig(revolverConfig.getGlobal()).serviceConfiguration(httpConfig) .apiConfigurations(generateApiConfigMap(httpConfig)) .serviceResolver(serviceNameResolver).traceCollector(trace -> { //TODO: Put in a publisher if required }).build()); } catch (ExecutionException e) { log.error("Error creating http command: {}", config.getService(), e); } } public static void addHttpCommand(String service, RevolverHttpCommand httpCommand) { httpCommands.put(service, httpCommand); } }