Description: Let sub-classes of GuiceFilter customize the servlet filter pipeline Author: Stuart McCulloch Bug-Google: http://code.google.com/p/google-guice/issues/detail?id=618 Last-Update: 2015-01-20 diff --git a/extensions/servlet/src/com/google/inject/servlet/AbstractFilterPipeline.java b/extensions/servlet/src/com/google/inject/servlet/AbstractFilterPipeline.java new file mode 100644 index 0000000..ab8f746 --- /dev/null +++ b/extensions/servlet/src/com/google/inject/servlet/AbstractFilterPipeline.java @@ -0,0 +1,150 @@ +/** + * 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.Sets; +import com.google.inject.Injector; +import com.google.inject.Provider; + +import java.io.IOException; +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; + +/** + * Skeleton implementation of a central routing/dispatch class which uses a sequence of + * {@link FilterDefinition}s to filter requests before delegating to the servlet pipeline. + * + * @author dhanji@gmail.com (Dhanji R. Prasanna) + */ +public abstract class AbstractFilterPipeline implements FilterPipeline { + + private final AbstractServletPipeline servletPipeline; + private final Provider 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; + + protected AbstractFilterPipeline(Injector injector, AbstractServletPipeline servletPipeline, + Provider servletContext) { + this.injector = injector; + this.servletPipeline = servletPipeline; + this.servletContext = servletContext; + } + + /** + * @return {@code true} if any filter mappings exist; otherwise {@code false} + */ + protected abstract boolean hasFiltersMapped(); + + /** + * @return snapshot of the filter mappings currently defined for this pipeline + */ + protected abstract FilterDefinition[] filterDefinitions(); + + public synchronized void initPipeline(ServletContext servletContext) + throws ServletException { + + //double-checked lock, prevents duplicate initialization + if (initialized) + return; + + // Used to prevent duplicate initialization. + Set initializedSoFar = Sets.newIdentityHashSet(); + + for (FilterDefinition filterDefinition : filterDefinitions()) { + filterDefinition.init(servletContext, injector, initializedSoFar); + } + + //next, initialize servlets... + servletPipeline.init(servletContext, injector); + + //everything was ok... + initialized = true; + } + + 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. + * + * 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. + * + * 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 AbstractServletPipeline 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); + } + }; + } + + public void destroyPipeline() { + //destroy servlets first + servletPipeline.destroy(); + + //go down chain and destroy all our filters + Set destroyedSoFar = Sets.newIdentityHashSet(); + for (FilterDefinition filterDefinition : filterDefinitions()) { + filterDefinition.destroy(destroyedSoFar); + } + } +} diff --git a/extensions/servlet/src/com/google/inject/servlet/AbstractServletPipeline.java b/extensions/servlet/src/com/google/inject/servlet/AbstractServletPipeline.java new file mode 100644 index 0000000..811820a --- /dev/null +++ b/extensions/servlet/src/com/google/inject/servlet/AbstractServletPipeline.java @@ -0,0 +1,187 @@ +/** + * 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.base.Preconditions; +import com.google.common.collect.Sets; +import com.google.inject.Injector; + +import java.io.IOException; +import java.util.Set; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; + +/** + * Skeleton implementation of a wrapping dispatcher for servlets based on a sequence of + * {@link ServletDefinition}s, much like {@link AbstractFilterPipeline} is for filters. + * + * @author dhanji@gmail.com (Dhanji R. Prasanna) + */ +public abstract class AbstractServletPipeline { + + /** + * @return {@code true} if any servlet mappings exist; otherwise {@code false} + */ + protected abstract boolean hasServletsMapped(); + + /** + * @return snapshot of the servlet mappings currently defined for this pipeline + */ + protected abstract ServletDefinition[] servletDefinitions(); + + void init(ServletContext servletContext, Injector injector) throws ServletException { + Set initializedSoFar = Sets.newIdentityHashSet(); + + for (ServletDefinition servletDefinition : servletDefinitions()) { + servletDefinition.init(servletContext, injector, initializedSoFar); + } + } + + boolean service(ServletRequest request, ServletResponse response) + throws IOException, ServletException { + + //stop at the first matching servlet and service + for (ServletDefinition servletDefinition : servletDefinitions()) { + if (servletDefinition.service(request, response)) { + return true; + } + } + + //there was no match... + return false; + } + + void destroy() { + Set destroyedSoFar = Sets.newIdentityHashSet(); + for (ServletDefinition servletDefinition : servletDefinitions()) { + servletDefinition.destroy(destroyedSoFar); + } + } + + /** + * @return Returns a request dispatcher wrapped with a servlet mapped to + * the given path or null if no mapping was found. + */ + RequestDispatcher getRequestDispatcher(String path) { + final String newRequestUri = path; + + // TODO(dhanji): check servlet spec to see if the following is legal or not. + // Need to strip query string if requested... + + for (final ServletDefinition servletDefinition : servletDefinitions()) { + if (servletDefinition.shouldServe(path)) { + return new RequestDispatcher() { + public void forward(ServletRequest servletRequest, ServletResponse servletResponse) + throws ServletException, IOException { + Preconditions.checkState(!servletResponse.isCommitted(), + "Response has been committed--you can only call forward before" + + " committing the response (hint: don't flush buffers)"); + + // clear buffer before forwarding + servletResponse.resetBuffer(); + + ServletRequest requestToProcess; + if (servletRequest instanceof HttpServletRequest) { + requestToProcess = wrapRequest((HttpServletRequest)servletRequest, newRequestUri); + } else { + // This should never happen, but instead of throwing an exception + // we will allow a happy case pass thru for maximum tolerance to + // legacy (and internal) code. + requestToProcess = servletRequest; + } + + // now dispatch to the servlet + doServiceImpl(servletDefinition, requestToProcess, servletResponse); + } + + public void include(ServletRequest servletRequest, ServletResponse servletResponse) + throws ServletException, IOException { + // route to the target servlet + doServiceImpl(servletDefinition, servletRequest, servletResponse); + } + + private void doServiceImpl(ServletDefinition servletDefinition, ServletRequest servletRequest, + ServletResponse servletResponse) throws ServletException, IOException { + servletRequest.setAttribute(REQUEST_DISPATCHER_REQUEST, Boolean.TRUE); + + try { + servletDefinition.doService(servletRequest, servletResponse); + } finally { + servletRequest.removeAttribute(REQUEST_DISPATCHER_REQUEST); + } + } + }; + } + } + + //otherwise, can't process + return null; + } + + // visible for testing + static HttpServletRequest wrapRequest(HttpServletRequest request, String newUri) { + return new RequestDispatcherRequestWrapper(request, newUri); + } + + /** + * A Marker constant attribute that when present in the request indicates to Guice servlet that + * this request has been generated by a request dispatcher rather than the servlet pipeline. + * In accordance with section 8.4.2 of the Servlet 2.4 specification. + */ + static final String REQUEST_DISPATCHER_REQUEST = "javax.servlet.forward.servlet_path"; + + private static class RequestDispatcherRequestWrapper extends HttpServletRequestWrapper { + private final String newRequestUri; + + RequestDispatcherRequestWrapper(HttpServletRequest servletRequest, String newRequestUri) { + super(servletRequest); + this.newRequestUri = newRequestUri; + } + + @Override + public String getRequestURI() { + return newRequestUri; + } + + @Override + public StringBuffer getRequestURL() { + StringBuffer url = new StringBuffer(); + String scheme = getScheme(); + int port = getServerPort(); + + url.append(scheme); + url.append("://"); + url.append(getServerName()); + // port might be -1 in some cases (see java.net.URL.getPort) + if (port > 0 && + (("http".equals(scheme) && (port != 80)) || + ("https".equals(scheme) && (port != 443)))) { + url.append(':'); + url.append(port); + } + url.append(getRequestURI()); + + return (url); + } + } +} diff --git a/extensions/servlet/src/com/google/inject/servlet/FilterChainInvocation.java b/extensions/servlet/src/com/google/inject/servlet/FilterChainInvocation.java index b4112cf..bfe5a83 100644 --- a/extensions/servlet/src/com/google/inject/servlet/FilterChainInvocation.java +++ b/extensions/servlet/src/com/google/inject/servlet/FilterChainInvocation.java @@ -50,7 +50,7 @@ class FilterChainInvocation implements FilterChain { private final FilterDefinition[] filterDefinitions; private final FilterChain proceedingChain; - private final ManagedServletPipeline servletPipeline; + private final AbstractServletPipeline servletPipeline; //state variable tracks current link in filterchain private int index = -1; @@ -58,7 +58,7 @@ class FilterChainInvocation implements FilterChain { private boolean cleanedStacks = false; public FilterChainInvocation(FilterDefinition[] filterDefinitions, - ManagedServletPipeline servletPipeline, FilterChain proceedingChain) { + AbstractServletPipeline servletPipeline, FilterChain proceedingChain) { this.filterDefinitions = filterDefinitions; this.servletPipeline = servletPipeline; diff --git a/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java b/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java index ff1e5b6..76ece31 100644 --- a/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java +++ b/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java @@ -37,11 +37,11 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; /** - * An internal representation of a filter definition against a particular URI pattern. + * Defines a filter mapped to a URI pattern and performs the request filtering for that filter. * * @author dhanji@gmail.com (Dhanji R. Prasanna) */ -class FilterDefinition implements ProviderWithExtensionVisitor { +public class FilterDefinition implements ProviderWithExtensionVisitor { private final String pattern; private final Key filterKey; private final UriPatternMatcher patternMatcher; @@ -90,7 +90,7 @@ class FilterDefinition implements ProviderWithExtensionVisitor return uri != null && patternMatcher.matches(uri); } - public void init(final ServletContext servletContext, Injector injector, + void init(final ServletContext servletContext, Injector injector, Set initializedSoFar) throws ServletException { // This absolutely must be a singleton, and so is only initialized once. @@ -130,7 +130,7 @@ class FilterDefinition implements ProviderWithExtensionVisitor initializedSoFar.add(filter); } - public void destroy(Set destroyedSoFar) { + void destroy(Set destroyedSoFar) { // filters are always singletons Filter reference = filter.get(); @@ -150,7 +150,7 @@ class FilterDefinition implements ProviderWithExtensionVisitor } } - public Filter getFilterIfMatching(HttpServletRequest request) { + Filter getFilterIfMatching(HttpServletRequest request) { final String path = ServletUtils.getContextRelativePath(request); if (shouldFilter(path)) { diff --git a/extensions/servlet/src/com/google/inject/servlet/FilterPipeline.java b/extensions/servlet/src/com/google/inject/servlet/FilterPipeline.java index 985064b..c745d20 100644 --- a/extensions/servlet/src/com/google/inject/servlet/FilterPipeline.java +++ b/extensions/servlet/src/com/google/inject/servlet/FilterPipeline.java @@ -26,7 +26,7 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; /** - * An internal dispatcher for guice-servlet registered servlets and filters. + * A dispatcher abstraction for guice-servlet registered servlets and filters. * By default, we assume a Guice 1.0 style servlet module is in play. In other * words, we dispatch directly to the web.xml pipeline after setting up scopes. * @@ -39,10 +39,27 @@ import javax.servlet.ServletResponse; * @author dhanji@gmail.com (Dhanji R. Prasanna) */ @ImplementedBy(DefaultFilterPipeline.class) -interface FilterPipeline { +public interface FilterPipeline { + + /** + * Initializes the pipeline, putting it into service. + * + * @param context of the web application + */ void initPipeline(ServletContext context) throws ServletException; + + /** + * Destroys the pipeline, taking it out of service. + */ void destroyPipeline(); + /** + * Dispatches a request against the pipeline. + * + * @param request to dispatch + * @param response to populate + * @param defaultFilterChain for last resort filtering + */ void dispatch(ServletRequest request, ServletResponse response, FilterChain defaultFilterChain) throws IOException, ServletException; } diff --git a/extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java b/extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java index ba7a5af..0737b38 100644 --- a/extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java +++ b/extensions/servlet/src/com/google/inject/servlet/GuiceFilter.java @@ -89,7 +89,7 @@ public class GuiceFilter implements Filter { this(null); } - @Inject GuiceFilter(FilterPipeline filterPipeline) { + @Inject protected GuiceFilter(FilterPipeline filterPipeline) { injectedPipeline = filterPipeline; } diff --git a/extensions/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java b/extensions/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java index 538e10a..76bc269 100644 --- a/extensions/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java +++ b/extensions/servlet/src/com/google/inject/servlet/ManagedFilterPipeline.java @@ -16,7 +16,6 @@ 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; @@ -24,50 +23,41 @@ 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. + * Managed implementation of a central routing/dispatch class which handles lifecycle of managed + * filters, and delegates to a managed servlet pipeline. * * @author dhanji@gmail.com (Dhanji R. Prasanna) */ @Singleton -class ManagedFilterPipeline implements FilterPipeline{ +class ManagedFilterPipeline extends AbstractFilterPipeline { private final FilterDefinition[] filterDefinitions; - private final ManagedServletPipeline servletPipeline; - private final Provider 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 FILTER_DEFS = TypeLiteral.get(FilterDefinition.class); @Inject public ManagedFilterPipeline(Injector injector, ManagedServletPipeline servletPipeline, Provider servletContext) { - this.injector = injector; - this.servletPipeline = servletPipeline; - this.servletContext = servletContext; + super(injector, servletPipeline, servletContext); this.filterDefinitions = collectFilterDefinitions(injector); } + @Override + protected boolean hasFiltersMapped() { + return filterDefinitions.length > 0; + } + + @Override + protected FilterDefinition[] filterDefinitions() { + return filterDefinitions; + } + /** * Introspects the injector and collects all instances of bound {@code List} * into a master list. @@ -84,86 +74,4 @@ class ManagedFilterPipeline implements FilterPipeline{ // Copy to a fixed-size array for speed of iteration. return filterDefinitions.toArray(new FilterDefinition[filterDefinitions.size()]); } - - public synchronized void initPipeline(ServletContext servletContext) - throws ServletException { - - //double-checked lock, prevents duplicate initialization - if (initialized) - return; - - // Used to prevent duplicate initialization. - Set initializedSoFar = Sets.newIdentityHashSet(); - - for (FilterDefinition filterDefinition : filterDefinitions) { - filterDefinition.init(servletContext, injector, initializedSoFar); - } - - //next, initialize servlets... - servletPipeline.init(servletContext, injector); - - //everything was ok... - initialized = true; - } - - 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. - * - * 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. - * - * 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); - } - }; - } - - public void destroyPipeline() { - //destroy servlets first - servletPipeline.destroy(); - - //go down chain and destroy all our filters - Set destroyedSoFar = Sets.newIdentityHashSet(); - for (FilterDefinition filterDefinition : filterDefinitions) { - filterDefinition.destroy(destroyedSoFar); - } - } } diff --git a/extensions/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java b/extensions/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java index 455551a..ab58a8e 100644 --- a/extensions/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java +++ b/extensions/servlet/src/com/google/inject/servlet/ManagedServletPipeline.java @@ -15,27 +15,14 @@ */ package com.google.inject.servlet; -import com.google.common.base.Preconditions; 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.Singleton; import com.google.inject.TypeLiteral; -import java.io.IOException; import java.util.List; -import java.util.Set; - -import javax.servlet.RequestDispatcher; -import javax.servlet.ServletContext; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletRequestWrapper; /** * A wrapping dispatcher for servlets, in much the same way as {@link ManagedFilterPipeline} is for @@ -44,7 +31,7 @@ import javax.servlet.http.HttpServletRequestWrapper; * @author dhanji@gmail.com (Dhanji R. Prasanna) */ @Singleton -class ManagedServletPipeline { +class ManagedServletPipeline extends AbstractServletPipeline { private final ServletDefinition[] servletDefinitions; private static final TypeLiteral SERVLET_DEFS = TypeLiteral.get(ServletDefinition.class); @@ -54,10 +41,16 @@ class ManagedServletPipeline { this.servletDefinitions = collectServletDefinitions(injector); } - boolean hasServletsMapped() { + @Override + protected boolean hasServletsMapped() { return servletDefinitions.length > 0; } + @Override + protected ServletDefinition[] servletDefinitions() { + return servletDefinitions; + } + /** * Introspects the injector and collects all instances of bound {@code List} * into a master list. @@ -74,140 +67,4 @@ class ManagedServletPipeline { // Copy to a fixed size array for speed. return servletDefinitions.toArray(new ServletDefinition[servletDefinitions.size()]); } - - public void init(ServletContext servletContext, Injector injector) throws ServletException { - Set initializedSoFar = Sets.newIdentityHashSet(); - - for (ServletDefinition servletDefinition : servletDefinitions) { - servletDefinition.init(servletContext, injector, initializedSoFar); - } - } - - public boolean service(ServletRequest request, ServletResponse response) - throws IOException, ServletException { - - //stop at the first matching servlet and service - for (ServletDefinition servletDefinition : servletDefinitions) { - if (servletDefinition.service(request, response)) { - return true; - } - } - - //there was no match... - return false; - } - - public void destroy() { - Set destroyedSoFar = Sets.newIdentityHashSet(); - for (ServletDefinition servletDefinition : servletDefinitions) { - servletDefinition.destroy(destroyedSoFar); - } - } - - /** - * @return Returns a request dispatcher wrapped with a servlet mapped to - * the given path or null if no mapping was found. - */ - RequestDispatcher getRequestDispatcher(String path) { - final String newRequestUri = path; - - // TODO(dhanji): check servlet spec to see if the following is legal or not. - // Need to strip query string if requested... - - for (final ServletDefinition servletDefinition : servletDefinitions) { - if (servletDefinition.shouldServe(path)) { - return new RequestDispatcher() { - public void forward(ServletRequest servletRequest, ServletResponse servletResponse) - throws ServletException, IOException { - Preconditions.checkState(!servletResponse.isCommitted(), - "Response has been committed--you can only call forward before" - + " committing the response (hint: don't flush buffers)"); - - // clear buffer before forwarding - servletResponse.resetBuffer(); - - ServletRequest requestToProcess; - if (servletRequest instanceof HttpServletRequest) { - requestToProcess = wrapRequest((HttpServletRequest)servletRequest, newRequestUri); - } else { - // This should never happen, but instead of throwing an exception - // we will allow a happy case pass thru for maximum tolerance to - // legacy (and internal) code. - requestToProcess = servletRequest; - } - - // now dispatch to the servlet - doServiceImpl(servletDefinition, requestToProcess, servletResponse); - } - - public void include(ServletRequest servletRequest, ServletResponse servletResponse) - throws ServletException, IOException { - // route to the target servlet - doServiceImpl(servletDefinition, servletRequest, servletResponse); - } - - private void doServiceImpl(ServletDefinition servletDefinition, ServletRequest servletRequest, - ServletResponse servletResponse) throws ServletException, IOException { - servletRequest.setAttribute(REQUEST_DISPATCHER_REQUEST, Boolean.TRUE); - - try { - servletDefinition.doService(servletRequest, servletResponse); - } finally { - servletRequest.removeAttribute(REQUEST_DISPATCHER_REQUEST); - } - } - }; - } - } - - //otherwise, can't process - return null; - } - - // visible for testing - static HttpServletRequest wrapRequest(HttpServletRequest request, String newUri) { - return new RequestDispatcherRequestWrapper(request, newUri); - } - - /** - * A Marker constant attribute that when present in the request indicates to Guice servlet that - * this request has been generated by a request dispatcher rather than the servlet pipeline. - * In accordance with section 8.4.2 of the Servlet 2.4 specification. - */ - public static final String REQUEST_DISPATCHER_REQUEST = "javax.servlet.forward.servlet_path"; - - private static class RequestDispatcherRequestWrapper extends HttpServletRequestWrapper { - private final String newRequestUri; - - public RequestDispatcherRequestWrapper(HttpServletRequest servletRequest, String newRequestUri) { - super(servletRequest); - this.newRequestUri = newRequestUri; - } - - @Override - public String getRequestURI() { - return newRequestUri; - } - - @Override - public StringBuffer getRequestURL() { - StringBuffer url = new StringBuffer(); - String scheme = getScheme(); - int port = getServerPort(); - - url.append(scheme); - url.append("://"); - url.append(getServerName()); - // port might be -1 in some cases (see java.net.URL.getPort) - if (port > 0 && - (("http".equals(scheme) && (port != 80)) || - ("https".equals(scheme) && (port != 443)))) { - url.append(':'); - url.append(port); - } - url.append(getRequestURI()); - - return (url); - } - } } diff --git a/extensions/servlet/src/com/google/inject/servlet/ServletDefinition.java b/extensions/servlet/src/com/google/inject/servlet/ServletDefinition.java index 11328ed..285ff31 100644 --- a/extensions/servlet/src/com/google/inject/servlet/ServletDefinition.java +++ b/extensions/servlet/src/com/google/inject/servlet/ServletDefinition.java @@ -46,12 +46,11 @@ import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; /** - * An internal representation of a servlet definition mapped to a particular URI pattern. Also - * performs the request dispatch to that servlet. How nice and OO =) + * Defines a servlet mapped to a URI pattern and performs the request dispatch to that servlet. * * @author dhanji@gmail.com (Dhanji R. Prasanna) */ -class ServletDefinition implements ProviderWithExtensionVisitor { +public class ServletDefinition implements ProviderWithExtensionVisitor { private final String pattern; private final Key servletKey; private final UriPatternMatcher patternMatcher; @@ -100,7 +99,7 @@ class ServletDefinition implements ProviderWithExtensionVisitor initializedSoFar) throws ServletException { // This absolutely must be a singleton, and so is only initialized once. @@ -140,7 +139,7 @@ class ServletDefinition implements ProviderWithExtensionVisitor destroyedSoFar) { + void destroy(Set destroyedSoFar) { HttpServlet reference = httpServlet.get(); // Do nothing if this Servlet was invalid (usually due to not being scoped @@ -169,7 +168,7 @@ class ServletDefinition implements ProviderWithExtensionVisitor Date: Tue Jan 20 18:51:01 2015 +0000 Enhance logging in filter/servlet definitions diff --git a/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java b/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java index 76ece31..6819665 100644 --- a/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java +++ b/extensions/servlet/src/com/google/inject/servlet/FilterDefinition.java @@ -15,6 +15,7 @@ */ package com.google.inject.servlet; +import com.google.common.base.Strings; import com.google.common.collect.Iterators; import com.google.inject.Injector; import com.google.inject.Key; @@ -29,6 +30,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; import javax.servlet.Filter; import javax.servlet.FilterConfig; @@ -42,6 +45,8 @@ import javax.servlet.http.HttpServletRequest; * @author dhanji@gmail.com (Dhanji R. Prasanna) */ public class FilterDefinition implements ProviderWithExtensionVisitor { + private static final Logger logger = Logger.getLogger(FilterDefinition.class.getName()); + private final String pattern; private final Key filterKey; private final UriPatternMatcher patternMatcher; @@ -154,7 +159,11 @@ public class FilterDefinition implements ProviderWithExtensionVisitor { + private static final Logger logger = Logger.getLogger(ServletDefinition.class.getName()); + private final String pattern; private final Key servletKey; private final UriPatternMatcher patternMatcher; @@ -283,7 +288,14 @@ public class ServletDefinition implements ProviderWithExtensionVisitor