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