1 /* This software is published under the terms of the OpenSymphony Software
2 * License version 1.1, of which a copy has been included with this
3 * distribution in the LICENSE.txt file. */
4 package com.opensymphony.module.sitemesh.filter;
5
6 import com.opensymphony.module.sitemesh.Factory;
7 import com.opensymphony.module.sitemesh.Page;
8
9 import javax.servlet.ServletOutputStream;
10 import javax.servlet.http.HttpServletResponse;
11 import javax.servlet.http.HttpServletResponseWrapper;
12 import java.io.IOException;
13 import java.io.PrintWriter;
14
15 /**
16 * Implementation of HttpServletResponseWrapper that captures page data instead of
17 * sending to the writer.
18 *
19 * <p>Should be used in filter-chains or when forwarding/including pages
20 * using a RequestDispatcher.</p>
21 *
22 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
23 * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
24 * @version $Revision: 1.11 $
25 */
26 public final class PageResponseWrapper extends HttpServletResponseWrapper {
27
28 private final RoutablePrintWriter routablePrintWriter;
29 private final RoutableServletOutputStream routableServletOutputStream;
30 private final Factory factory;
31
32 private Buffer buffer;
33 private boolean aborted = false;
34 private boolean parseablePage = false;
35
36 public PageResponseWrapper(final HttpServletResponse response, Factory factory) {
37 super(response);
38 this.factory = factory;
39
40 routablePrintWriter = new RoutablePrintWriter(new RoutablePrintWriter.DestinationFactory() {
41 public PrintWriter activateDestination() throws IOException {
42 return response.getWriter();
43 }
44 });
45 routableServletOutputStream = new RoutableServletOutputStream(new RoutableServletOutputStream.DestinationFactory() {
46 public ServletOutputStream create() throws IOException {
47 return response.getOutputStream();
48 }
49 });
50 }
51
52 /**
53 * Set the content-type of the request and store it so it can
54 * be passed to the {@link com.opensymphony.module.sitemesh.PageParser}.
55 */
56 public void setContentType(String type) {
57 super.setContentType(type);
58
59 if (type != null) {
60 // this is the content type + charset. eg: text/html;charset=UTF-8
61 int offset = type.lastIndexOf("charset=");
62 String encoding = null;
63 if (offset != -1)
64 encoding = extractContentTypeValue(type, offset + 8);
65 String contentType = extractContentTypeValue(type, 0);
66
67 if (factory.shouldParsePage(contentType)) {
68 activateSiteMesh(contentType, encoding);
69 }
70 }
71
72 }
73
74 private void activateSiteMesh(String contentType, String encoding) {
75 if (parseablePage) {
76 return; // already activated
77 }
78 parseablePage = true;
79 buffer = new Buffer(factory, contentType, encoding);
80 routablePrintWriter.updateDestination(new RoutablePrintWriter.DestinationFactory() {
81 public PrintWriter activateDestination() {
82 return buffer.getWriter();
83 }
84 });
85 routableServletOutputStream.updateDestination(new RoutableServletOutputStream.DestinationFactory() {
86 public ServletOutputStream create() {
87 return buffer.getOutputStream();
88 }
89 });
90 }
91
92 private String extractContentTypeValue(String type, int startIndex) {
93 if (startIndex < 0)
94 return null;
95
96 // Skip over any leading spaces
97 while (startIndex < type.length() && type.charAt(startIndex) == ' ')
98 startIndex++;
99
100 if (startIndex >= type.length())
101 return null;
102
103 int endIndex = startIndex;
104
105 if (type.charAt(startIndex) == '"') {
106 startIndex++;
107 endIndex = type.indexOf('"', startIndex);
108 if (endIndex == -1)
109 endIndex = type.length();
110 } else {
111 // Scan through until we hit either the end of the string or a
112 // special character (as defined in RFC-2045). Note that we ignore '/'
113 // since we want to capture it as part of the value.
114 char ch;
115 while (endIndex < type.length() && (ch = type.charAt(endIndex)) != ' ' && ch != ';'
116 && ch != '(' && ch != ')' && ch != '[' && ch != ']' && ch != '<' && ch != '>'
117 && ch != ':' && ch != ',' && ch != '=' && ch != '?' && ch != '@' && ch!= '"'
118 && ch !='\\')
119 endIndex++;
120 }
121 return type.substring(startIndex, endIndex);
122 }
123
124 /** Prevent content-length being set if page is parseable. */
125 public void setContentLength(int contentLength) {
126 if (!parseablePage) super.setContentLength(contentLength);
127 }
128
129 /** Prevent content-length being set if page is parseable. */
130 public void setHeader(String name, String value) {
131 if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
132 setContentType(value);
133 } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
134 super.setHeader(name, value);
135 }
136 }
137
138 /** Prevent content-length being set if page is parseable. */
139 public void addHeader(String name, String value) {
140 if (name.toLowerCase().equals("content-type")) { // ensure ContentType is always set through setContentType()
141 setContentType(value);
142 } else if (!parseablePage || !name.toLowerCase().equals("content-length")) {
143 super.addHeader(name, value);
144 }
145 }
146
147 /**
148 * Prevent 'not modified' (304) HTTP status from being sent if page is parseable
149 * (so web-server/browser doesn't cache contents).
150 */
151 public void setStatus(int sc) {
152 if (!parseablePage || sc != HttpServletResponse.SC_NOT_MODIFIED) {
153 super.setStatus(sc);
154 }
155 }
156
157 public ServletOutputStream getOutputStream() {
158 return routableServletOutputStream;
159 }
160
161 public PrintWriter getWriter() {
162 return routablePrintWriter;
163 }
164
165 public Page getPage() throws IOException {
166 if (aborted || !parseablePage) {
167 return null;
168 } else {
169 return buffer.parse();
170 }
171 }
172
173 public void sendError(int sc) throws IOException {
174 aborted = true;
175 super.sendError(sc);
176 }
177
178 public void sendError(int sc, String msg) throws IOException {
179 aborted = true;
180 super.sendError(sc, msg);
181 }
182
183 public void sendRedirect(String location) throws IOException {
184 aborted = true;
185 super.sendRedirect(location);
186 }
187
188 public boolean isUsingStream() {
189 return buffer != null && buffer.isUsingStream();
190 }
191 }