package ru.vyarus.dropwizard.guice.module.installer.feature.web; import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import io.dropwizard.jetty.setup.ServletEnvironment; import io.dropwizard.setup.Environment; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ru.vyarus.dropwizard.guice.module.installer.FeatureInstaller; import ru.vyarus.dropwizard.guice.module.installer.feature.web.util.WebUtils; import ru.vyarus.dropwizard.guice.module.installer.install.InstanceInstaller; import ru.vyarus.dropwizard.guice.module.installer.option.InstallerOptionsSupport; import ru.vyarus.dropwizard.guice.module.installer.order.Order; import ru.vyarus.dropwizard.guice.module.installer.order.Ordered; import ru.vyarus.dropwizard.guice.module.installer.util.FeatureUtils; import ru.vyarus.dropwizard.guice.module.installer.util.Reporter; import javax.servlet.ServletRegistration; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import java.util.Set; import static ru.vyarus.dropwizard.guice.module.installer.InstallersOptions.DenyServletRegistrationWithClash; /** * Search for http servlets annotated with {@link WebServlet} (servlet api annotation). Such servlets will not * be installed by jetty because dropwizard didn't depend on jetty-annotations. * <p> * Only the following {@link WebServlet} annotation properties are supported: name, urlPatterns ( or value), * initParams, asyncSupported. * <p> * When servlet name not defined, then name will be generated as: . (dot) at the beginning to indicate * generated name, followed by lower-cased class name. If class ends with "servlet" then it will be cut off. * For example, for class "MyCoolServlet" generated name will be ".mycool". * <p> * If servlet mapping clash (partially or completely) with some other servlet then warning log will be printed, * but overall process will not fail. Use * {@link ru.vyarus.dropwizard.guice.module.installer.InstallersOptions#DenyServletRegistrationWithClash} to throw * exception instead of warning. * <p> * By default, everything is installed for main context. Special annotation * {@link ru.vyarus.dropwizard.guice.module.installer.feature.web.AdminContext} must be used to install into admin * or both contexts. * <p> * Reporting format: <pre>[urls] [context markers: M - main, A - admin] (class) [servlet name]</pre>. * If servlet registered only in main context, then context marker (M) is not shown. * * @author Vyacheslav Rusakov * @since 06.08.2016 */ @Order(90) public class WebServletInstaller extends InstallerOptionsSupport implements FeatureInstaller<HttpServlet>, InstanceInstaller<HttpServlet>, Ordered { private final Logger logger = LoggerFactory.getLogger(WebServletInstaller.class); private final Reporter reporter = new Reporter(WebServletInstaller.class, "servlets ="); @Override public boolean matches(final Class<?> type) { return FeatureUtils.is(type, HttpServlet.class) && FeatureUtils.hasAnnotation(type, WebServlet.class); } @Override public void install(final Environment environment, final HttpServlet instance) { final Class<? extends HttpServlet> extType = FeatureUtils.getInstanceClass(instance); final WebServlet annotation = FeatureUtils.getAnnotation(extType, WebServlet.class); final String[] patterns = annotation.urlPatterns().length > 0 ? annotation.urlPatterns() : annotation.value(); Preconditions.checkArgument(patterns.length > 0, "Servlet %s not specified url pattern for mapping", extType.getName()); final AdminContext context = FeatureUtils.getAnnotation(extType, AdminContext.class); final String name = WebUtils.getServletName(annotation, extType); reporter.line("%-15s %-5s %-2s (%s) %s", Joiner.on(",").join(patterns), WebUtils.getAsyncMarker(annotation), WebUtils.getContextMarkers(context), extType.getName(), name); if (WebUtils.isForMain(context)) { configure(environment.servlets(), instance, extType, name, annotation); } if (WebUtils.isForAdmin(context)) { configure(environment.admin(), instance, extType, name, annotation); } } @Override public void report() { reporter.report(); } private void configure(final ServletEnvironment environment, final HttpServlet servlet, final Class<? extends HttpServlet> type, final String name, final WebServlet annotation) { final ServletRegistration.Dynamic mapping = environment.addServlet(name, servlet); final Set<String> clash = mapping .addMapping(annotation.urlPatterns().length > 0 ? annotation.urlPatterns() : annotation.value()); if (clash != null && !clash.isEmpty()) { final String msg = String.format( "Servlet registration %s clash with already installed servlets on paths: %s", type.getSimpleName(), Joiner.on(',').join(clash)); if (option(DenyServletRegistrationWithClash)) { throw new IllegalStateException(msg); } else { logger.warn(msg); } } if (annotation.initParams().length > 0) { for (WebInitParam param : annotation.initParams()) { mapping.setInitParameter(param.name(), param.value()); } } mapping.setAsyncSupported(annotation.asyncSupported()); } }