View Javadoc

1   /*
2    * $Id: Include.java 651946 2008-04-27 13:41:38Z apetrelli $
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  
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 &lt;s:property...&gt; 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   * &lt;-- One: --&gt;
77   * &lt;s:include value="myJsp.jsp" /&gt;
78   *
79   * &lt;-- Two: --&gt;
80   * &lt;s:include value="myJsp.jsp"&gt;
81   *    &lt;s:param name="param1" value="value2" /&gt;
82   *    &lt;s:param name="param2" value="value2" /&gt;
83   * &lt;/s:include&gt;
84   *
85   * &lt;-- Three: --&gt;
86   * &lt;s:include value="myJsp.jsp"&gt;
87   *    &lt;s:param name="param1"&gt;value1&lt;/s:param&gt;
88   *    &lt;s:param name="param2"&gt;value2&lt;/s:param&gt;
89   * &lt;/s:include&gt;
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         // Add URL
130         urlBuf.append(page);
131 
132         // Add request parameters
133         if (parameters.size() > 0) {
134             urlBuf.append('?');
135 
136             String concat = "";
137 
138             // Set parameters
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         // Include
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         // .. is illegal in an absolute path according to the Servlet Spec and will cause
198         // known problems on Orion application servers.
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         // don't use the default implementation of addParameter,
229         // instead, include tag requires that each parameter be a list of objects,
230         // just like the HTTP servlet interfaces are (String[])
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         // Include the resource
254         rd.include((HttpServletRequest) request, pageResponse);
255 
256         //write the response back to the JspWriter, using the correct encoding.
257         String encoding = getEncoding();
258 
259         if (encoding != null) {
260             //use the encoding specified in the property file
261             pageResponse.getContent().writeTo(writer, encoding);
262         } else {
263             //use the platform specific encoding
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             //if we are using a writer, we need to flush the
383             //data to the underlying outputstream.
384             //most containers do this - but it seems Jetty 4.0.5 doesn't
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 }