1 package com.opensymphony.sitemesh.webapp;
2
3 import com.opensymphony.module.sitemesh.Config;
4 import com.opensymphony.module.sitemesh.Factory;
5 import com.opensymphony.sitemesh.Content;
6 import com.opensymphony.sitemesh.Decorator;
7 import com.opensymphony.sitemesh.DecoratorSelector;
8 import com.opensymphony.sitemesh.ContentProcessor;
9 import com.opensymphony.sitemesh.compatability.DecoratorMapper2DecoratorSelector;
10 import com.opensymphony.sitemesh.compatability.PageParser2ContentProcessor;
11
12 import javax.servlet;
13 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletResponse;
15 import java.io.IOException;
16
17 /**
18 * Core Filter for integrating SiteMesh into a Java web application.
19 *
20 * @author Joe Walnes
21 * @author Scott Farquhar
22 * @since SiteMesh 3
23 */
24 public class SiteMeshFilter implements Filter {
25
26 private FilterConfig filterConfig;
27 private ContainerTweaks containerTweaks;
28
29 public void init(FilterConfig filterConfig) {
30 this.filterConfig = filterConfig;
31 containerTweaks = new ContainerTweaks();
32 }
33
34 public void destroy() {
35 filterConfig = null;
36 containerTweaks = null;
37 }
38
39 /**
40 * Main method of the Filter.
41 * <p>Checks if the Filter has been applied this request. If not, parses the page
42 * and applies {@link com.opensymphony.module.sitemesh.Decorator} (if found).
43 */
44 public void doFilter(ServletRequest rq, ServletResponse rs, FilterChain chain)
45 throws IOException, ServletException {
46
47 HttpServletRequest request = (HttpServletRequest) rq;
48 HttpServletResponse response = (HttpServletResponse) rs;
49 ServletContext servletContext = filterConfig.getServletContext();
50
51 SiteMeshWebAppContext webAppContext = new SiteMeshWebAppContext(request, response, servletContext);
52
53 ContentProcessor contentProcessor = initContentProcessor(webAppContext);
54 DecoratorSelector decoratorSelector = initDecoratorSelector(webAppContext);
55
56 if (filterAlreadyAppliedForRequest(request)) {
57 // Prior to Servlet 2.4 spec, it was unspecified whether the filter should be called again upon an include().
58 chain.doFilter(request, response);
59 return;
60 }
61
62 if (!contentProcessor.handles(webAppContext)) {
63 // Optimization: If the content doesn't need to be processed, bypass SiteMesh.
64 chain.doFilter(request, response);
65 return;
66 }
67
68 if (containerTweaks.shouldAutoCreateSession()) {
69 // Some containers (such as Tomcat 4) will not allow sessions to be created in the decorator.
70 // (i.e after the response has been committed).
71 request.getSession(true);
72 }
73
74 try {
75
76 Content content = obtainContent(contentProcessor, webAppContext, request, response, chain);
77
78 if (content == null) {
79 return;
80 }
81
82 Decorator decorator = decoratorSelector.selectDecorator(content, webAppContext);
83 decorator.render(content, webAppContext);
84
85 } catch (IllegalStateException e) {
86 // Some containers (such as WebLogic) throw an IllegalStateException when an error page is served.
87 // It may be ok to ignore this. However, for safety it is propegated if possible.
88 if (!containerTweaks.shouldIgnoreIllegalStateExceptionOnErrorPage()) {
89 throw e;
90 }
91 } catch (RuntimeException e) {
92 if (containerTweaks.shouldLogUnhandledExceptions()) {
93 // Some containers (such as Tomcat 4) swallow RuntimeExceptions in filters.
94 servletContext.log("Unhandled exception occurred whilst decorating page", e);
95 }
96 throw e;
97 }
98
99 }
100
101 protected ContentProcessor initContentProcessor(SiteMeshWebAppContext webAppContext) {
102 // TODO: Remove heavy coupling on horrible SM2 Factory
103 Factory factory = Factory.getInstance(new Config(filterConfig));
104 factory.refresh();
105 return new PageParser2ContentProcessor(factory);
106 }
107
108 protected DecoratorSelector initDecoratorSelector(SiteMeshWebAppContext webAppContext) {
109 // TODO: Remove heavy coupling on horrible SM2 Factory
110 Factory factory = Factory.getInstance(new Config(filterConfig));
111 factory.refresh();
112 return new DecoratorMapper2DecoratorSelector(factory.getDecoratorMapper());
113 }
114
115 /**
116 * Continue in filter-chain, writing all content to buffer and parsing
117 * into returned {@link com.opensymphony.module.sitemesh.Page} object. If
118 * {@link com.opensymphony.module.sitemesh.Page} is not parseable, null is returned.
119 */
120 private Content obtainContent(ContentProcessor contentProcessor, SiteMeshWebAppContext webAppContext,
121 HttpServletRequest request, HttpServletResponse response, FilterChain chain)
122 throws IOException, ServletException {
123
124 ContentBufferingResponse contentBufferingResponse = new ContentBufferingResponse(response, contentProcessor, webAppContext);
125 chain.doFilter(request, contentBufferingResponse);
126 // TODO: check if another servlet or filter put a page object in the request
127 // Content result = request.getAttribute(PAGE);
128 // if (result == null) {
129 // // parse the page
130 // result = pageResponse.getPage();
131 // }
132 webAppContext.setUsingStream(contentBufferingResponse.isUsingStream());
133 return contentBufferingResponse.getContent();
134 }
135
136 private boolean filterAlreadyAppliedForRequest(HttpServletRequest request) {
137 final String alreadyAppliedKey = "com.opensymphony.sitemesh.APPLIED_ONCE";
138 if (request.getAttribute(alreadyAppliedKey) == Boolean.TRUE) {
139 return true;
140 } else {
141 request.setAttribute(alreadyAppliedKey, Boolean.TRUE);
142 return false;
143 }
144 }
145
146 }