View Javadoc

1   /*
2    * $Id: Include.java 497654 2007-01-19 00:21:57Z rgielen $
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
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 &lt;s:property...&gt; 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   * &lt;-- One: --&gt;
73   * &lt;s:include value="myJsp.jsp" /&gt;
74   *
75   * &lt;-- Two: --&gt;
76   * &lt;s:include value="myJsp.jsp"&gt;
77   *    &lt;s:param name="param1" value="value2" /&gt;
78   *    &lt;s:param name="param2" value="value2" /&gt;
79   * &lt;/s:include&gt;
80   *
81   * &lt;-- Three: --&gt;
82   * &lt;s:include value="myJsp.jsp"&gt;
83   *    &lt;s:param name="param1"&gt;value1&lt;/s:param&gt;
84   *    &lt;s:param name="param2"&gt;value2&lt;s:param&gt;
85   * &lt;/s:include&gt;
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         // Add URL
126         urlBuf.append(page);
127 
128         // Add request parameters
129         if (parameters.size() > 0) {
130             urlBuf.append('?');
131 
132             String concat = "";
133 
134             // Set parameters
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         // Include
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         // .. is illegal in an absolute path according to the Servlet Spec and will cause
194         // known problems on Orion application servers.
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         // don't use the default implementation of addParameter,
225         // instead, include tag requires that each parameter be a list of objects,
226         // just like the HTTP servlet interfaces are (String[])
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         // Include the resource
250         rd.include((HttpServletRequest) request, pageResponse);
251 
252         //write the response back to the JspWriter, using the correct encoding.
253         String encoding = getEncoding();
254 
255         if (encoding != null) {
256             //use the encoding specified in the property file
257             pageResponse.getContent().writeTo(writer, encoding);
258         } else {
259             //use the platform specific encoding
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             //if we are using a writer, we need to flush the
379             //data to the underlying outputstream.
380             //most containers do this - but it seems Jetty 4.0.5 doesn't
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 }