1 /** 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.inject.servlet; 17 18 import com.google.common.collect.Lists; 19 import com.google.common.collect.Sets; 20 import com.google.inject.Binding; 21 import com.google.inject.Inject; 22 import com.google.inject.Injector; 23 import com.google.inject.Provider; 24 import com.google.inject.Singleton; 25 import com.google.inject.TypeLiteral; 26 27 import java.io.IOException; 28 import java.util.List; 29 import java.util.Set; 30 31 import javax.servlet.Filter; 32 import javax.servlet.FilterChain; 33 import javax.servlet.RequestDispatcher; 34 import javax.servlet.ServletContext; 35 import javax.servlet.ServletException; 36 import javax.servlet.ServletRequest; 37 import javax.servlet.ServletResponse; 38 import javax.servlet.http.HttpServletRequest; 39 import javax.servlet.http.HttpServletRequestWrapper; 40 41 /** 42 * Central routing/dispatch class handles lifecycle of managed filters, and delegates to the servlet 43 * pipeline. 44 * 45 * @author dhanji (at) gmail.com (Dhanji R. Prasanna) 46 */ 47 @Singleton 48 class ManagedFilterPipeline implements FilterPipeline{ 49 private final FilterDefinition[] filterDefinitions; 50 private final ManagedServletPipeline servletPipeline; 51 private final Provider<ServletContext> servletContext; 52 53 //Unfortunately, we need the injector itself in order to create filters + servlets 54 private final Injector injector; 55 56 //Guards a DCL, so needs to be volatile 57 private volatile boolean initialized = false; 58 private static final TypeLiteral<FilterDefinition> FILTER_DEFS = 59 TypeLiteral.get(FilterDefinition.class); 60 61 @Inject 62 public ManagedFilterPipeline(Injector injector, ManagedServletPipeline servletPipeline, 63 Provider<ServletContext> servletContext) { 64 this.injector = injector; 65 this.servletPipeline = servletPipeline; 66 this.servletContext = servletContext; 67 68 this.filterDefinitions = collectFilterDefinitions(injector); 69 } 70 71 /** 72 * Introspects the injector and collects all instances of bound {@code List<FilterDefinition>} 73 * into a master list. 74 * 75 * We have a guarantee that {@link com.google.inject.Injector#getBindings()} returns a map 76 * that preserves insertion order in entry-set iterators. 77 */ 78 private FilterDefinition[] collectFilterDefinitions(Injector injector) { 79 List<FilterDefinition> filterDefinitions = Lists.newArrayList(); 80 for (Binding<FilterDefinition> entry : injector.findBindingsByType(FILTER_DEFS)) { 81 filterDefinitions.add(entry.getProvider().get()); 82 } 83 84 // Copy to a fixed-size array for speed of iteration. 85 return filterDefinitions.toArray(new FilterDefinition[filterDefinitions.size()]); 86 } 87 88 public synchronized void initPipeline(ServletContext servletContext) 89 throws ServletException { 90 91 //double-checked lock, prevents duplicate initialization 92 if (initialized) 93 return; 94 95 // Used to prevent duplicate initialization. 96 Set<Filter> initializedSoFar = Sets.newIdentityHashSet(); 97 98 for (FilterDefinition filterDefinition : filterDefinitions) { 99 filterDefinition.init(servletContext, injector, initializedSoFar); 100 } 101 102 //next, initialize servlets... 103 servletPipeline.init(servletContext, injector); 104 105 //everything was ok... 106 initialized = true; 107 } 108 109 public void dispatch(ServletRequest request, ServletResponse response, 110 FilterChain proceedingFilterChain) throws IOException, ServletException { 111 112 //lazy init of filter pipeline (OK by the servlet specification). This is needed 113 //in order for us not to force users to create a GuiceServletContextListener subclass. 114 if (!initialized) { 115 initPipeline(servletContext.get()); 116 } 117 118 //obtain the servlet pipeline to dispatch against 119 new FilterChainInvocation(filterDefinitions, servletPipeline, proceedingFilterChain) 120 .doFilter(withDispatcher(request, servletPipeline), response); 121 122 } 123 124 /** 125 * Used to create an proxy that dispatches either to the guice-servlet pipeline or the regular 126 * pipeline based on uri-path match. This proxy also provides minimal forwarding support. 127 * 128 * We cannot forward from a web.xml Servlet/JSP to a guice-servlet (because the filter pipeline 129 * is not called again). However, we can wrap requests with our own dispatcher to forward the 130 * *other* way. web.xml Servlets/JSPs can forward to themselves as per normal. 131 * 132 * This is not a problem cuz we intend for people to migrate from web.xml to guice-servlet, 133 * incrementally, but not the other way around (which, we should actively discourage). 134 */ 135 @SuppressWarnings({ "JavaDoc", "deprecation" }) 136 private ServletRequest withDispatcher(ServletRequest servletRequest, 137 final ManagedServletPipeline servletPipeline) { 138 139 // don't wrap the request if there are no servlets mapped. This prevents us from inserting our 140 // wrapper unless it's actually going to be used. This is necessary for compatibility for apps 141 // that downcast their HttpServletRequests to a concrete implementation. 142 if (!servletPipeline.hasServletsMapped()) { 143 return servletRequest; 144 } 145 146 HttpServletRequest request = (HttpServletRequest) servletRequest; 147 //noinspection OverlyComplexAnonymousInnerClass 148 return new HttpServletRequestWrapper(request) { 149 150 @Override 151 public RequestDispatcher getRequestDispatcher(String path) { 152 final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path); 153 154 return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path); 155 } 156 }; 157 } 158 159 public void destroyPipeline() { 160 //destroy servlets first 161 servletPipeline.destroy(); 162 163 //go down chain and destroy all our filters 164 Set<Filter> destroyedSoFar = Sets.newIdentityHashSet(); 165 for (FilterDefinition filterDefinition : filterDefinitions) { 166 filterDefinition.destroy(destroyedSoFar); 167 } 168 } 169 } 170