Java tutorial
/** * Copyright (C) 2014 John Leacox * * 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. * * This file incorporates code covered by the following terms: * * Copyright (C) 2006 Google Inc. * * 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 com.leacox.dagger.servlet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import dagger.ObjectGraph; import dagger.ScopingObjectGraph; import javax.servlet.Filter; import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import javax.servlet.http.HttpServlet; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import static com.google.common.base.Preconditions.checkState; /** * Configures injection using dagger and configures filters and servlets. * <p/> * This class should be subclassed to register filters and servlets in the {@link #configureServlets()} method. Your * dagger modules should be added to the {@link #getBaseModules()} and {@link #getRequestScopedModules()} methods. * <p/> * Your dagger modules should be split into separate classes by scope like so: * <ul> * <li>Application Wide - Put application wide modules in {@link #getBaseModules()}</li> * <li>Request Scoped - Put request scoped modules in {@link #getRequestScopedModules()}</li> * </ul> * <p/> * The following modules are included by dagger-servlet to provide some standard bindings: * <ul> * <li> * {@link com.leacox.dagger.servlet.ServletModule} - This module should be included as part of the base modules, * either directly in the modules list, or by putting it in the {@code includes} list of your own application module. * <p/> * The following bindings are provided by this module: * <ul> * <li>{@link ServletContext}</li> * <li>Several internal dagger-servlet bindings</li> * </ul> * </li> * <li> * {@link com.leacox.dagger.servlet.ServletRequestModule} - This module should be included as part of the request scoped * modules, either directly in the modules list, or by putting it in the {@code includes} list of your own request * scoped module. * <p/> * The following bindings are provided by this module: * <ul> * <li>{@link javax.servlet.ServletRequest}</li> * <li>{@link javax.servlet.ServletResponse}</li> * <li>{@link javax.servlet.http.HttpSession}</li> * <p/> * </ul> * </li> * </ul> * * @author John Leacox */ public abstract class DaggerServletContextListener implements ServletContextListener { public static final String OBJECT_GRAPH_NAME = ObjectGraph.class.getName(); private ObjectGraph objectGraph; private List<FilterDefinition> filterDefinitions = null; private List<ServletDefinition> servletDefinitions = null; @Override public void contextInitialized(ServletContextEvent servletContextEvent) { checkState(filterDefinitions == null, "Re-entry is not allowed."); checkState(servletDefinitions == null, "Re-entry is not allowed."); filterDefinitions = Lists.newArrayList(); servletDefinitions = Lists.newArrayList(); try { ServletContext servletContext = servletContextEvent.getServletContext(); ObjectGraph unscopedGraph = ObjectGraph.create(getBaseModules()); ObjectGraph scopingObjectGraph = ScopingObjectGraph.create(unscopedGraph) .addScopedModules(RequestScoped.class, getRequestScopedModules()); scopingObjectGraph.get(ServletContextProvider.class).set(servletContext); scopingObjectGraph.get(InternalServletModule.ObjectGraphProvider.class).set(scopingObjectGraph); Iterable<Object> fullModules = Iterables.concat(Arrays.asList(getBaseModules()), Arrays.asList(getRequestScopedModules())); scopingObjectGraph.get(InternalServletModule.FullModulesProvider.class) .set(Iterables.toArray(fullModules, Object.class)); configureServlets(); scopingObjectGraph.get(InternalServletModule.FilterDefinitionsProvider.class) .set(filterDefinitions.toArray(new FilterDefinition[filterDefinitions.size()])); // Ensure that servlets are not bound twice to the same pattern. Set<String> servletUris = Sets.newHashSet(); for (ServletDefinition servletDefinition : servletDefinitions) { if (servletUris.contains(servletDefinition.getPattern())) { // TODO: Consider finding all servlet configuration errors and throw one exception with all of them. throw new IllegalStateException("More than one servlet was mapped to the same URI pattern: " + servletDefinition.getPattern()); } else { servletUris.add(servletDefinition.getPattern()); } } scopingObjectGraph.get(InternalServletModule.ServletDefinitionsProvider.class) .set(servletDefinitions.toArray(new ServletDefinition[servletDefinitions.size()])); // Make sure the dagger filter is injected DaggerFilter daggerFilter = scopingObjectGraph.get(DaggerFilter.class); scopingObjectGraph.inject(daggerFilter); objectGraph = scopingObjectGraph; servletContext.setAttribute(OBJECT_GRAPH_NAME, scopingObjectGraph); } finally { filterDefinitions = null; servletDefinitions = null; } } @Override public void contextDestroyed(ServletContextEvent servletContextEvent) { ServletContext servletContext = servletContextEvent.getServletContext(); servletContext.removeAttribute(OBJECT_GRAPH_NAME); } /** * Gets the servlet object graph created from the base and request scoped module. The returned object graph will be * * @return the servlet object graph, null if the context listener has not been initialized. * @{code null} until the context listener has been initialized. */ protected ObjectGraph getObjectGraph() { return objectGraph; } /** * Override this method to return an array of your application level Dagger modules. {@link ServletModule} should * be included. */ protected abstract Object[] getBaseModules(); /** * Override this method to return an array of your request scoped Dagger modules. {@link ServletRequestModule} * should be included. */ protected abstract Object[] getRequestScopedModules(); /** * <h3>Servlet Mapping EDSL</h3> * <p/> * <p> Part of the EDSL builder language for configuring servlets * and filters with dagger-servlet. Think of this as an in-code replacement for web.xml. * Filters and servlets are configured here using simple java method calls. Here is a typical * example of registering a filter in your context listener: * <p/> * <pre> * MyContextListener extends DaggerServletContextListener { * {@literal @}Override * protected Class<?>[] getBaseModules() { * return new Class<?>[]{ MyModule.class, ServletModule.class }; * } * * {@literal @}Override * protected Class<?>[] getRequestScopedModules() { * return new Class<?>[]{ MyRequestModule.class }; * } * * {@literal @}Override * protected void configureServlets() { * serve("*.html").with(MyServlet.class); * } * } * </pre> * <p/> * This registers a servlet (subclass of {@code HttpServlet}) called {@code MyServlet} to service * any web pages ending in {@code .html}. You can also use a path-style syntax to register * servlets: * <p/> * <pre> * <b>serve("/my/*").with(MyServlet.class)</b> * </pre> * <p/> * Every servlet (or filter) is required to be a singleton. If you cannot annotate the class * directly, you should annotate the provides method your module. Mapping a servlet that is bound * under any other scope is an error. * <p/> * <p/> * <h4>Dispatch Order</h4> * You are free to register as many servlets and filters as you like this way. They will * be compared and dispatched in the order in which the filter methods are called: * <p/> * <pre> * MyContextListener extends DaggerServletContextListener { * {@literal @}Override * protected Class<?>[] getBaseModules() { * return new Class<?>[]{ MyModule.class, ServletModule.class }; * } * * {@literal @}Override * protected Class<?>[] getRequestScopedModules() { * return new Class<?>[]{ MyRequestModule.class }; * } * * {@literal @}Override * protected Class<?>[] getSessionScopedModules() { * return new Class<?>[]{ MySessionModule.class }; * } * * {@literal @}Override * protected void configureServlets() { * filter("/*").through(MyFilter.class); * filter("*.css").through(MyCssFilter.class); * filter("*.jpg").through(new MyJpgFilter()); * // etc.. * * serve("*.html").with(MyServlet.class); * serve("/my/*").with(MyServlet.class); * serve("*.jpg").with(new MyServlet()); * // etc... * } * } * </pre> * This will traverse down the list of rules in lexical order. For example, a url * "{@code /my/file.js}" (after it runs through the matching filters) will first * be compared against the servlet mapping: * <p/> * <pre> * serve("*.html").with(MyServlet.class); * </pre> * And failing that, it will descend to the next servlet mapping: * <p/> * <pre> * serve("/my/*").with(MyServlet.class); * </pre> * <p/> * Since this rule matches, Dagger Servlet will dispatch to {@code MyServlet}. These * two mapping rules can also be written in more compact form using varargs syntax: * <p/> * <pre> * serve(<b>"*.html", "/my/*"</b>).with(MyServlet.class); * </pre> * <p/> * This way you can map several URI patterns to the same servlet. A similar syntax is * also available for filter mappings. * <p/> * <p/> * <h4>Regular Expressions</h4> * You can also map servlets (or filters) to URIs using regular expressions: * <pre> * <b>serveRegex("(.)*ajax(.)*").with(MyAjaxServlet.class)</b> * </pre> * <p/> * This will map any URI containing the text "ajax" in it to {@code MyAjaxServlet}. Such as: * <ul> * <li>http://www.example.com/ajax.html</li> * <li>http://www.example.com/content/ajax/index</li> * <li>http://www.example.com/it/is_totally_ajaxian</li> * </ul> * <p/> * <p/> * <h3>Initialization Parameters</h3> * <p/> * Servlets (and filters) allow you to pass in init params * using the {@code <init-param>} tag in web.xml. You can similarly pass in parameters to * Servlets and filters registered in dagger-servlet using a {@link java.util.Map} of parameter * name/value pairs. For example, to initialize {@code MyServlet} with two parameters * ({@code name="John", site="example.com"}) you could write: * <p/> * <pre> * Map<String, String> params = new HashMap<String, String>(); * params.put("name", "John"); * params.put("site", "example.com"); * * ... * serve("/*").with(MyServlet.class, <b>params</b>) * </pre> */ // TODO: guice-servlet supports bindings from multiple modules. Support for something like this would be good // so that drop-in plugins can supply servlet functionality. protected void configureServlets() { } /** * @param urlPattern Any Servlet-style pattern. examples: /*, /html/*, *.html, etc. */ protected final FilterDefinitionBuilder filter(String urlPattern, String... morePatterns) { return new FilterDefinitionBuilderImpl(Lists.asList(urlPattern, morePatterns), UriPatternType.SERVLET); } /** * @param regex Any Java-style regular expression. */ protected final FilterDefinitionBuilder filterRegex(String regex, String... moreRegexes) { return new FilterDefinitionBuilderImpl(Lists.asList(regex, moreRegexes), UriPatternType.REGEX); } /** * @param urlPattern Any Servlet-style pattern. examples: /*, /html/*, *.html, etc. */ protected final ServletDefinitionBuilder serve(String urlPattern, String... morePatterns) { return new ServletDefinitionBuilderImpl(Lists.asList(urlPattern, morePatterns), UriPatternType.SERVLET); } /** * @param regex Any Java-style regular expression. */ protected final ServletDefinitionBuilder serveRegex(String regex, String... moreRegexes) { return new ServletDefinitionBuilderImpl(Lists.asList(regex, moreRegexes), UriPatternType.REGEX); } /** * See the EDSL examples at {@link #configureServlets()} */ public static interface FilterDefinitionBuilder { void through(Class<? extends Filter> filterClass); void through(Filter filter); void through(Class<? extends Filter> filterClass, Map<String, String> initParams); void through(Filter filter, Map<String, String> initParams); } private class FilterDefinitionBuilderImpl implements FilterDefinitionBuilder { private final List<String> uriPatterns; private final UriPatternType uriPatternType; private FilterDefinitionBuilderImpl(List<String> uriPatterns, UriPatternType uriPatternType) { this.uriPatterns = uriPatterns; this.uriPatternType = uriPatternType; } @Override public void through(Class<? extends Filter> filterClass) { through(filterClass, Maps.<String, String>newHashMap()); } @Override public void through(Filter filter) { through(filter, Maps.<String, String>newHashMap()); } @Override public void through(Class<? extends Filter> filterClass, Map<String, String> initParams) { through(filterClass, initParams, null); } @Override public void through(Filter filter, Map<String, String> initParams) { through(filter.getClass(), initParams, filter); } private void through(Class<? extends Filter> filterClass, Map<String, String> initParams, Filter filterInstance) { for (String pattern : uriPatterns) { filterDefinitions.add(new FilterDefinition(pattern, filterClass, UriPatternType.get(uriPatternType, pattern), initParams, filterInstance)); } } } /** * See the EDSL examples at {@link #configureServlets()} */ public static interface ServletDefinitionBuilder { void with(Class<? extends HttpServlet> servletClass); void with(HttpServlet servlet); void with(Class<? extends HttpServlet> servletClass, Map<String, String> initParams); void with(HttpServlet servlet, Map<String, String> initParams); } private class ServletDefinitionBuilderImpl implements ServletDefinitionBuilder { private final List<String> uriPatterns; private final UriPatternType uriPatternType; private ServletDefinitionBuilderImpl(List<String> uriPatterns, UriPatternType uriPatternType) { this.uriPatterns = uriPatterns; this.uriPatternType = uriPatternType; } @Override public void with(Class<? extends HttpServlet> servletClass) { with(servletClass, Maps.<String, String>newHashMap()); } @Override public void with(HttpServlet servlet) { with(servlet, Maps.<String, String>newHashMap()); } @Override public void with(Class<? extends HttpServlet> servletClass, Map<String, String> initParams) { with(servletClass, initParams, null); } @Override public void with(HttpServlet servlet, Map<String, String> initParams) { with(servlet.getClass(), initParams, servlet); } private void with(Class<? extends HttpServlet> servletClass, Map<String, String> initParams, HttpServlet servletInstance) { for (String pattern : uriPatterns) { servletDefinitions.add(new ServletDefinition(pattern, servletClass, UriPatternType.get(uriPatternType, pattern), initParams, servletInstance)); } } } }