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 }