001: /*
002: * $Id: Include.java 497654 2007-01-19 00:21:57Z rgielen $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.components;
022:
023: import java.io.IOException;
024: import java.io.OutputStreamWriter;
025: import java.io.PrintWriter;
026: import java.io.Writer;
027: import java.net.URLEncoder;
028: import java.util.ArrayList;
029: import java.util.Iterator;
030: import java.util.List;
031: import java.util.Map;
032: import java.util.Stack;
033: import java.util.StringTokenizer;
034:
035: import javax.servlet.RequestDispatcher;
036: import javax.servlet.ServletException;
037: import javax.servlet.ServletOutputStream;
038: import javax.servlet.ServletRequest;
039: import javax.servlet.http.HttpServletRequest;
040: import javax.servlet.http.HttpServletResponse;
041: import javax.servlet.http.HttpServletResponseWrapper;
042:
043: import org.apache.commons.logging.Log;
044: import org.apache.commons.logging.LogFactory;
045: import org.apache.struts2.views.annotations.StrutsTag;
046: import org.apache.struts2.views.annotations.StrutsTagAttribute;
047: import org.apache.struts2.RequestUtils;
048: import org.apache.struts2.StrutsConstants;
049: import org.apache.struts2.util.FastByteArrayOutputStream;
050:
051: import com.opensymphony.xwork2.inject.Inject;
052: import com.opensymphony.xwork2.util.ValueStack;
053:
054: /**
055: * <!-- START SNIPPET: javadoc -->
056: * <p>Include a servlet's output (result of servlet or a JSP page).</p>
057: * <p>Note: Any additional params supplied to the included page are <b>not</b> accessible within the rendered page
058: * through the <s:property...> tag!</p>
059: * <!-- END SNIPPET: javadoc -->
060: *
061: *
062: * <!-- START SNIPPET: params -->
063: * <ul>
064: * <li>value* (String) - jsp page to be included</li>
065: * </ul>
066: * <!-- END SNIPPET: params -->
067: *
068: *
069: * <p/> <b>Examples</b>
070: * <pre>
071: * <!-- START SNIPPET: example -->
072: * <-- One: -->
073: * <s:include value="myJsp.jsp" />
074: *
075: * <-- Two: -->
076: * <s:include value="myJsp.jsp">
077: * <s:param name="param1" value="value2" />
078: * <s:param name="param2" value="value2" />
079: * </s:include>
080: *
081: * <-- Three: -->
082: * <s:include value="myJsp.jsp">
083: * <s:param name="param1">value1</s:param>
084: * <s:param name="param2">value2<s:param>
085: * </s:include>
086: * <!-- END SNIPPET: example -->
087: *
088: * <!-- START SNIPPET: exampledescription -->
089: * Example one - do an include myJsp.jsp page
090: * Example two - do an include to myJsp.jsp page with parameters param1=value1 and param2=value2
091: * Example three - do an include to myJsp.jsp page with parameters param1=value1 and param2=value2
092: * <!-- END SNIPPET: exampledescription -->
093: * </pre>
094: *
095: */
096: @StrutsTag(name="include",tldTagClass="org.apache.struts2.views.jsp.IncludeTag",description="Include a servlet's output " + "(result of servlet or a JSP page)")
097: public class Include extends Component {
098:
099: private static final Log _log = LogFactory.getLog(Include.class);
100:
101: private static String encoding;
102: private static boolean encodingDefined = true;
103:
104: protected String value;
105: private HttpServletRequest req;
106: private HttpServletResponse res;
107: private static String defaultEncoding;
108:
109: public Include(ValueStack stack, HttpServletRequest req,
110: 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",
123: "You must specify the URL to include. Example: /foo.jsp");
124: StringBuffer urlBuf = new StringBuffer();
125:
126: // Add URL
127: urlBuf.append(page);
128:
129: // Add request parameters
130: if (parameters.size() > 0) {
131: urlBuf.append('?');
132:
133: String concat = "";
134:
135: // Set parameters
136: Iterator iter = parameters.entrySet().iterator();
137:
138: while (iter.hasNext()) {
139: Map.Entry entry = (Map.Entry) iter.next();
140: Object name = entry.getKey();
141: List values = (List) entry.getValue();
142:
143: for (int i = 0; i < values.size(); i++) {
144: urlBuf.append(concat);
145: urlBuf.append(name);
146: urlBuf.append('=');
147:
148: try {
149: urlBuf.append(URLEncoder.encode(values.get(i)
150: .toString(), "UTF-8"));
151: } catch (Exception e) {
152: _log.warn("unable to url-encode "
153: + values.get(i).toString()
154: + ", 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: LogFactory.getLog(getClass()).warn(
169: "Exception thrown during include of " + result, e);
170: }
171:
172: return super .end(writer, body);
173: }
174:
175: @StrutsTagAttribute(description="The jsp/servlet output to include",required=true)
176: public void setValue(String value) {
177: this .value = value;
178: }
179:
180: public static String getContextRelativePath(ServletRequest request,
181: String relativePath) {
182: String returnValue;
183:
184: if (relativePath.startsWith("/")) {
185: returnValue = relativePath;
186: } else if (!(request instanceof HttpServletRequest)) {
187: returnValue = relativePath;
188: } else {
189: HttpServletRequest hrequest = (HttpServletRequest) request;
190: String uri = (String) request
191: .getAttribute("javax.servlet.include.servlet_path");
192:
193: if (uri == null) {
194: uri = RequestUtils.getServletPath(hrequest);
195: }
196:
197: returnValue = uri.substring(0, uri.lastIndexOf('/')) + '/'
198: + relativePath;
199: }
200:
201: // .. is illegal in an absolute path according to the Servlet Spec and will cause
202: // known problems on Orion application servers.
203: if (returnValue.indexOf("..") != -1) {
204: Stack stack = new Stack();
205: StringTokenizer pathParts = new StringTokenizer(returnValue
206: .replace('\\', '/'), "/");
207:
208: while (pathParts.hasMoreTokens()) {
209: String part = pathParts.nextToken();
210:
211: if (!part.equals(".")) {
212: if (part.equals("..")) {
213: stack.pop();
214: } else {
215: stack.push(part);
216: }
217: }
218: }
219:
220: StringBuffer flatPathBuffer = new StringBuffer();
221:
222: for (int i = 0; i < stack.size(); i++) {
223: flatPathBuffer.append("/").append(stack.elementAt(i));
224: }
225:
226: returnValue = flatPathBuffer.toString();
227: }
228:
229: return returnValue;
230: }
231:
232: public void addParameter(String key, Object value) {
233: // don't use the default implementation of addParameter,
234: // instead, include tag requires that each parameter be a list of objects,
235: // just like the HTTP servlet interfaces are (String[])
236: if (value != null) {
237: List currentValues = (List) parameters.get(key);
238:
239: if (currentValues == null) {
240: currentValues = new ArrayList();
241: parameters.put(key, currentValues);
242: }
243:
244: currentValues.add(value);
245: }
246: }
247:
248: public static void include(String aResult, Writer writer,
249: ServletRequest request, HttpServletResponse response)
250: throws ServletException, IOException {
251: String resourcePath = getContextRelativePath(request, aResult);
252: RequestDispatcher rd = request
253: .getRequestDispatcher(resourcePath);
254:
255: if (rd == null) {
256: throw new ServletException("Not a valid resource path:"
257: + resourcePath);
258: }
259:
260: PageResponse pageResponse = new PageResponse(response);
261:
262: // Include the resource
263: rd.include((HttpServletRequest) request, pageResponse);
264:
265: //write the response back to the JspWriter, using the correct encoding.
266: String encoding = getEncoding();
267:
268: if (encoding != null) {
269: //use the encoding specified in the property file
270: pageResponse.getContent().writeTo(writer, encoding);
271: } else {
272: //use the platform specific encoding
273: pageResponse.getContent().writeTo(writer, null);
274: }
275: }
276:
277: /**
278: * Get the encoding specified by the property 'struts.i18n.encoding' in struts.properties,
279: * or return the default platform encoding if not specified.
280: * <p/>
281: * Note that if the property is not initially defined, this will return the system default,
282: * even if the property is later defined. This is mainly for performance reasons. Undefined
283: * properties throw exceptions, which are a costly operation.
284: * <p/>
285: * If the property is initially defined, it is read every time, until is is undefined, and then
286: * the system default is used.
287: * <p/>
288: * Why not cache it completely? Some applications will wish to be able to dynamically set the
289: * encoding at runtime.
290: *
291: * @return The encoding to be used.
292: */
293: private static String getEncoding() {
294: if (encodingDefined) {
295: try {
296: encoding = defaultEncoding;
297: } catch (IllegalArgumentException e) {
298: encoding = System.getProperty("file.encoding");
299: encodingDefined = false;
300: }
301: }
302:
303: return encoding;
304: }
305:
306: /**
307: * Implementation of ServletOutputStream that stores all data written
308: * to it in a temporary buffer accessible from {@link #getBuffer()} .
309: *
310: * @author <a href="joe@truemesh.com">Joe Walnes</a>
311: * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
312: */
313: static final class PageOutputStream extends ServletOutputStream {
314:
315: private FastByteArrayOutputStream buffer;
316:
317: public PageOutputStream() {
318: buffer = new FastByteArrayOutputStream();
319: }
320:
321: /**
322: * Return all data that has been written to this OutputStream.
323: */
324: public FastByteArrayOutputStream getBuffer() throws IOException {
325: flush();
326:
327: return buffer;
328: }
329:
330: public void close() throws IOException {
331: buffer.close();
332: }
333:
334: public void flush() throws IOException {
335: buffer.flush();
336: }
337:
338: public void write(byte[] b, int o, int l) throws IOException {
339: buffer.write(b, o, l);
340: }
341:
342: public void write(int i) throws IOException {
343: buffer.write(i);
344: }
345:
346: public void write(byte[] b) throws IOException {
347: buffer.write(b);
348: }
349: }
350:
351: /**
352: * Simple wrapper to HTTPServletResponse that will allow getWriter()
353: * and getResponse() to be called as many times as needed without
354: * causing conflicts.
355: * <p/>
356: * The underlying outputStream is a wrapper around
357: * {@link PageOutputStream} which will store
358: * the written content to a buffer.
359: * <p/>
360: * This buffer can later be retrieved by calling {@link #getContent}.
361: *
362: * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
363: * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
364: */
365: static final class PageResponse extends HttpServletResponseWrapper {
366:
367: protected PrintWriter pagePrintWriter;
368: protected ServletOutputStream outputStream;
369: private PageOutputStream pageOutputStream = null;
370:
371: /**
372: * Create PageResponse wrapped around an existing HttpServletResponse.
373: */
374: public PageResponse(HttpServletResponse response) {
375: super (response);
376: }
377:
378: /**
379: * Return the content buffered inside the {@link PageOutputStream}.
380: *
381: * @return
382: * @throws IOException
383: */
384: public FastByteArrayOutputStream getContent()
385: throws IOException {
386: //if we are using a writer, we need to flush the
387: //data to the underlying outputstream.
388: //most containers do this - but it seems Jetty 4.0.5 doesn't
389: if (pagePrintWriter != null) {
390: pagePrintWriter.flush();
391: }
392:
393: return ((PageOutputStream) getOutputStream()).getBuffer();
394: }
395:
396: /**
397: * Return instance of {@link PageOutputStream}
398: * allowing all data written to stream to be stored in temporary buffer.
399: */
400: public ServletOutputStream getOutputStream() throws IOException {
401: if (pageOutputStream == null) {
402: pageOutputStream = new PageOutputStream();
403: }
404:
405: return pageOutputStream;
406: }
407:
408: /**
409: * Return PrintWriter wrapper around PageOutputStream.
410: */
411: public PrintWriter getWriter() throws IOException {
412: if (pagePrintWriter == null) {
413: pagePrintWriter = new PrintWriter(
414: new OutputStreamWriter(getOutputStream(),
415: getCharacterEncoding()));
416: }
417:
418: return pagePrintWriter;
419: }
420: }
421: }
|