/* * Copyright (C) 2008 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.google.inject.servlet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.inject.Binding; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import java.io.IOException; import java.util.List; import java.util.Set; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * Central routing/dispatch class handles lifecycle of managed filters, and delegates to the servlet * pipeline. * * @author dhanji@gmail.com (Dhanji R. Prasanna) */ @Singleton class ManagedFilterPipeline implements FilterPipeline { private final FilterDefinition[] filterDefinitions; private final ManagedServletPipeline servletPipeline; private final Provider<ServletContext> servletContext; //Unfortunately, we need the injector itself in order to create filters + servlets private final Injector injector; //Guards a DCL, so needs to be volatile private volatile boolean initialized = false; private static final TypeLiteral<FilterDefinition> FILTER_DEFS = TypeLiteral.get(FilterDefinition.class); @Inject public ManagedFilterPipeline( Injector injector, ManagedServletPipeline servletPipeline, Provider<ServletContext> servletContext) { this.injector = injector; this.servletPipeline = servletPipeline; this.servletContext = servletContext; this.filterDefinitions = collectFilterDefinitions(injector); } /** * Introspects the injector and collects all instances of bound {@code List<FilterDefinition>} * into a master list. * * <p>We have a guarantee that {@link com.google.inject.Injector#getBindings()} returns a map that * preserves insertion order in entry-set iterators. */ private FilterDefinition[] collectFilterDefinitions(Injector injector) { List<FilterDefinition> filterDefinitions = Lists.newArrayList(); for (Binding<FilterDefinition> entry : injector.findBindingsByType(FILTER_DEFS)) { filterDefinitions.add(entry.getProvider().get()); } // Copy to a fixed-size array for speed of iteration. return filterDefinitions.toArray(new FilterDefinition[filterDefinitions.size()]); } @Override public synchronized void initPipeline(ServletContext servletContext) throws ServletException { //double-checked lock, prevents duplicate initialization if (initialized) return; // Used to prevent duplicate initialization. Set<Filter> initializedSoFar = Sets.newIdentityHashSet(); for (FilterDefinition filterDefinition : filterDefinitions) { filterDefinition.init(servletContext, injector, initializedSoFar); } //next, initialize servlets... servletPipeline.init(servletContext, injector); //everything was ok... initialized = true; } @Override public void dispatch( ServletRequest request, ServletResponse response, FilterChain proceedingFilterChain) throws IOException, ServletException { //lazy init of filter pipeline (OK by the servlet specification). This is needed //in order for us not to force users to create a GuiceServletContextListener subclass. if (!initialized) { initPipeline(servletContext.get()); } //obtain the servlet pipeline to dispatch against new FilterChainInvocation(filterDefinitions, servletPipeline, proceedingFilterChain) .doFilter(withDispatcher(request, servletPipeline), response); } /** * Used to create an proxy that dispatches either to the guice-servlet pipeline or the regular * pipeline based on uri-path match. This proxy also provides minimal forwarding support. * * <p>We cannot forward from a web.xml Servlet/JSP to a guice-servlet (because the filter pipeline * is not called again). However, we can wrap requests with our own dispatcher to forward the * *other* way. web.xml Servlets/JSPs can forward to themselves as per normal. * * <p>This is not a problem cuz we intend for people to migrate from web.xml to guice-servlet, * incrementally, but not the other way around (which, we should actively discourage). */ @SuppressWarnings({"JavaDoc", "deprecation"}) private ServletRequest withDispatcher( ServletRequest servletRequest, final ManagedServletPipeline servletPipeline) { // don't wrap the request if there are no servlets mapped. This prevents us from inserting our // wrapper unless it's actually going to be used. This is necessary for compatibility for apps // that downcast their HttpServletRequests to a concrete implementation. if (!servletPipeline.hasServletsMapped()) { return servletRequest; } HttpServletRequest request = (HttpServletRequest) servletRequest; //noinspection OverlyComplexAnonymousInnerClass return new HttpServletRequestWrapper(request) { @Override public RequestDispatcher getRequestDispatcher(String path) { final RequestDispatcher dispatcher = servletPipeline.getRequestDispatcher(path); return (null != dispatcher) ? dispatcher : super.getRequestDispatcher(path); } }; } @Override public void destroyPipeline() { //destroy servlets first servletPipeline.destroy(); //go down chain and destroy all our filters Set<Filter> destroyedSoFar = Sets.newIdentityHashSet(); for (FilterDefinition filterDefinition : filterDefinitions) { filterDefinition.destroy(destroyedSoFar); } } }