├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main └── java └── com ├── twilio └── wiztowar │ ├── WarAssetsBundle.java │ └── DWAdapter.java └── yammer └── dropwizard └── config └── ExtendedEnvironment.java /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | *.iml 4 | *.class 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Twilio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WizToWar - Have your cake and eat it too 2 | ======================================== 3 | 4 | WizToWar is a simple library that enables a [Dropwizard](http://dropwizard.io) service to also be deployable in a WAR container such as Tomcat. 5 | 6 | By following the steps in the usage section below you will be able to create 7 | both a Dropwizard jar and a WAR of the same service. 8 | 9 | 10 | Caveat emptor: 11 | -------------- 12 | 13 | * Only tested on Tomcat 7 14 | * No support for bundles 15 | * Many features untested 16 | * Goes against the whole philosophy of Dropwizard... 17 | 18 | Usage 19 | ------ 20 | 21 | Include the wiztowar jar as a dependency: 22 | 23 | ```xml 24 | 25 | com.twilio 26 | wiztowar 27 | 1.3 28 | 29 | ``` 30 | 31 | Create a new class for your application like this: 32 | 33 | ```java 34 | package com.twilio.mixerstate; 35 | 36 | import com.google.common.io.Resources; 37 | import com.twilio.wiztowar.DWAdapter; 38 | import com.yammer.dropwizard.Service; 39 | 40 | import java.io.File; 41 | import java.net.URISyntaxException; 42 | import java.net.URL; 43 | 44 | 45 | public class MixerStateDWApplication extends DWAdapter { 46 | final static MixerStateService service = new MixerStateService(); 47 | 48 | /** 49 | * Return the Dropwizard service you want to run. 50 | */ 51 | public Service getSingletonService(){ 52 | return service; 53 | } 54 | 55 | /** 56 | * Return the File where the configuration lives. 57 | */ 58 | @Override 59 | public File getConfigurationFile() { 60 | 61 | URL url = Resources.getResource("mixer-state-server.yml"); 62 | try { 63 | return new File(url.toURI()); 64 | } catch (URISyntaxException e) { 65 | throw new IllegalStateException(e); 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | 72 | Create a main/webapp/WEB-INF/web.xml file: 73 | ------------------------------------------ 74 | 75 | ```xml 76 | 77 | 78 | 79 | 80 | com.twilio.mixerstate.MixerStateDWApplication 81 | 82 | 83 | ``` 84 | 85 | Make sure you also build a WAR artifact 86 | --------------------------------------------- 87 | 88 | There are two alternatives to building a war: 89 | 90 | ### a. Add instructions to also build a WAR 91 | 92 | This goes in `` section: 93 | 94 | ```xml 95 | 96 | org.apache.maven.plugins 97 | maven-war-plugin 98 | 2.4 99 | 100 | 101 | default-war 102 | package 103 | 104 | war 105 | 106 | 107 | 108 | 109 | target/webapp 110 | 111 | 112 | ``` 113 | 114 | ### b. Change packaging of your Dropwizard service 115 | 116 | If you do not intend to run the Dropwizard service standalone, you can simply 117 | change the "packaging" element in pom.xml to be "war" instead of "jar". 118 | 119 | 120 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.twilio 6 | wiztowar 7 | 1.7-SNAPSHOT 8 | jar 9 | 10 | wiztowar 11 | WizToWar is a simple library that helps you deploy Dropwizard services as a WAR in a WAR container 12 | 13 | http://github.com/twilio/wiztowar 14 | 15 | 16 | 17 | 18 | christer 19 | Christer Fahlgren 20 | christer@twilio.com 21 | 22 | 23 | 24 | 25 | The MIT License 26 | http://www.opensource.org/licenses/mit-license.php 27 | repo 28 | 29 | 30 | 31 | 32 | 0.6.2 33 | 2.2.0 34 | 35 | 36 | 37 | scm:git:git://github.com/twilio/wiztowar.git 38 | scm:git:git@github.com:twilio/wiztowar.git 39 | http://github.com/twilio/wiztowar 40 | 41 | 42 | 43 | 44 | sonatype-nexus-snapshots 45 | Sonatype Nexus snapshot repository 46 | https://oss.sonatype.org/content/repositories/snapshots 47 | 48 | 49 | sonatype-nexus-staging 50 | Sonatype Nexus release repository 51 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 52 | 53 | 54 | 55 | 56 | 57 | com.yammer.dropwizard 58 | dropwizard-core 59 | ${dropwizard.version} 60 | 61 | 62 | com.yammer.metrics 63 | metrics-annotation 64 | ${metrics.version} 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-release-plugin 73 | 2.2.2 74 | 75 | -Dgpg.passphrase=${gpg.passphrase} 76 | 77 | 78 | 79 | 80 | 81 | 82 | release-sign-artifacts 83 | 84 | 85 | performRelease 86 | true 87 | 88 | 89 | 90 | 91 | 92 | org.apache.maven.plugins 93 | maven-gpg-plugin 94 | 1.4 95 | 96 | ${gpg.passphrase} 97 | 98 | 99 | 100 | sign-artifacts 101 | verify 102 | 103 | sign 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/com/twilio/wiztowar/WarAssetsBundle.java: -------------------------------------------------------------------------------- 1 | package com.twilio.wiztowar; 2 | 3 | import com.yammer.dropwizard.Bundle; 4 | import com.yammer.dropwizard.assets.AssetServlet; 5 | import com.yammer.dropwizard.config.Bootstrap; 6 | import com.yammer.dropwizard.config.Environment; 7 | 8 | import static com.google.common.base.Preconditions.checkArgument; 9 | 10 | /** 11 | * A variant of the {@link com.yammer.dropwizard.assets.AssetsBundle} included in Dropwizard 12 | * suited for deployments where there is a non-empty servlet context. 13 | */ 14 | public class WarAssetsBundle implements Bundle { 15 | private static final String DEFAULT_INDEX_FILE = "index.htm"; 16 | private static final String DEFAULT_PATH = "/assets"; 17 | 18 | private final String resourcePath; 19 | private final String warPath; 20 | private final String uriPath; 21 | private final String indexFile; 22 | 23 | /** 24 | * Creates a new {@link WarAssetsBundle} which serves up static assets from 25 | * {@code src/main/resources/assets/*} as {@code /assets/*}. 26 | * 27 | * @see com.twilio.wiztowar.WarAssetsBundle#WarAssetsBundle(String, String, String, String) 28 | */ 29 | public WarAssetsBundle() { 30 | this("", DEFAULT_PATH, DEFAULT_INDEX_FILE,""); 31 | } 32 | 33 | /** 34 | * Creates a new {@link WarAssetsBundle} which will configure the service to serve the static files 35 | * located in {@code src/main/resources/${path}} as {@code /${path}}. For example, given a 36 | * {@code path} of {@code "/assets"}, {@code src/main/resources/assets/example.js} would be 37 | * served up from {@code /assets/example.js}. 38 | * 39 | * @param path the classpath and URI root of the static asset files 40 | * @see com.twilio.wiztowar.WarAssetsBundle#WarAssetsBundle(String, String, String, String) 41 | */ 42 | public WarAssetsBundle(String path) { 43 | this(path, path, DEFAULT_INDEX_FILE,""); 44 | } 45 | 46 | /** 47 | * Creates a new AssetsBundle which will configure the service to serve the static files 48 | * located in {@code src/main/resources/${resourcePath}} as {@code /${uriPath}}. For example, given a 49 | * {@code resourcePath} of {@code "/assets"} and a uriPath of {@code "/js"}, 50 | * {@code src/main/resources/assets/example.js} would be served up from {@code /js/example.js}. 51 | * 52 | * @param resourcePath the resource path (in the classpath) of the static asset files 53 | * @param uriPath the uri path for the static asset files 54 | * @see com.twilio.wiztowar.WarAssetsBundle#WarAssetsBundle(String, String, String, String) 55 | */ 56 | public WarAssetsBundle(String resourcePath, String uriPath) { 57 | this(resourcePath, uriPath, DEFAULT_INDEX_FILE,""); 58 | } 59 | 60 | /** 61 | * Creates a new AssetsBundle which will configure the service to serve the static files 62 | * located in {@code src/main/resources/${resourcePath}} as {@code /${uriPath}}. If no file name is 63 | * in ${uriPath}, ${indexFile} is appended before serving. For example, given a 64 | * {@code resourcePath} of {@code "/assets"} and a uriPath of {@code "/js"}, 65 | * {@code src/main/resources/assets/example.js} would be served up from {@code /js/example.js}. 66 | * 67 | * @param warPath the path to the application - typically the war-name in an app-server 68 | * @param resourcePath the resource path (in the classpath) of the static asset files 69 | * @param uriPath the uri path for the static asset files 70 | * @param indexFile the name of the index file to use 71 | */ 72 | public WarAssetsBundle(String warPath, String resourcePath, String uriPath, String indexFile) { 73 | checkArgument(resourcePath.startsWith("/"), "%s is not an absolute path", resourcePath); 74 | checkArgument(!"/".equals(resourcePath), "%s is the classpath root", resourcePath); 75 | this.resourcePath = resourcePath.endsWith("/") ? resourcePath : (resourcePath + '/'); 76 | this.uriPath = uriPath.endsWith("/") ? uriPath : (uriPath + '/'); 77 | this.indexFile = indexFile; 78 | this.warPath = warPath; 79 | } 80 | 81 | @Override 82 | public void initialize(Bootstrap bootstrap) { 83 | // nothing doing 84 | } 85 | 86 | @Override 87 | public void run(Environment environment) { 88 | environment.addServlet(createServlet(), uriPath + '*'); 89 | } 90 | 91 | private AssetServlet createServlet() { 92 | final String warPathWithTrailingSlash = warPath.endsWith("/") ? warPath : warPath + "/"; 93 | final String warPathWithStartingSlash = warPathWithTrailingSlash 94 | .startsWith("/") ? warPathWithTrailingSlash : "/" + warPathWithTrailingSlash; 95 | final String uriPathSansStartingSlash = uriPath.startsWith("/") ? uriPath.substring(1) : uriPath; 96 | return new AssetServlet(resourcePath, warPathWithStartingSlash + uriPathSansStartingSlash, indexFile); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/yammer/dropwizard/config/ExtendedEnvironment.java: -------------------------------------------------------------------------------- 1 | package com.yammer.dropwizard.config; 2 | 3 | import com.google.common.collect.*; 4 | import com.sun.jersey.core.reflection.AnnotatedMethod; 5 | import com.sun.jersey.core.reflection.MethodList; 6 | import com.yammer.dropwizard.json.ObjectMapperFactory; 7 | import com.yammer.dropwizard.tasks.Task; 8 | import com.yammer.dropwizard.validation.Validator; 9 | import com.yammer.metrics.core.HealthCheck; 10 | import org.eclipse.jetty.servlet.FilterHolder; 11 | import org.eclipse.jetty.servlet.ServletHolder; 12 | import org.eclipse.jetty.util.resource.Resource; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import javax.ws.rs.HttpMethod; 17 | import javax.ws.rs.Path; 18 | import java.util.EventListener; 19 | 20 | /** 21 | * This class is here to enable us to pull out various things from the {@link Environment}. 22 | */ 23 | public class ExtendedEnvironment extends Environment { 24 | 25 | final private static Logger logger = LoggerFactory.getLogger(ExtendedEnvironment.class); 26 | /** 27 | * Creates a new environment. 28 | * 29 | * @param name the name of the service 30 | * @param configuration the service's {@link com.yammer.dropwizard.config.Configuration} 31 | * @param objectMapperFactory the {@link com.yammer.dropwizard.json.ObjectMapperFactory} for the service 32 | */ 33 | public ExtendedEnvironment(String name, 34 | Configuration configuration, 35 | ObjectMapperFactory objectMapperFactory, 36 | Validator validator) { 37 | super(name, configuration, objectMapperFactory, validator); 38 | 39 | } 40 | 41 | @Override 42 | public ImmutableSet getTasks() { 43 | return super.getTasks(); 44 | } 45 | 46 | @Override 47 | public ImmutableSet getHealthChecks() { 48 | return super.getHealthChecks(); 49 | } 50 | 51 | @Override 52 | public Resource getBaseResource() { 53 | return super.getBaseResource(); 54 | } 55 | 56 | @Override 57 | public ImmutableSet getProtectedTargets() { 58 | return super.getProtectedTargets(); 59 | } 60 | 61 | @Override 62 | public ImmutableMap getServlets() { 63 | return super.getServlets(); 64 | } 65 | 66 | @Override 67 | public ImmutableMultimap getFilters() { 68 | return super.getFilters(); 69 | } 70 | 71 | @Override 72 | public ImmutableSet getServletListeners() { 73 | return super.getServletListeners(); 74 | } 75 | 76 | /** 77 | * Log all the resources. 78 | */ 79 | private void logResources() { 80 | final ImmutableSet.Builder builder = ImmutableSet.builder(); 81 | 82 | for (Class klass : super.getJerseyResourceConfig().getClasses()) { 83 | if (klass.isAnnotationPresent(Path.class)) { 84 | builder.add(klass.getCanonicalName()); 85 | } 86 | } 87 | 88 | for (Object o : super.getJerseyResourceConfig().getSingletons()) { 89 | if (o.getClass().isAnnotationPresent(Path.class)) { 90 | builder.add(o.getClass().getCanonicalName()); 91 | } 92 | } 93 | 94 | logger.debug("resources = {}", builder.build()); 95 | } 96 | 97 | /** 98 | * Log all endpoints. 99 | */ 100 | public void logEndpoints(Configuration configuration) { 101 | final StringBuilder stringBuilder = new StringBuilder(1024).append("The following paths were found for the configured resources:\n\n"); 102 | 103 | final ImmutableList.Builder> builder = ImmutableList.builder(); 104 | for (Object o : super.getJerseyResourceConfig().getSingletons()) { 105 | if (o.getClass().isAnnotationPresent(Path.class)) { 106 | builder.add(o.getClass()); 107 | } 108 | } 109 | for (Class klass : super.getJerseyResourceConfig().getClasses()) { 110 | if (klass.isAnnotationPresent(Path.class)) { 111 | builder.add(klass); 112 | } 113 | } 114 | 115 | for (Class klass : builder.build()) { 116 | final String path = klass.getAnnotation(Path.class).value(); 117 | String rootPath = configuration.getHttpConfiguration().getRootPath(); 118 | if (rootPath.endsWith("/*")) { 119 | rootPath = rootPath.substring(0, rootPath.length() - (path.startsWith("/") ? 2 : 1)); 120 | } 121 | 122 | final ImmutableList.Builder endpoints = ImmutableList.builder(); 123 | for (AnnotatedMethod method : annotatedMethods(klass)) { 124 | final StringBuilder pathBuilder = new StringBuilder() 125 | .append(rootPath) 126 | .append(path); 127 | if (method.isAnnotationPresent(Path.class)) { 128 | final String methodPath = method.getAnnotation(Path.class).value(); 129 | if (!methodPath.startsWith("/") && !path.endsWith("/")) { 130 | pathBuilder.append('/'); 131 | } 132 | pathBuilder.append(methodPath); 133 | } 134 | for (HttpMethod verb : method.getMetaMethodAnnotations(HttpMethod.class)) { 135 | endpoints.add(String.format(" %-7s %s (%s)", 136 | verb.value(), 137 | pathBuilder.toString(), 138 | klass.getCanonicalName())); 139 | } 140 | } 141 | 142 | for (String line : Ordering.natural().sortedCopy(endpoints.build())) { 143 | stringBuilder.append(line).append('\n'); 144 | } 145 | } 146 | 147 | logger.info(stringBuilder.toString()); 148 | } 149 | 150 | 151 | private MethodList annotatedMethods(Class resource) { 152 | return new MethodList(resource, true).hasMetaAnnotation(HttpMethod.class); 153 | } 154 | 155 | /** 156 | * Validate the Jersey resources before launching. 157 | */ 158 | public void validateJerseyResources() { 159 | logResources(); 160 | } 161 | } -------------------------------------------------------------------------------- /src/main/java/com/twilio/wiztowar/DWAdapter.java: -------------------------------------------------------------------------------- 1 | package com.twilio.wiztowar; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.util.EventListener; 7 | import java.util.HashSet; 8 | import java.util.Map; 9 | import java.util.Set; 10 | 11 | import javax.servlet.FilterRegistration; 12 | import javax.servlet.ServletContext; 13 | import javax.servlet.ServletContextEvent; 14 | import javax.servlet.ServletContextListener; 15 | import javax.servlet.ServletRegistration; 16 | import javax.servlet.ServletRegistration.Dynamic; 17 | import javax.servlet.http.HttpServlet; 18 | import javax.ws.rs.core.Application; 19 | 20 | import org.eclipse.jetty.servlet.FilterHolder; 21 | import org.eclipse.jetty.servlet.ServletHolder; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import com.google.common.collect.ImmutableMap; 26 | import com.yammer.dropwizard.Service; 27 | import com.yammer.dropwizard.config.Bootstrap; 28 | import com.yammer.dropwizard.config.Configuration; 29 | import com.yammer.dropwizard.config.ConfigurationException; 30 | import com.yammer.dropwizard.config.ConfigurationFactory; 31 | import com.yammer.dropwizard.config.ExtendedEnvironment; 32 | import com.yammer.dropwizard.config.HttpConfiguration; 33 | import com.yammer.dropwizard.config.LoggingFactory; 34 | import com.yammer.dropwizard.jersey.JacksonMessageBodyProvider; 35 | import com.yammer.dropwizard.json.ObjectMapperFactory; 36 | import com.yammer.dropwizard.servlets.ThreadNameFilter; 37 | import com.yammer.dropwizard.tasks.TaskServlet; 38 | import com.yammer.dropwizard.util.Generics; 39 | import com.yammer.dropwizard.validation.Validator; 40 | import com.yammer.metrics.HealthChecks; 41 | import com.yammer.metrics.core.HealthCheck; 42 | import com.yammer.metrics.reporting.AdminServlet; 43 | import com.yammer.metrics.util.DeadlockHealthCheck; 44 | 45 | /** 46 | * The {@link DWAdapter} adapts a Dropwizard {@link Service} to be hooked in to the lifecycle of a WAR. 47 | */ 48 | public abstract class DWAdapter extends Application implements ServletContextListener { 49 | 50 | /** 51 | * The {@link ServletContext} that Dropwizard will run in. 52 | */ 53 | private static ServletContext servletContext; 54 | 55 | /** 56 | * The {@link Logger} to use. 57 | */ 58 | final static Logger logger = LoggerFactory.getLogger(DWAdapter.class); 59 | 60 | /** 61 | * The Jersey singletons. 62 | */ 63 | private HashSet singletons; 64 | 65 | /** 66 | * The Jersey classes. 67 | */ 68 | private HashSet> classes; 69 | 70 | /** 71 | * The {@link Service} which we are adapting. 72 | */ 73 | private Service dwService; 74 | 75 | /** 76 | * The {@link Service} which we are adapting. 77 | */ 78 | private ExtendedEnvironment environment; 79 | 80 | /** 81 | * Implementation of the Jersey Application. Returns the classes. 82 | * 83 | * @return the Jersey configured classes 84 | */ 85 | @Override 86 | public Set> getClasses() { 87 | synchronized (this) { 88 | if (dwService == null) { 89 | initialize(); 90 | } 91 | } 92 | return classes; 93 | } 94 | 95 | /** 96 | * Implementation of the Jersey Application. Returns the singletons. 97 | * 98 | * @return the Jersey configured singletons. 99 | */ 100 | @Override 101 | public Set getSingletons() { 102 | synchronized (this) { 103 | if (dwService == null) { 104 | initialize(); 105 | } 106 | } 107 | return singletons; 108 | } 109 | 110 | /** 111 | * Initialize the {@link Service} and configure the Servlet and Jersey environment. 112 | */ 113 | private void initialize() { 114 | try { 115 | dwService = getSingletonService(); 116 | if (dwService == null) { 117 | throw new IllegalStateException("The singleton service is null"); 118 | } 119 | final Bootstrap bootstrap = new Bootstrap(dwService); 120 | dwService.initialize(bootstrap); 121 | final T configuration = parseConfiguration(getConfigurationFile(), 122 | getConfigurationClass(), 123 | bootstrap.getObjectMapperFactory().copy()); 124 | if (configuration != null) { 125 | logger.info("The WizToWar adapter defers logging configuration to the application server"); 126 | new LoggingFactory(configuration.getLoggingConfiguration(), 127 | bootstrap.getName()).configure(); 128 | } 129 | 130 | final Validator validator = new Validator(); 131 | environment = new ExtendedEnvironment(bootstrap.getName(), configuration, bootstrap.getObjectMapperFactory(), validator); 132 | final Map properties = this.getProperties(servletContext); 133 | if (properties != null) { 134 | for (final Map.Entry prop : properties.entrySet()) { 135 | environment.setJerseyProperty(prop.getKey(), prop.getValue()); 136 | } 137 | } 138 | try { 139 | 140 | environment.start(); 141 | 142 | bootstrap.runWithBundles(configuration, environment); //Fix for issue https://github.com/twilio/wiztowar/issues/2 143 | 144 | dwService.run(configuration, environment); 145 | addHealthChecks(environment); 146 | if (servletContext == null) { 147 | throw new IllegalStateException("ServletContext is null"); 148 | } 149 | 150 | createInternalServlet(environment, servletContext); 151 | createExternalServlet(environment, configuration.getHttpConfiguration(), servletContext); 152 | environment.validateJerseyResources(); 153 | environment.logEndpoints(configuration); 154 | 155 | // Now collect the Jersey configuration 156 | singletons = new HashSet(); 157 | singletons.addAll(environment.getJerseyResourceConfig().getSingletons()); 158 | classes = new HashSet>(); 159 | classes.addAll(environment.getJerseyResourceConfig().getClasses()); 160 | 161 | Dynamic registration = servletContext.addServlet("Jersey REST Service", environment.getJerseyServletContainer()); 162 | registration.addMapping("/rest/*"); 163 | registration.setLoadOnStartup(1); 164 | 165 | } catch (Exception e) { 166 | logger.error("Error {} ", e); 167 | throw new IllegalStateException(e); 168 | } 169 | } catch (Exception e) { 170 | logger.error("Error {} ", e); 171 | throw new IllegalStateException(e); 172 | } 173 | } 174 | 175 | /** 176 | * Parse the configuration from the {@link File}. 177 | * 178 | * @param file the {@link File} containing the configuration. 179 | * @param configurationClass the configuration class 180 | * @param objectMapperFactory the {@link ObjectMapperFactory} to use 181 | * @return the configuration instance 182 | * @throws IOException 183 | * @throws ConfigurationException 184 | */ 185 | private T parseConfiguration(final File file, 186 | final Class configurationClass, 187 | final ObjectMapperFactory objectMapperFactory) throws IOException, ConfigurationException { 188 | final ConfigurationFactory configurationFactory = 189 | ConfigurationFactory.forClass(configurationClass, new Validator(), objectMapperFactory); 190 | if (file != null) { 191 | if (!file.exists()) { 192 | throw new FileNotFoundException("File " + file + " not found"); 193 | } 194 | return configurationFactory.build(file); 195 | } 196 | return configurationFactory.build(); 197 | } 198 | 199 | /** 200 | * Retrieve the configuration class. 201 | * 202 | * @return the configuration class. 203 | */ 204 | protected Class getConfigurationClass() { 205 | return Generics.getTypeParameter(getClass(), Configuration.class); 206 | } 207 | 208 | /** 209 | * This method is adapted from ServerFactory.createInternalServlet. 210 | */ 211 | private void createInternalServlet(final ExtendedEnvironment env, final ServletContext context) { 212 | if (context.getMajorVersion() >= 3) { 213 | 214 | // Add the Task servlet 215 | final ServletRegistration.Dynamic taskServlet = context.addServlet("TaskServlet", new TaskServlet(env.getTasks())); 216 | taskServlet.setAsyncSupported(true); 217 | taskServlet.addMapping("/tasks/*"); 218 | 219 | // Add the Admin servlet 220 | final ServletRegistration.Dynamic adminServlet = context.addServlet("AdminServlet", new AdminServlet()); 221 | adminServlet.setAsyncSupported(true); 222 | adminServlet.addMapping("/admin/*"); 223 | } else throw new IllegalStateException("The WizToWar adapter doesn't support servlet versions under 3"); 224 | } 225 | 226 | /** 227 | * This method is adapted from ServerFactory.createExternalServlet. 228 | * 229 | * @param env the {@link ExtendedEnvironment} from which we find the resources to act on. 230 | * @param context the {@link ServletContext} to add to 231 | */ 232 | private void createExternalServlet(ExtendedEnvironment env, HttpConfiguration config, ServletContext context) { 233 | context.addFilter("ThreadNameFilter", ThreadNameFilter.class); 234 | 235 | if (!env.getProtectedTargets().isEmpty()) { 236 | logger.warn("The WizToWar adapter doesn't support protected targets"); 237 | } 238 | 239 | for (ImmutableMap.Entry entry : env.getServlets().entrySet()) { 240 | final HttpServlet servletInstance = (HttpServlet) entry.getValue().getServletInstance(); 241 | final ServletRegistration.Dynamic servletReg = context.addServlet(entry.getKey(), servletInstance); 242 | servletReg.setAsyncSupported(true); 243 | servletReg.addMapping(entry.getKey()); 244 | servletReg.setLoadOnStartup(1); 245 | } 246 | 247 | env.addProvider(new JacksonMessageBodyProvider(env.getObjectMapperFactory().build(), 248 | env.getValidator())); 249 | 250 | for (ImmutableMap.Entry entry : env.getFilters().entries()) { 251 | String mapping = entry.getKey(); 252 | FilterHolder holder = entry.getValue(); 253 | FilterRegistration.Dynamic filterReg = context.addFilter(holder.getName(), holder.getClassName()); 254 | filterReg.addMappingForUrlPatterns(null, false, mapping); 255 | filterReg.setInitParameters(holder.getInitParameters()); 256 | } 257 | 258 | for (EventListener listener : env.getServletListeners()) { 259 | context.addListener(listener); 260 | } 261 | 262 | for (Map.Entry entry : config.getContextParameters().entrySet()) { 263 | context.setInitParameter(entry.getKey(), entry.getValue()); 264 | } 265 | 266 | if (env.getSessionHandler() != null) { 267 | logger.warn("The WizToWar adapter doesn't support custom session handlers."); 268 | 269 | } 270 | } 271 | 272 | /** 273 | * This method is adapted from ServerFactory.buildServer. 274 | * 275 | * @param env the {@link ExtendedEnvironment} to get {@link HealthCheck}s from. 276 | */ 277 | private void addHealthChecks(ExtendedEnvironment env) { 278 | HealthChecks.defaultRegistry().register(new DeadlockHealthCheck()); 279 | for (HealthCheck healthCheck : env.getHealthChecks()) { 280 | HealthChecks.defaultRegistry().register(healthCheck); 281 | } 282 | 283 | if (env.getHealthChecks().isEmpty()) { 284 | logger.warn('\n' + 285 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + 286 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + 287 | "! THIS SERVICE HAS NO HEALTHCHECKS. THIS MEANS YOU WILL NEVER KNOW IF IT !\n" + 288 | "! DIES IN PRODUCTION, WHICH MEANS YOU WILL NEVER KNOW IF YOU'RE LETTING !\n" + 289 | "! YOUR USERS DOWN. YOU SHOULD ADD A HEALTHCHECK FOR EACH DEPENDENCY OF !\n" + 290 | "! YOUR SERVICE WHICH FULLY (BUT LIGHTLY) TESTS YOUR SERVICE'S ABILITY TO !\n" + 291 | "! USE THAT SERVICE. THINK OF IT AS A CONTINUOUS INTEGRATION TEST. !\n" + 292 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" + 293 | "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" 294 | ); 295 | } 296 | 297 | 298 | } 299 | 300 | /** 301 | * Override to provide your particular Dropwizard Service. 302 | * 303 | * @return your {@link Service} 304 | */ 305 | public abstract Service getSingletonService(); 306 | 307 | /** 308 | * Override to provide your configuration {@link File} location. 309 | * 310 | * @return the {@link File} to read the configuration from. 311 | */ 312 | public abstract File getConfigurationFile(); 313 | 314 | /** 315 | * Override to return properties that will be accessible as Jersey properties in your service. 316 | * This is useful for accessing servlet context attributes, for example. 317 | * 318 | * @return a map of properties 319 | */ 320 | public Map getProperties(final ServletContext servletContext) { 321 | return null; 322 | } 323 | 324 | public void shutDown() { 325 | try { 326 | this.environment.stop(); 327 | } catch (Exception e) { 328 | logger.error("Failed to stop environment cleanly due to {}", e); 329 | } 330 | } 331 | 332 | @Override 333 | public void contextInitialized(ServletContextEvent sce) { 334 | servletContext = sce.getServletContext(); 335 | initialize(); 336 | } 337 | 338 | @Override 339 | public void contextDestroyed(ServletContextEvent sce) { 340 | shutDown(); 341 | } 342 | } 343 | 344 | --------------------------------------------------------------------------------