1
2
3
4
5
6
7
8
9
10
11
12
13
14 package net.sourceforge.statelessfilter.filter;
15
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.net.URI;
19 import java.net.URISyntaxException;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.Enumeration;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Properties;
26 import java.util.regex.Pattern;
27
28 import javax.servlet.Filter;
29 import javax.servlet.FilterChain;
30 import javax.servlet.FilterConfig;
31 import javax.servlet.ServletContext;
32 import javax.servlet.ServletException;
33 import javax.servlet.ServletRequest;
34 import javax.servlet.ServletResponse;
35 import javax.servlet.http.HttpServletRequest;
36 import javax.servlet.http.HttpServletResponse;
37
38 import net.sourceforge.statelessfilter.backend.ISessionBackend;
39 import net.sourceforge.statelessfilter.processor.IRequestProcessor;
40 import net.sourceforge.statelessfilter.spring.SpringContextChecker;
41 import net.sourceforge.statelessfilter.spring.SpringObjectInstantiationListener;
42 import net.sourceforge.statelessfilter.wrappers.BufferedHttpResponseWrapper;
43 import net.sourceforge.statelessfilter.wrappers.StatelessRequestWrapper;
44 import net.sourceforge.statelessfilter.wrappers.headers.HeaderBufferedHttpResponseWrapper;
45
46 import org.apache.commons.lang.StringUtils;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50
51
52
53
54
55
56
57
58 public class StatelessFilter implements Filter {
59 private static final String CONFIG_ATTRIBUTE_PREFIX = "attribute.";
60 private static final String CONFIG_DEFAULT_BACKEND = "default";
61 private static final String CONFIG_DIRTY = "dirtycheck";
62 private static final String CONFIG_INSTANTIATION_LISTENER = "instantiationListener";
63 private static final String CONFIG_LOCATION = "configurationLocation";
64 private static final String CONFIG_LOCATION_DEFAULT = "/stateless.properties";
65
66
67
68 private static final String CONFIG_PLUGIN_BACKEND = "stateless-backend.properties";
69 private static final String CONFIG_PLUGIN_BACKEND_IMPL = "backendImpl";
70
71
72
73 private static final String CONFIG_PLUGIN_PROCESSOR = "stateless-processor.properties";
74 private static final String CONFIG_PLUGIN_PROCESSOR_IMPL = "processorImpl";
75 private static final String DEBUG_INIT = "Stateless filter init...";
76 private static final String DEBUG_PROCESSING = "Processing ";
77 private static final String DOT = ".";
78 private static final String EXCLUDE_PATTERN_SEPARATOR = ",";
79 private static final String INFO_BACKEND = "Backend ";
80 private static final String INFO_BUFFERING = " enables output buffering.";
81 private static final String INFO_DEFAULT_BACKEND = "Default Session backend is ";
82 private static final String INFO_READY = " ready.";
83 private static final String INFO_REQUEST_PROCESSOR = "Request processor ";
84
85 private static Logger logger = LoggerFactory.getLogger(StatelessFilter.class);
86
87 private static final String WARN_BACKEND_NOT_FOUND1 = "Specified backend '";
88 private static final String WARN_BACKEND_NOT_FOUND2 = "' is not installed. Missing jar ? ";
89 private static final String WARN_LOAD_CONF = "Cannot load global configuration /stateless.properties. Using defaults";
90 private IObjectInstantiationListener instantiationListener = null;
91
92 private List<Pattern> excludePatterns = null;
93 private ServletContext servletContext = null;
94 protected Configuration configuration = new Configuration();
95
96
97
98
99
100 public static final String PARAM_EXCLUDE_PATTERN_LIST = "excludePatternList";
101
102
103
104
105 public void destroy() {
106
107 for (ISessionBackend backend : configuration.backends.values()) {
108 backend.destroy();
109 }
110 configuration.backends.clear();
111 }
112
113
114
115
116
117 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
118 ServletException {
119 HttpServletResponse httpResponse = (HttpServletResponse) response;
120
121
122 HttpServletRequest httpRequest = (HttpServletRequest) request;
123 if (isExcluded(httpRequest)) {
124 chain.doFilter(request, response);
125 return;
126 }
127
128
129 StatelessRequestWrapper statelessRequest = new StatelessRequestWrapper((HttpServletRequest) request,
130 configuration);
131
132
133 HttpServletResponse targetResponse = httpResponse;
134
135 BufferedHttpResponseWrapper bufferedResponse = null;
136 if (Configuration.BUFFERING_FULL.equals(configuration.isBufferingRequired)) {
137 bufferedResponse = new BufferedHttpResponseWrapper(httpResponse);
138 targetResponse = bufferedResponse;
139 }else
140 if (Configuration.BUFFERING_HEADERS.equals(configuration.isBufferingRequired)) {
141 targetResponse = new HeaderBufferedHttpResponseWrapper(statelessRequest,httpResponse);
142 }
143
144
145 if (configuration.requestProcessors != null && !configuration.requestProcessors.isEmpty()) {
146 IRequestProcessor rp = null;
147 for (int i = 0; i < configuration.requestProcessors.size(); i++) {
148 rp = configuration.requestProcessors.get(i);
149 try {
150 rp.preRequest(statelessRequest, targetResponse);
151 } catch (Exception e) {
152 throw new IOException(e.getMessage());
153 }
154 }
155 }
156
157
158 chain.doFilter(statelessRequest, targetResponse);
159
160
161 if (!statelessRequest.isSessionWritten()) {
162 statelessRequest.writeSession(statelessRequest, httpResponse);
163 }
164
165
166 if (configuration.requestProcessors != null && !configuration.requestProcessors.isEmpty()) {
167 IRequestProcessor rp = null;
168 for (int i = configuration.requestProcessors.size() - 1; i >= 0; i--) {
169 rp = configuration.requestProcessors.get(i);
170 try {
171 rp.postProcess(statelessRequest, targetResponse);
172 } catch (Exception e) {
173 throw new IOException(e.getMessage());
174 }
175 }
176 }
177
178
179 if (bufferedResponse != null) {
180 if (!bufferedResponse.performSend()) {
181 bufferedResponse.flushBuffer();
182 response.getOutputStream().write(bufferedResponse.getBuffer());
183 response.flushBuffer();
184 }
185 }
186 }
187
188
189
190
191 public void init(FilterConfig filterConfig) throws ServletException {
192 if (logger.isDebugEnabled()) {
193 logger.debug(DEBUG_INIT);
194 }
195
196
197 this.servletContext = filterConfig.getServletContext();
198
199
200 Properties globalProp = new Properties();
201
202
203 String configLocation = filterConfig.getInitParameter(CONFIG_LOCATION);
204 if (configLocation == null) {
205 configLocation = CONFIG_LOCATION_DEFAULT;
206 }
207 try {
208 globalProp.load(StatelessFilter.class.getResourceAsStream(configLocation));
209 } catch (Exception e) {
210 logger.warn(WARN_LOAD_CONF);
211
212 }
213
214
215
216 if (SpringContextChecker.checkForSpring(servletContext)) {
217
218 logger.info("Enabling Spring instantiation listener");
219
220 instantiationListener = new SpringObjectInstantiationListener();
221 instantiationListener.setServletContext(servletContext);
222 }
223
224
225 try {
226 initInstantiationListener(globalProp);
227 } catch (Exception e) {
228 throw new ServletException("Failed to load instantiation listener from /stateless.properties",
229 e);
230 }
231
232
233
234 try {
235 detectAndInitPlugins(CONFIG_PLUGIN_BACKEND, globalProp, IPlugin.TYPE_BACKEND);
236 detectAndInitPlugins(CONFIG_PLUGIN_PROCESSOR, globalProp, IPlugin.TYPE_REQUEST_PROCESSOR);
237
238 } catch (Exception e) {
239 throw new ServletException(e);
240 }
241
242
243 initAttributeMapping(globalProp);
244
245
246 initDefaultBackend(globalProp);
247
248
249 initDirtyState(globalProp);
250
251
252 initBuffering(globalProp);
253
254
255 initExcludedPattern(filterConfig);
256
257 checkConfiguration();
258 }
259
260 private void initBuffering(Properties globalProp) {
261 String buffering = globalProp.getProperty("buffering", Configuration.BUFFERING_FALSE);
262 applyBuffering(buffering, "Configuration");
263 }
264
265
266
267
268
269
270 private void applyBuffering(String mode, String source) {
271 if (Configuration.BUFFERING_FULL.equals(mode)
272 && !Configuration.BUFFERING_FULL.equals(configuration.isBufferingRequired)) {
273 configuration.isBufferingRequired = Configuration.BUFFERING_FULL;
274 if (logger.isInfoEnabled())
275 logger.info("Switching to buffering mode " + configuration.isBufferingRequired + " (" + source + ")");
276 } else if (Configuration.BUFFERING_HEADERS.equals(mode)
277 && Configuration.BUFFERING_FALSE.equals(configuration.isBufferingRequired)) {
278 configuration.isBufferingRequired = Configuration.BUFFERING_HEADERS;
279 if (logger.isInfoEnabled())
280 logger.info("Switching to buffering mode " + configuration.isBufferingRequired + " (" + source + ")");
281 }
282
283 }
284
285
286
287
288
289
290 private void checkConfiguration() throws ServletException {
291 if (this.configuration.backends.size() == 0) {
292 throw new ServletException(
293 "No backend installed. Please add one (stateless-session for instance) in the classpath");
294 }
295 }
296
297 private void initExcludedPattern(FilterConfig filterConfig) {
298 String excludedPatternList = filterConfig.getInitParameter(PARAM_EXCLUDE_PATTERN_LIST);
299 if (excludedPatternList != null) {
300 String[] splittedExcludedPatternList = excludedPatternList.split(EXCLUDE_PATTERN_SEPARATOR);
301 List<Pattern> patterns = new ArrayList<Pattern>();
302 Pattern pattern = null;
303 for (String element : splittedExcludedPatternList) {
304 pattern = Pattern.compile(element);
305 patterns.add(pattern);
306 }
307 this.excludePatterns = patterns;
308 }
309 }
310
311 private void detectAndInitPlugins(String propertyFile, Properties filterConfiguration, String type)
312 throws Exception {
313 Enumeration<URL> configurationURLs;
314
315 configurationURLs = StatelessFilter.class.getClassLoader().getResources(propertyFile);
316
317 URL url = null;
318 Properties pluginConfiguration = null;
319 InputStream is = null;
320 while (configurationURLs.hasMoreElements()) {
321 url = configurationURLs.nextElement();
322 if (logger.isDebugEnabled()) {
323 logger.debug(DEBUG_PROCESSING + url.toString());
324 }
325
326
327 pluginConfiguration = new Properties();
328 is = url.openStream();
329 pluginConfiguration.load(is);
330 is.close();
331
332
333 initPlugin(pluginConfiguration, filterConfiguration, type);
334 }
335
336 }
337
338 private void initAttributeMapping(Properties globalProp) throws ServletException {
339 for (Object key : globalProp.keySet()) {
340 String paramName = (String) key;
341 if (paramName.startsWith(CONFIG_ATTRIBUTE_PREFIX)) {
342 String attrName = paramName.substring(CONFIG_ATTRIBUTE_PREFIX.length());
343 String backend = globalProp.getProperty(paramName);
344 configuration.backendsAttributeMapping.put(attrName, backend);
345
346
347 if (!configuration.backends.containsKey(backend)) {
348 throw new ServletException("Attributes are mapped on backend " + backend
349 + " but it is not installed.");
350 }
351 }
352 }
353 }
354
355 private void initDefaultBackend(Properties globalProp) {
356 String defaultBack = globalProp.getProperty(CONFIG_DEFAULT_BACKEND);
357 if (defaultBack != null) {
358 if (configuration.backends.containsKey(defaultBack)) {
359 configuration.defaultBackend = defaultBack;
360 } else {
361 if (logger.isWarnEnabled()) {
362 logger.warn(WARN_BACKEND_NOT_FOUND1 + defaultBack + WARN_BACKEND_NOT_FOUND2);
363 }
364
365 }
366 }
367 }
368
369 private void initDirtyState(Properties globalProp) {
370 String useDirty = globalProp.getProperty(CONFIG_DIRTY);
371 if (Boolean.parseBoolean(useDirty)) {
372 configuration.useDirty = true;
373 }
374
375 if (logger.isInfoEnabled()) {
376 logger.info("Use dirty state: " + configuration.useDirty);
377 logger.info(INFO_DEFAULT_BACKEND + configuration.defaultBackend);
378 }
379 }
380
381
382
383
384
385
386
387
388
389
390 private void initInstantiationListener(Properties globalProp) throws ClassNotFoundException,
391 InstantiationException, IllegalAccessException {
392 String clazz = globalProp.getProperty(CONFIG_INSTANTIATION_LISTENER);
393 if (!StringUtils.isEmpty(clazz)) {
394 @SuppressWarnings("unchecked")
395 Class<IObjectInstantiationListener> backClazz = (Class<IObjectInstantiationListener>) Class.forName(clazz);
396
397
398 instantiationListener = backClazz.newInstance();
399 instantiationListener.setServletContext(servletContext);
400 logger.info("Using instantiation listener {}", clazz);
401 }
402 }
403
404
405
406
407
408
409
410
411 @SuppressWarnings("unchecked")
412 private void initPlugin(Properties backendProperties, Properties globalProperties, String type) throws Exception {
413
414 IPlugin plugin = null;
415
416 String location = CONFIG_PLUGIN_BACKEND_IMPL;
417 if (IPlugin.TYPE_REQUEST_PROCESSOR.equals(type)) {
418 location = CONFIG_PLUGIN_PROCESSOR_IMPL;
419 }
420
421 if (instantiationListener != null) {
422
423 plugin = (IPlugin) instantiationListener.getInstance((String) backendProperties.get(location));
424 }
425
426 if (plugin == null) {
427
428
429
430 String clazz = (String) backendProperties.get(location);
431 Class<IPlugin> backClazz = (Class<IPlugin>) Class.forName(clazz);
432
433
434 plugin = backClazz.newInstance();
435 }
436
437
438 HashMap<String, String> conf = new HashMap<String, String>();
439 String paramName = null;
440 String prefix = null;
441 String attrName = null;
442 for (Object key : globalProperties.keySet()) {
443 paramName = (String) key;
444 prefix = plugin.getId() + DOT;
445 if (paramName.startsWith(prefix)) {
446 attrName = paramName.substring(prefix.length());
447 conf.put(attrName, globalProperties.getProperty(paramName));
448 }
449 }
450
451
452 plugin.init(conf);
453
454
455 applyBuffering(plugin.isBufferingRequired(), plugin.getId());
456
457
458 if (IPlugin.TYPE_REQUEST_PROCESSOR.equals(type)) {
459 configuration.requestProcessors.add((IRequestProcessor) plugin);
460
461
462 if (logger.isInfoEnabled()) {
463 logger.info(INFO_REQUEST_PROCESSOR + plugin.getId() + INFO_READY);
464 }
465 }
466
467
468 if (IPlugin.TYPE_BACKEND.equals(type)) {
469 configuration.backends.put(plugin.getId(), (ISessionBackend) plugin);
470
471
472 if (logger.isInfoEnabled()) {
473 logger.info(INFO_BACKEND + plugin.getId() + INFO_READY);
474 }
475
476
477 if (StringUtils.isEmpty(configuration.defaultBackend)) {
478 configuration.defaultBackend = plugin.getId();
479 }
480 }
481
482 }
483
484
485
486
487
488
489
490
491 private boolean isExcluded(HttpServletRequest httpRequest) {
492 if (this.excludePatterns == null) {
493 return false;
494 }
495
496 String uri = httpRequest.getRequestURI();
497 if (logger.isDebugEnabled()) {
498 logger.debug("Check URI : " + uri);
499 }
500
501 try {
502 uri = new URI(uri).normalize().toString();
503
504 for (Pattern pattern : this.excludePatterns) {
505 if (pattern.matcher(uri).matches()) {
506 if (logger.isInfoEnabled()) {
507 logger.info("URI excluded : " + uri);
508 }
509 return true;
510 }
511 }
512
513 } catch (URISyntaxException e) {
514 logger.warn(
515 "The following URI has a bad syntax. The request will be processed by the filter. URI : " + uri, e);
516 }
517
518 return false;
519 }
520 }