FilterDefinition.java revision 71fe73ed4c6bcccbc6b71be1061435300b195c2f
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 */
16package com.google.inject.servlet;
17
18import com.google.inject.Injector;
19import com.google.inject.Key;
20import com.google.inject.Scopes;
21import com.google.inject.internal.util.Iterators;
22import com.google.inject.spi.BindingTargetVisitor;
23import com.google.inject.spi.ProviderInstanceBinding;
24import com.google.inject.spi.ProviderWithExtensionVisitor;
25
26import java.io.IOException;
27import java.util.Collections;
28import java.util.Enumeration;
29import java.util.HashMap;
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.atomic.AtomicReference;
33import javax.servlet.Filter;
34import javax.servlet.FilterConfig;
35import javax.servlet.ServletContext;
36import javax.servlet.ServletException;
37import javax.servlet.ServletRequest;
38import javax.servlet.ServletResponse;
39import javax.servlet.http.HttpServletRequest;
40
41/**
42 * An internal representation of a filter definition against a particular URI pattern.
43 *
44 * @author dhanji@gmail.com (Dhanji R. Prasanna)
45 */
46class FilterDefinition implements ProviderWithExtensionVisitor<FilterDefinition> {
47  private final String pattern;
48  private final Key<? extends Filter> filterKey;
49  private final UriPatternMatcher patternMatcher;
50  private final Map<String, String> initParams;
51  // set only if this was bound to an instance of a Filter.
52  private final Filter filterInstance;
53
54  // always set after init is called.
55  private final AtomicReference<Filter> filter = new AtomicReference<Filter>();
56
57  public FilterDefinition(String pattern, Key<? extends Filter> filterKey,
58      UriPatternMatcher patternMatcher, Map<String, String> initParams, Filter filterInstance) {
59    this.pattern = pattern;
60    this.filterKey = filterKey;
61    this.patternMatcher = patternMatcher;
62    this.initParams = Collections.unmodifiableMap(new HashMap<String, String>(initParams));
63    this.filterInstance = filterInstance;
64  }
65
66  public FilterDefinition get() {
67    return this;
68  }
69
70  public <B, V> V acceptExtensionVisitor(BindingTargetVisitor<B, V> visitor,
71      ProviderInstanceBinding<? extends B> binding) {
72    if(visitor instanceof ServletModuleTargetVisitor) {
73      if(filterInstance != null) {
74        return ((ServletModuleTargetVisitor<B, V>)visitor).visit(
75            new InstanceFilterBindingImpl(initParams,
76                pattern,
77                filterInstance,
78                patternMatcher));
79      } else {
80        return ((ServletModuleTargetVisitor<B, V>)visitor).visit(
81            new LinkedFilterBindingImpl(initParams,
82                pattern,
83                filterKey,
84                patternMatcher));
85      }
86    } else {
87      return visitor.visit(binding);
88    }
89  }
90
91  private boolean shouldFilter(String uri) {
92    return patternMatcher.matches(uri);
93  }
94
95  public void init(final ServletContext servletContext, Injector injector,
96      Set<Filter> initializedSoFar) throws ServletException {
97
98    // This absolutely must be a singleton, and so is only initialized once.
99    if (!Scopes.isSingleton(injector.getBinding(filterKey))) {
100      throw new ServletException("Filters must be bound as singletons. "
101        + filterKey + " was not bound in singleton scope.");
102    }
103
104    Filter filter = injector.getInstance(filterKey);
105    this.filter.set(filter);
106
107    // Only fire init() if this Singleton filter has not already appeared earlier
108    // in the filter chain.
109    if (initializedSoFar.contains(filter)) {
110      return;
111    }
112
113    //initialize our filter with the configured context params and servlet context
114    filter.init(new FilterConfig() {
115      public String getFilterName() {
116        return filterKey.toString();
117      }
118
119      public ServletContext getServletContext() {
120        return servletContext;
121      }
122
123      public String getInitParameter(String s) {
124        return initParams.get(s);
125      }
126
127      public Enumeration getInitParameterNames() {
128        return Iterators.asEnumeration(initParams.keySet().iterator());
129      }
130    });
131
132    initializedSoFar.add(filter);
133  }
134
135  public void destroy(Set<Filter> destroyedSoFar) {
136    // filters are always singletons
137    Filter reference = filter.get();
138
139    // Do nothing if this Filter was invalid (usually due to not being scoped
140    // properly), or was already destroyed. According to Servlet Spec: it is
141    // "out of service", and does not need to be destroyed.
142    // Also prevent duplicate destroys to the same singleton that may appear
143    // more than once on the filter chain.
144    if (null == reference || destroyedSoFar.contains(reference)) {
145      return;
146    }
147
148    try {
149      reference.destroy();
150    } finally {
151      destroyedSoFar.add(reference);
152    }
153  }
154
155  public void doFilter(ServletRequest servletRequest,
156      ServletResponse servletResponse, FilterChainInvocation filterChainInvocation)
157      throws IOException, ServletException {
158
159    final String path = ((HttpServletRequest) servletRequest).getServletPath();
160
161    if (shouldFilter(path)) {
162      filter.get()
163            .doFilter(servletRequest, servletResponse, filterChainInvocation);
164
165    } else {
166      //otherwise proceed down chain anyway
167      filterChainInvocation.doFilter(servletRequest, servletResponse);
168    }
169  }
170
171  //VisibleForTesting
172  Filter getFilter() {
173    return filter.get();
174  }
175}
176