View Javadoc

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