1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.components;
22
23 import java.io.IOException;
24 import java.io.OutputStreamWriter;
25 import java.io.PrintWriter;
26 import java.io.Writer;
27 import java.net.URLEncoder;
28 import java.util.ArrayList;
29 import java.util.Iterator;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Stack;
33 import java.util.StringTokenizer;
34
35 import javax.servlet.RequestDispatcher;
36 import javax.servlet.ServletException;
37 import javax.servlet.ServletOutputStream;
38 import javax.servlet.ServletRequest;
39 import javax.servlet.http.HttpServletRequest;
40 import javax.servlet.http.HttpServletResponse;
41 import javax.servlet.http.HttpServletResponseWrapper;
42
43 import org.apache.commons.logging.Log;
44 import org.apache.commons.logging.LogFactory;
45 import org.apache.struts2.views.annotations.StrutsTag;
46 import org.apache.struts2.views.annotations.StrutsTagAttribute;
47 import org.apache.struts2.RequestUtils;
48 import org.apache.struts2.StrutsConstants;
49 import org.apache.struts2.util.FastByteArrayOutputStream;
50
51 import com.opensymphony.xwork2.inject.Inject;
52 import com.opensymphony.xwork2.util.ValueStack;
53
54 /***
55 * <!-- START SNIPPET: javadoc -->
56 * <p>Include a servlet's output (result of servlet or a JSP page).</p>
57 * <p>Note: Any additional params supplied to the included page are <b>not</b> accessible within the rendered page
58 * through the <s:property...> tag!</p>
59 * <!-- END SNIPPET: javadoc -->
60 *
61 *
62 * <!-- START SNIPPET: params -->
63 * <ul>
64 * <li>value* (String) - jsp page to be included</li>
65 * </ul>
66 * <!-- END SNIPPET: params -->
67 *
68 *
69 * <p/> <b>Examples</b>
70 * <pre>
71 * <!-- START SNIPPET: example -->
72 * <-- One: -->
73 * <s:include value="myJsp.jsp" />
74 *
75 * <-- Two: -->
76 * <s:include value="myJsp.jsp">
77 * <s:param name="param1" value="value2" />
78 * <s:param name="param2" value="value2" />
79 * </s:include>
80 *
81 * <-- Three: -->
82 * <s:include value="myJsp.jsp">
83 * <s:param name="param1">value1</s:param>
84 * <s:param name="param2">value2<s:param>
85 * </s:include>
86 * <!-- END SNIPPET: example -->
87 *
88 * <!-- START SNIPPET: exampledescription -->
89 * Example one - do an include myJsp.jsp page
90 * Example two - do an include to myJsp.jsp page with parameters param1=value1 and param2=value2
91 * Example three - do an include to myJsp.jsp page with parameters param1=value1 and param2=value2
92 * <!-- END SNIPPET: exampledescription -->
93 * </pre>
94 *
95 */
96 @StrutsTag(name="include", tldTagClass="org.apache.struts2.views.jsp.IncludeTag", description="Include a servlet's output " +
97 "(result of servlet or a JSP page)")
98 public class Include extends Component {
99
100 private static final Log _log = LogFactory.getLog(Include.class);
101
102 private static String encoding;
103 private static boolean encodingDefined = true;
104
105 protected String value;
106 private HttpServletRequest req;
107 private HttpServletResponse res;
108 private static String defaultEncoding;
109
110 public Include(ValueStack stack, HttpServletRequest req, HttpServletResponse res) {
111 super(stack);
112 this.req = req;
113 this.res = res;
114 }
115
116 @Inject(StrutsConstants.STRUTS_I18N_ENCODING)
117 public static void setDefaultEncoding(String encoding) {
118 defaultEncoding = encoding;
119 }
120
121 public boolean end(Writer writer, String body) {
122 String page = findString(value, "value", "You must specify the URL to include. Example: /foo.jsp");
123 StringBuffer urlBuf = new StringBuffer();
124
125
126 urlBuf.append(page);
127
128
129 if (parameters.size() > 0) {
130 urlBuf.append('?');
131
132 String concat = "";
133
134
135 Iterator iter = parameters.entrySet().iterator();
136
137 while (iter.hasNext()) {
138 Map.Entry entry = (Map.Entry) iter.next();
139 Object name = entry.getKey();
140 List values = (List) entry.getValue();
141
142 for (int i = 0; i < values.size(); i++) {
143 urlBuf.append(concat);
144 urlBuf.append(name);
145 urlBuf.append('=');
146
147 try {
148 urlBuf.append(URLEncoder.encode(values.get(i).toString(), "UTF-8"));
149 } catch (Exception e) {
150 _log.warn("unable to url-encode "+values.get(i).toString()+", it will be ignored");
151 }
152
153 concat = "&";
154 }
155 }
156 }
157
158 String result = urlBuf.toString();
159
160
161 try {
162 include(result, writer, req, res);
163 } catch (Exception e) {
164 LogFactory.getLog(getClass()).warn("Exception thrown during include of " + result, e);
165 }
166
167 return super.end(writer, body);
168 }
169
170 @StrutsTagAttribute(description="The jsp/servlet output to include", required=true)
171 public void setValue(String value) {
172 this.value = value;
173 }
174
175 public static String getContextRelativePath(ServletRequest request, String relativePath) {
176 String returnValue;
177
178 if (relativePath.startsWith("/")) {
179 returnValue = relativePath;
180 } else if (!(request instanceof HttpServletRequest)) {
181 returnValue = relativePath;
182 } else {
183 HttpServletRequest hrequest = (HttpServletRequest) request;
184 String uri = (String) request.getAttribute("javax.servlet.include.servlet_path");
185
186 if (uri == null) {
187 uri = RequestUtils.getServletPath(hrequest);
188 }
189
190 returnValue = uri.substring(0, uri.lastIndexOf('/')) + '/' + relativePath;
191 }
192
193
194
195 if (returnValue.indexOf("..") != -1) {
196 Stack stack = new Stack();
197 StringTokenizer pathParts = new StringTokenizer(returnValue.replace('//', '/'), "/");
198
199 while (pathParts.hasMoreTokens()) {
200 String part = pathParts.nextToken();
201
202 if (!part.equals(".")) {
203 if (part.equals("..")) {
204 stack.pop();
205 } else {
206 stack.push(part);
207 }
208 }
209 }
210
211 StringBuffer flatPathBuffer = new StringBuffer();
212
213 for (int i = 0; i < stack.size(); i++) {
214 flatPathBuffer.append("/").append(stack.elementAt(i));
215 }
216
217 returnValue = flatPathBuffer.toString();
218 }
219
220 return returnValue;
221 }
222
223 public void addParameter(String key, Object value) {
224
225
226
227 if (value != null) {
228 List currentValues = (List) parameters.get(key);
229
230 if (currentValues == null) {
231 currentValues = new ArrayList();
232 parameters.put(key, currentValues);
233 }
234
235 currentValues.add(value);
236 }
237 }
238
239 public static void include(String aResult, Writer writer, ServletRequest request, HttpServletResponse response) throws ServletException, IOException {
240 String resourcePath = getContextRelativePath(request, aResult);
241 RequestDispatcher rd = request.getRequestDispatcher(resourcePath);
242
243 if (rd == null) {
244 throw new ServletException("Not a valid resource path:" + resourcePath);
245 }
246
247 PageResponse pageResponse = new PageResponse(response);
248
249
250 rd.include((HttpServletRequest) request, pageResponse);
251
252
253 String encoding = getEncoding();
254
255 if (encoding != null) {
256
257 pageResponse.getContent().writeTo(writer, encoding);
258 } else {
259
260 pageResponse.getContent().writeTo(writer, null);
261 }
262 }
263
264 /***
265 * Get the encoding specified by the property 'struts.i18n.encoding' in struts.properties,
266 * or return the default platform encoding if not specified.
267 * <p/>
268 * Note that if the property is not initially defined, this will return the system default,
269 * even if the property is later defined. This is mainly for performance reasons. Undefined
270 * properties throw exceptions, which are a costly operation.
271 * <p/>
272 * If the property is initially defined, it is read every time, until is is undefined, and then
273 * the system default is used.
274 * <p/>
275 * Why not cache it completely? Some applications will wish to be able to dynamically set the
276 * encoding at runtime.
277 *
278 * @return The encoding to be used.
279 */
280 private static String getEncoding() {
281 if (encodingDefined) {
282 try {
283 encoding = defaultEncoding;
284 } catch (IllegalArgumentException e) {
285 encoding = System.getProperty("file.encoding");
286 encodingDefined = false;
287 }
288 }
289
290 return encoding;
291 }
292
293
294 /***
295 * Implementation of ServletOutputStream that stores all data written
296 * to it in a temporary buffer accessible from {@link #getBuffer()} .
297 *
298 * @author <a href="joe@truemesh.com">Joe Walnes</a>
299 * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
300 */
301 static final class PageOutputStream extends ServletOutputStream {
302
303 private FastByteArrayOutputStream buffer;
304
305
306 public PageOutputStream() {
307 buffer = new FastByteArrayOutputStream();
308 }
309
310
311 /***
312 * Return all data that has been written to this OutputStream.
313 */
314 public FastByteArrayOutputStream getBuffer() throws IOException {
315 flush();
316
317 return buffer;
318 }
319
320 public void close() throws IOException {
321 buffer.close();
322 }
323
324 public void flush() throws IOException {
325 buffer.flush();
326 }
327
328 public void write(byte[] b, int o, int l) throws IOException {
329 buffer.write(b, o, l);
330 }
331
332 public void write(int i) throws IOException {
333 buffer.write(i);
334 }
335
336 public void write(byte[] b) throws IOException {
337 buffer.write(b);
338 }
339 }
340
341
342 /***
343 * Simple wrapper to HTTPServletResponse that will allow getWriter()
344 * and getResponse() to be called as many times as needed without
345 * causing conflicts.
346 * <p/>
347 * The underlying outputStream is a wrapper around
348 * {@link PageOutputStream} which will store
349 * the written content to a buffer.
350 * <p/>
351 * This buffer can later be retrieved by calling {@link #getContent}.
352 *
353 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
354 * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
355 */
356 static final class PageResponse extends HttpServletResponseWrapper {
357
358 protected PrintWriter pagePrintWriter;
359 protected ServletOutputStream outputStream;
360 private PageOutputStream pageOutputStream = null;
361
362
363 /***
364 * Create PageResponse wrapped around an existing HttpServletResponse.
365 */
366 public PageResponse(HttpServletResponse response) {
367 super(response);
368 }
369
370
371 /***
372 * Return the content buffered inside the {@link PageOutputStream}.
373 *
374 * @return
375 * @throws IOException
376 */
377 public FastByteArrayOutputStream getContent() throws IOException {
378
379
380
381 if (pagePrintWriter != null) {
382 pagePrintWriter.flush();
383 }
384
385 return ((PageOutputStream) getOutputStream()).getBuffer();
386 }
387
388 /***
389 * Return instance of {@link PageOutputStream}
390 * allowing all data written to stream to be stored in temporary buffer.
391 */
392 public ServletOutputStream getOutputStream() throws IOException {
393 if (pageOutputStream == null) {
394 pageOutputStream = new PageOutputStream();
395 }
396
397 return pageOutputStream;
398 }
399
400 /***
401 * Return PrintWriter wrapper around PageOutputStream.
402 */
403 public PrintWriter getWriter() throws IOException {
404 if (pagePrintWriter == null) {
405 pagePrintWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding()));
406 }
407
408 return pagePrintWriter;
409 }
410 }
411 }