diff --git a/README.md b/README.md index 25e89a1..1c52f33 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ entirely with spring, and tell the spring service which components to enable usi With **dropwizard-spring** it is not necessary to subclass `com.yammer.dropwizard.Service`, instead you reference the provided `com.hmsonline.dropwizard.spring.SpringService` class as your service class. +With **dropwizard-spring** 0.7+ it is possible to use your own `com.yammer.dropwizard.Service` (Application in dropwizard 0.7) +and use the provided `com.hmsonline.dropwizard.spring.SpringBundle` class by adding `bootstrap.addBundle(new SpringBundle());` +to the `initialize` method of your dropwizard Service/Application class. This allows you to subclass the SpringServiceConfiguration +dropwizard Configuration object to add your own configuration settings, and also introduce your own logic into your +dropwizard Service/Application. ## Maven Configuration @@ -108,6 +113,34 @@ This is required to have maven build a "fat," executable jar file. configLocations: - conf/dropwizard-beans.xml + # Beans to be created from the configuration provided + # These will be constructed *before* the configLocations are parsed allowing their values to be used. + # + # Example below reflects this equivalent spring XML config: + # + # + # + # + # + # + # + # + # + # + beans: + uniqueBeanNameHere: + clazz: class.to.Construct + config: + key1: "value1" + key2: 1234 + key3: true + remoteApi: + clazz: com.myapp.config.RemoteAPI + config: + url: "http://api.domain.com/rest/v2/" + username: "myuser" + password: "some-password-hash" + # Servlet Filter # List of FilterConfiguration filters: diff --git a/pom.xml b/pom.xml index 35e416c..f328be9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.hmsonline dropwizard-spring - 0.6.2-SNAPSHOT + 0.7.0-SNAPSHOT jar Spring Integration for DropWizard https://github.com/hmsonline/dropwizard-spring @@ -30,6 +30,7 @@ UTF-8 0.7.0 3.2.8.RELEASE + 1.7.0 @@ -67,6 +68,11 @@ spring-web ${spring.version} + + commons-beanutils + commons-beanutils + ${beanutils.version} + junit junit diff --git a/src/main/java/com/hmsonline/dropwizard/spring/BeanConfiguration.java b/src/main/java/com/hmsonline/dropwizard/spring/BeanConfiguration.java new file mode 100644 index 0000000..c8e6cfe --- /dev/null +++ b/src/main/java/com/hmsonline/dropwizard/spring/BeanConfiguration.java @@ -0,0 +1,31 @@ +// Copyright (c) 2015 Hardiker Ltd. +package com.hmsonline.dropwizard.spring; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class BeanConfiguration { + + @JsonProperty + private String clazz; // Filter class. + + @JsonProperty + private Map config; // Init params. + + public String getClazz() { + return clazz; + } + + public void setClazz(String clazz) { + this.clazz = clazz; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} diff --git a/src/main/java/com/hmsonline/dropwizard/spring/SpringBundle.java b/src/main/java/com/hmsonline/dropwizard/spring/SpringBundle.java new file mode 100644 index 0000000..bddf71f --- /dev/null +++ b/src/main/java/com/hmsonline/dropwizard/spring/SpringBundle.java @@ -0,0 +1,119 @@ +// Copyright (c) 2015 Hardiker Ltd. +package com.hmsonline.dropwizard.spring; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.hmsonline.dropwizard.spring.web.XmlRestWebApplicationContext; +import io.dropwizard.ConfiguredBundle; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; +import org.springframework.context.ApplicationContext; + +import java.util.List; + + +public class SpringBundle implements ConfiguredBundle { + + SpringService service = new SpringService(); + + @Override + public void initialize(Bootstrap bootstrap) { + // This is needed to avoid an exception when deserializing Json to an ArrayList + bootstrap.getObjectMapper().enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY); + } + + @Override + public void run(SpringServiceConfiguration configuration, Environment environment) { + SpringConfiguration config = configuration.getSpring(); + + ApplicationContext parentCtx = initSpringParent(); + + Dropwizard dw = (Dropwizard) parentCtx.getBean("dropwizard"); + dw.setConfiguration(configuration); + dw.setEnvironment(environment); + + parentCtx = wrapApplicationContext(parentCtx, config); + parentCtx = initSpringConfigBasedBeans(parentCtx, config); + + ApplicationContext appCtx = initSpring(config, parentCtx); + loadResourceBeans(config.getResources(), appCtx, environment); + loadHealthCheckBeans(config.getHealthChecks(), appCtx, environment); + loadManagedBeans(config.getManaged(), appCtx, environment); + loadLifeCycleBeans(config.getLifeCycles(), appCtx, environment); + loadJerseyProviders(config.getJerseyProviders(), appCtx, environment); + loadTasks(config.getTasks(), appCtx, environment); + + // Load filter or listeners for WebApplicationContext. + if (appCtx instanceof XmlRestWebApplicationContext) { + try { + loadWebConfigs(environment, config, appCtx); + } catch (ClassNotFoundException e) { + throw new RuntimeException("CNFE when loading spring web configs: " + e.getMessage(), e); + } + } + + enableJerseyFeatures(config.getEnabledJerseyFeatures(), environment); + disableJerseyFeatures(config.getDisabledJerseyFeatures(), environment); + } + + /** + * This allows you to wrap the ApplicationContext, potentially with another ApplicationContext to extend the base + * functionality to meet your needs. Don't forget to refresh any new ApplicationContext's you create! + * + * @param parent Root Application Context + * @param config SpringConfiguration object for reference + * @return Application Context for further use + */ + @SuppressWarnings("unused") + protected ApplicationContext wrapApplicationContext(ApplicationContext parent, SpringConfiguration config) { + return parent; + } + + void loadWebConfigs(Environment environment, SpringConfiguration config, ApplicationContext appCtx) throws ClassNotFoundException { + service.loadWebConfigs(environment, config, appCtx); + } + + void loadResourceBeans(List resources, ApplicationContext ctx, Environment env) { + service.loadResourceBeans(resources, ctx, env); + } + + void loadHealthCheckBeans(List healthChecks, ApplicationContext ctx, Environment env) { + service.loadHealthCheckBeans(healthChecks, ctx, env); + } + + void loadManagedBeans(List manageds, ApplicationContext ctx, Environment env) { + service.loadManagedBeans(manageds, ctx, env); + } + + void loadLifeCycleBeans(List lifeCycles, ApplicationContext ctx, Environment env) { + service.loadLifeCycleBeans(lifeCycles, ctx, env); + } + + void loadJerseyProviders(List providers, ApplicationContext ctx, Environment env) { + service.loadJerseyProviders(providers, ctx, env); + } + + void loadTasks(List tasks, ApplicationContext ctx, Environment env) { + service.loadTasks(tasks, ctx, env); + } + + void enableJerseyFeatures(List features, Environment env) { + service.enableJerseyFeatures(features, env); + } + + void disableJerseyFeatures(List features, Environment env) { + service.disableJerseyFeatures(features, env); + } + + ApplicationContext initSpringParent() { + return service.initSpringParent(); + } + + ApplicationContext initSpringConfigBasedBeans(ApplicationContext parent, SpringConfiguration springConfiguration) { + return service.initSpringConfigBasedBeans(parent, springConfiguration); + } + + ApplicationContext initSpring(SpringConfiguration config, ApplicationContext parent) { + return service.initSpring(config, parent); + } + +} \ No newline at end of file diff --git a/src/main/java/com/hmsonline/dropwizard/spring/SpringConfiguration.java b/src/main/java/com/hmsonline/dropwizard/spring/SpringConfiguration.java index 2d9a542..11b4150 100644 --- a/src/main/java/com/hmsonline/dropwizard/spring/SpringConfiguration.java +++ b/src/main/java/com/hmsonline/dropwizard/spring/SpringConfiguration.java @@ -1,4 +1,5 @@ // Copyright (c) 2012 Health Market Science, Inc. +// Extended by Hardiker Ltd package com.hmsonline.dropwizard.spring; @@ -35,6 +36,9 @@ public class SpringConfiguration extends Configuration { @JsonProperty private List resources; + @JsonProperty + private Map beans; + @JsonProperty private List healthChecks; @@ -75,6 +79,10 @@ public List getResources() { return resources; } + public Map getBeans() { + return beans; + } + public List getHealthChecks() { return healthChecks; } diff --git a/src/main/java/com/hmsonline/dropwizard/spring/SpringService.java b/src/main/java/com/hmsonline/dropwizard/spring/SpringService.java index 42e4366..759bf16 100644 --- a/src/main/java/com/hmsonline/dropwizard/spring/SpringService.java +++ b/src/main/java/com/hmsonline/dropwizard/spring/SpringService.java @@ -1,4 +1,5 @@ // Copyright (c) 2012 Health Market Science, Inc. +// Extended by Hardiker Ltd package com.hmsonline.dropwizard.spring; import java.text.MessageFormat; @@ -16,6 +17,7 @@ import io.dropwizard.servlets.tasks.Task; import io.dropwizard.setup.Bootstrap; import io.dropwizard.setup.Environment; +import org.apache.commons.beanutils.PropertyUtils; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletHolder; import org.slf4j.Logger; @@ -24,6 +26,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; +import org.springframework.context.support.StaticApplicationContext; import javax.servlet.DispatcherType; import javax.servlet.Filter; @@ -58,6 +61,8 @@ public void run(SpringServiceConfiguration configuration, Environment environmen dw.setConfiguration(configuration); dw.setEnvironment(environment); + parentCtx = initSpringConfigBasedBeans(parentCtx, config); + ApplicationContext appCtx = initSpring(config, parentCtx); loadResourceBeans(config.getResources(), appCtx, environment); loadHealthCheckBeans(config.getHealthChecks(), appCtx, environment); @@ -78,7 +83,7 @@ public void run(SpringServiceConfiguration configuration, Environment environmen /** * Load filter, servlets or listeners for WebApplicationContext. */ - private void loadWebConfigs(Environment environment, SpringConfiguration config, ApplicationContext appCtx) throws ClassNotFoundException { + void loadWebConfigs(Environment environment, SpringConfiguration config, ApplicationContext appCtx) throws ClassNotFoundException { // Load filters. loadFilters(config.getFilters(), environment); @@ -93,7 +98,7 @@ private void loadWebConfigs(Environment environment, SpringConfiguration config, * Load all filters. */ @SuppressWarnings("unchecked") - private void loadFilters(Map filters, Environment environment) throws ClassNotFoundException { + void loadFilters(Map filters, Environment environment) throws ClassNotFoundException { if (filters != null) { for (Map.Entry filterEntry : filters.entrySet()) { FilterConfiguration filter = filterEntry.getValue(); @@ -121,7 +126,7 @@ private void loadFilters(Map filters, Environment e * Load all servlets. */ @SuppressWarnings("unchecked") - private void loadServlets(Map servlets, Environment environment) throws ClassNotFoundException { + void loadServlets(Map servlets, Environment environment) throws ClassNotFoundException { if (servlets != null) { for (Map.Entry servletEntry : servlets.entrySet()) { ServletConfiguration servlet = servletEntry.getValue(); @@ -145,7 +150,7 @@ private void loadServlets(Map servlets, Environmen } } - private void loadResourceBeans(List resources, ApplicationContext ctx, Environment env) { + void loadResourceBeans(List resources, ApplicationContext ctx, Environment env) { if (resources != null) { for (String resource : resources) { try { @@ -158,7 +163,7 @@ private void loadResourceBeans(List resources, ApplicationContext ctx, E } - private void loadHealthCheckBeans(List healthChecks, ApplicationContext ctx, Environment env) { + void loadHealthCheckBeans(List healthChecks, ApplicationContext ctx, Environment env) { if (healthChecks != null) { for (String healthCheck : healthChecks) { try { @@ -171,7 +176,7 @@ private void loadHealthCheckBeans(List healthChecks, ApplicationContext } } - private void loadManagedBeans(List manageds, ApplicationContext ctx, Environment env) { + void loadManagedBeans(List manageds, ApplicationContext ctx, Environment env) { if (manageds != null) { for (String managed : manageds) { try { @@ -183,7 +188,7 @@ private void loadManagedBeans(List manageds, ApplicationContext ctx, Env } } - private void loadLifeCycleBeans(List lifeCycles, ApplicationContext ctx, Environment env) { + void loadLifeCycleBeans(List lifeCycles, ApplicationContext ctx, Environment env) { if (lifeCycles != null) { for (String lifeCycle : lifeCycles) { try { @@ -195,7 +200,7 @@ private void loadLifeCycleBeans(List lifeCycles, ApplicationContext ctx, } } - private void loadJerseyProviders(List providers, ApplicationContext ctx, Environment env) { + void loadJerseyProviders(List providers, ApplicationContext ctx, Environment env) { if (providers != null) { for (String provider : providers) { try { @@ -207,7 +212,7 @@ private void loadJerseyProviders(List providers, ApplicationContext ctx, } } - private void loadTasks(List tasks, ApplicationContext ctx, Environment env) { + void loadTasks(List tasks, ApplicationContext ctx, Environment env) { if (tasks != null) { for (String task : tasks) { try { @@ -219,7 +224,7 @@ private void loadTasks(List tasks, ApplicationContext ctx, Environment e } } - private void enableJerseyFeatures(List features, Environment env) { + void enableJerseyFeatures(List features, Environment env) { if (features != null) { for (String feature : features) { env.jersey().enable(feature); @@ -227,7 +232,7 @@ private void enableJerseyFeatures(List features, Environment env) { } } - private void disableJerseyFeatures(List features, Environment env) { + void disableJerseyFeatures(List features, Environment env) { if (features != null) { for (String feature : features) { env.jersey().disable(feature); @@ -235,13 +240,42 @@ private void disableJerseyFeatures(List features, Environment env) { } } - private ApplicationContext initSpringParent() { + ApplicationContext initSpringParent() { ApplicationContext parent = new ClassPathXmlApplicationContext( new String[]{"dropwizardSpringApplicationContext.xml"}, true); return parent; } - private ApplicationContext initSpring(SpringConfiguration config, ApplicationContext parent) { + ApplicationContext initSpringConfigBasedBeans(ApplicationContext parent, SpringConfiguration springConfiguration) { + StaticApplicationContext child = new StaticApplicationContext(parent); + child.refresh(); + + Map beanConfigs = springConfiguration.getBeans(); + if (beanConfigs != null) { + try { + for (Map.Entry beanEntry : beanConfigs.entrySet()) { + String title = beanEntry.getKey(); + BeanConfiguration beanConfig = beanEntry.getValue(); + + Class clazz = Class.forName(beanConfig.getClazz()); + child.registerSingleton(title, clazz); + + Object bean = child.getBean(title, clazz); + for (Map.Entry entry : beanConfig.getConfig().entrySet()) { + PropertyUtils.setProperty(bean, entry.getKey(), entry.getValue()); + } + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + } + + return child; + } + + ApplicationContext initSpring(SpringConfiguration config, ApplicationContext parent) { ApplicationContext appCtx = null; // Get Application Context Type String ctxType = config.getAppContextType(); @@ -269,7 +303,7 @@ private ApplicationContext initSpring(SpringConfiguration config, ApplicationCon return appCtx; } - private void logNoSuchBeanDefinitionException(NoSuchBeanDefinitionException nsbde) { + void logNoSuchBeanDefinitionException(NoSuchBeanDefinitionException nsbde) { if (LOG.isWarnEnabled()) { LOG.warn("Skipping missing Spring bean: ", nsbde); }