Commits

Anonymous committed 1cc5301

IncludeTag ported from 1.3 (thanks to John Patterson). Resolves WW-236

git-svn-id: http://svn.opensymphony.com/svn/webwork/trunk@51573baa09-0c28-0410-bef9-dab3c582ae83

Comments (0)

Files changed (3)

src/etc/taglib.tld

     </tag>
 
     <tag>
+        <name>include</name>
+        <tagclass>com.opensymphony.webwork.views.jsp.IncludeTag</tagclass>
+        <bodycontent>JSP</bodycontent>
+        <info>Used to include another page or action.</info>
+        <attribute>
+            <name>page</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+        </attribute>
+        <attribute>
+            <name>value</name>
+            <required>false</required>
+            <rtexprvalue>false</rtexprvalue>
+        </attribute>
+    </tag>
+
+    <tag>
         <name>bean</name>
         <tagclass>com.opensymphony.webwork.views.jsp.BeanTag</tagclass>
         <bodycontent>JSP</bodycontent>

src/java/com/opensymphony/webwork/util/FastByteArrayOutputStream.java

+/*
+ * WebWork, Web Application Framework
+ *
+ * Distributable under Apache license.
+ * See terms of license at opensource.org
+ */
+package com.opensymphony.webwork.util;
+
+import javax.servlet.jsp.JspWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.util.Iterator;
+import java.util.LinkedList;
+
+/**
+ * A speedy implementation of ByteArrayOutputStream. It's not synchronized, and it
+ * does not copy buffers when it's expanded. There's also no copying of the internal buffer
+ * if it's contents is extracted with the writeTo(stream) method.
+ *
+ * @author Rickard �berg
+ * @version $Revision$
+ */
+public class FastByteArrayOutputStream
+        extends OutputStream {
+    // Static --------------------------------------------------------
+    private static final int DEFAULT_BLOCK_SIZE = 8192;
+
+    // Attributes ----------------------------------------------------
+    // internal buffer
+    private byte[] buffer;
+    private LinkedList buffers;
+    private int index;
+    private int size;
+    private int blockSize;
+    // is the stream closed?
+    private boolean closed;
+
+    // Constructors --------------------------------------------------
+    public FastByteArrayOutputStream() {
+        this(DEFAULT_BLOCK_SIZE);
+    }
+
+    public FastByteArrayOutputStream(int aSize) {
+        blockSize = aSize;
+        buffer = new byte[blockSize];
+    }
+
+    // Public
+    public void writeTo(OutputStream out) throws IOException {
+        // Check if we have a list of buffers
+        if (buffers != null) {
+            Iterator enum = buffers.iterator();
+            while (enum.hasNext()) {
+                byte[] bytes = (byte[]) enum.next();
+                out.write(bytes, 0, blockSize);
+            }
+        }
+
+        // write the internal buffer directly
+        out.write(buffer, 0, index);
+    }
+
+    public void writeTo(RandomAccessFile out) throws IOException {
+        // Check if we have a list of buffers
+        if (buffers != null) {
+            Iterator enum = buffers.iterator();
+            while (enum.hasNext()) {
+                byte[] bytes = (byte[]) enum.next();
+                out.write(bytes, 0, blockSize);
+            }
+        }
+
+        // write the internal buffer directly
+        out.write(buffer, 0, index);
+    }
+
+    public void writeTo(JspWriter out, String encoding) throws IOException {
+        // Check if we have a list of buffers
+        if (buffers != null) {
+            Iterator enum = buffers.iterator();
+            while (enum.hasNext()) {
+                byte[] bytes = (byte[]) enum.next();
+
+                if (encoding != null)
+                    out.write(new String(bytes, encoding));
+                else
+                    out.write(new String(bytes));
+            }
+        }
+
+        // write the internal buffer directly
+        if (encoding != null)
+            out.write(new String(buffer, 0, index, encoding));
+        else
+            out.write(new String(buffer, 0, index));
+    }
+
+    public int getSize() {
+        return size + index;
+    }
+
+    public byte[] toByteArray() {
+        byte[] data = new byte[getSize()];
+
+        // Check if we have a list of buffers
+        int pos = 0;
+        if (buffers != null) {
+            Iterator enum = buffers.iterator();
+            while (enum.hasNext()) {
+                byte[] bytes = (byte[]) enum.next();
+                System.arraycopy(bytes, 0, data, pos, blockSize);
+                pos += blockSize;
+            }
+        }
+
+        // write the internal buffer directly
+        System.arraycopy(buffer, 0, data, pos, index);
+
+        return data;
+    }
+
+    public String toString() {
+        return new String(toByteArray());
+    }
+
+    // OutputStream overrides ----------------------------------------
+    public void write(int datum) throws IOException {
+        if (closed) {
+            throw new IOException("Stream closed");
+        } else {
+            if (index == blockSize) {
+                // Create new buffer and store current in linked list
+                if (buffers == null)
+                    buffers = new LinkedList();
+
+                buffers.addLast(buffer);
+
+                buffer = new byte[blockSize];
+                size += index;
+                index = 0;
+            }
+
+            // store the byte
+            buffer[index++] = (byte) datum;
+        }
+    }
+
+    public void write(byte[] data, int offset, int length)
+            throws IOException {
+        if (data == null) {
+            throw new NullPointerException();
+        } else if ((offset < 0) || (offset + length > data.length)
+                || (length < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (closed) {
+            throw new IOException("Stream closed");
+        } else {
+            if (index + length >= blockSize) {
+                // Write byte by byte
+                // FIXME optimize this to use arraycopy's instead
+                for (int i = 0; i < length; i++)
+                    write(data[offset + i]);
+            } else {
+                // Copy in the subarray
+                System.arraycopy(data, offset, buffer, index, length);
+                index += length;
+            }
+        }
+    }
+
+    public void close() {
+        closed = true;
+    }
+}
+

src/java/com/opensymphony/webwork/views/jsp/IncludeTag.java

+/*
+ * WebWork, Web Application Framework
+ *
+ * Distributable under Apache license.
+ * See terms of license at opensource.org
+ */
+package com.opensymphony.webwork.views.jsp;
+
+import com.opensymphony.webwork.config.Configuration;
+import com.opensymphony.webwork.util.FastByteArrayOutputStream;
+import com.opensymphony.xwork.ActionContext;
+import com.opensymphony.xwork.util.OgnlValueStack;
+import org.apache.commons.logging.LogFactory;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletException;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspTagException;
+import javax.servlet.jsp.PageContext;
+import javax.servlet.jsp.tagext.TagSupport;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.net.URLEncoder;
+import java.util.*;
+
+/**
+ * Include a servlets output (e.g. result of servlet, or a JSP page).
+ *
+ * @author Rickard �berg (rickard@dreambean.com)
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ * @version $Revision$
+ */
+public class IncludeTag
+        extends TagSupport
+        implements ParameterizedTag {
+    // Static --------------------------------------------------------
+    public static void include(String aResult, PageContext aContext)
+            throws ServletException, IOException {
+        String resourcePath = getContextRelativePath(aContext.getRequest(), aResult);
+        RequestDispatcher rd = aContext.getRequest().getRequestDispatcher(resourcePath);
+
+        if (rd == null)
+            throw new ServletException("Not a valid resource path:" + resourcePath);
+
+        PageResponse pageResponse = new PageResponse((HttpServletResponse) aContext.getResponse());
+
+        // Include the resource
+        rd.include((HttpServletRequest) aContext.getRequest(), pageResponse);
+
+        //write the response back to the JspWriter, using the correct encoding.
+        String encoding = getEncoding();
+        if (encoding != null) {
+            //use the encoding specified in the property file
+            pageResponse.getContent().writeTo(aContext.getOut(), encoding);
+        } else {
+            //use the platform specific encoding
+            pageResponse.getContent().writeTo(aContext.getOut(), null);
+        }
+    }
+
+    private static String encoding;
+    private static boolean encodingDefined = true;
+
+    /**
+     * Get the encoding specified by the property 'webwork.i18n.encoding' in webwork.properties,
+     * or return the default platform encoding if not specified.
+     * <p>
+     * Note that if the property is not initially defined, this will return the system default,
+     * even if the property is later defined.  This is mainly for performance reasons.  Undefined
+     * properties throw exceptions, which are a costly operation.
+     * <p>
+     * If the property is initially defined, it is read every time, until is is undefined, and then
+     * the system default is used.
+     * <p>
+     * Why not cache it completely?  Some applications will wish to be able to dynamically set the
+     * encoding at runtime.
+     *
+     * @return  The encoding to be used.
+     */
+    private static String getEncoding() {
+        if (encodingDefined) {
+            try {
+                encoding = Configuration.getString("webwork.i18n.encoding");
+            } catch (IllegalArgumentException e) {
+                encoding = System.getProperty("file.encoding");
+                encodingDefined = false;
+            }
+        }
+        return encoding;
+
+    }
+
+    public static String getContextRelativePath(ServletRequest request,
+                                                String relativePath) {
+        String returnValue;
+
+        if (relativePath.startsWith("/"))
+            returnValue = relativePath;
+        else if (!(request instanceof HttpServletRequest))
+            returnValue = relativePath;
+        else {
+            HttpServletRequest hrequest = (HttpServletRequest) request;
+            String uri = (String)
+                    request.getAttribute("javax.servlet.include.servlet_path");
+            if (uri == null)
+                uri = hrequest.getServletPath();
+            returnValue = uri.substring(0, uri.lastIndexOf('/')) + '/' + relativePath;
+        }
+
+        // .. is illegal in an absolute path according to the Servlet Spec and will cause
+        // known problems on Orion application servers.
+        if (returnValue.indexOf("..") != -1) {
+            Stack stack = new Stack();
+            StringTokenizer pathParts = new StringTokenizer(returnValue.replace('\\', '/'), "/");
+            while (pathParts.hasMoreTokens()) {
+                String part = pathParts.nextToken();
+                if (!part.equals(".")) {
+                    if (part.equals(".."))
+                        stack.pop();
+                    else
+                        stack.push(part);
+                }
+            }
+
+            StringBuffer flatPathBuffer = new StringBuffer();
+
+            for (int i = 0; i < stack.size(); i++)
+                flatPathBuffer.append("/" + stack.elementAt(i));
+
+            returnValue = flatPathBuffer.toString();
+        }
+
+        return returnValue;
+    }
+
+    // Attributes ----------------------------------------------------
+    protected String pageAttr;
+    protected String valueAttr;
+    protected Map params;
+
+    // Public --------------------------------------------------------
+
+    /**
+     * Name of page/servlet to include.
+     *
+     * @param   aPage
+     * @deprecated use value attribute instead
+     */
+    public void setPage(String aPage) {
+        pageAttr = aPage;
+    }
+
+    /**
+     * Name of property whose value is the name of the page/servlet to include.
+     *
+     * @param   aName
+     */
+    public void setValue(String aName) {
+        valueAttr = aName;
+    }
+
+    // ParamTag.Parametric implementation ----------------------------
+    /**
+     * Add a parameter to the URL of the included page/servlet.
+     *
+     * @param   name
+     * @param   value
+     */
+    public void addParam(String name, Object value) {
+        if (value != null) {
+            List currentValues = (List) params.get(name);
+            if (currentValues == null) {
+                currentValues = new ArrayList();
+                params.put(name, currentValues);
+            }
+            currentValues.add(value);
+        }
+    }
+
+    // BodyTag implementation ----------------------------------------
+    public int doStartTag() throws JspException {
+        // Init parameter map
+        params = new HashMap();
+
+        return super.doStartTag();
+    }
+
+    public int doEndTag() throws JspException {
+        OgnlValueStack stack = ActionContext.getContext().getValueStack();
+
+
+        String page;
+        // If value is set, we resolve it to get the page name
+        if (valueAttr != null) {
+            page = (String) stack.findValue(valueAttr);
+        } else {
+            page = pageAttr;
+        }
+
+        StringBuffer urlBuf = new StringBuffer();
+
+        // Add URL
+        urlBuf.append(page);
+
+        // Add request parameters
+        if (params.size() > 0) {
+
+            urlBuf.append('?');
+            String concat = "";
+
+            // Set parameters
+            Iterator enum = params.entrySet().iterator();
+            while (enum.hasNext()) {
+                Map.Entry entry = (Map.Entry) enum.next();
+                Object name = entry.getKey();
+                List values = (List) entry.getValue();
+                for (int i = 0; i < values.size(); i++) {
+                    urlBuf.append(concat);
+                    urlBuf.append(name);
+                    urlBuf.append('=');
+
+                    try {
+                        urlBuf.append(URLEncoder.encode(values.get(i).toString(), "UTF-8"));
+                    } catch (Exception e) {
+
+                    }
+                    concat = "&";
+                }
+            }
+        }
+        params = null;
+
+        String result = urlBuf.toString();
+
+        // Include
+        try {
+            include(result, pageContext);
+        } catch (Exception e) {
+            LogFactory.getLog(getClass()).warn("Exception thrown during include of " + result, e);
+            throw new JspTagException(e.toString());
+        }
+
+        return EVAL_PAGE;
+    }
+
+    /* (non-Javadoc)
+     * @see com.opensymphony.webwork.views.jsp.ParameterizedTag#getParams()
+     */
+    public Map getParams() {
+        return params;
+    }
+}
+
+/**
+ * Implementation of ServletOutputStream that stores all data written
+ * to it in a temporary buffer accessible from {@link #getBuffer()} .
+ *
+ * @author <a href="joe@truemesh.com">Joe Walnes</a>
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ */
+final class PageOutputStream extends ServletOutputStream {
+    private FastByteArrayOutputStream buffer;
+
+    public PageOutputStream() {
+        buffer = new FastByteArrayOutputStream();
+    }
+
+    public void write(byte[] b, int o, int l) throws IOException {
+        buffer.write(b, o, l);
+    }
+
+    public void write(int i) throws IOException {
+        buffer.write(i);
+    }
+
+    public void flush() throws IOException {
+        buffer.flush();
+    }
+
+    public void close() throws IOException {
+        buffer.close();
+    }
+
+    public void write(byte b[]) throws IOException {
+        buffer.write(b);
+    }
+
+    /** Return all data that has been written to this OutputStream. */
+    public FastByteArrayOutputStream getBuffer() throws IOException {
+        flush();
+        return buffer;
+    }
+
+}
+
+
+/**
+ * Simple wrapper to HTTPServletResponse that will allow getWriter()
+ * and getResponse() to be called as many times as needed without
+ * causing conflicts.
+ * <p>
+ * The underlying outputStream is a wrapper around
+ * {@link com.opensymphony.webwork.views.jsp.PageOutputStream} which will store
+ * the written content to a buffer.
+ * <p>
+ * This buffer can later be retrieved by calling {@link #getContent}.
+ *
+ * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
+ * @author <a href="mailto:scott@atlassian.com">Scott Farquhar</a>
+ */
+final class PageResponse extends HttpServletResponseWrapper {
+    protected PrintWriter pagePrintWriter;
+    protected ServletOutputStream outputStream;
+    private PageOutputStream pageOutputStream = null;
+
+    /** Create PageResponse wrapped around an existing HttpServletResponse. */
+    public PageResponse(HttpServletResponse response) {
+        super(response);
+    }
+
+    /** Return PrintWriter wrapper around PageOutputStream. */
+    public PrintWriter getWriter() throws IOException {
+        if (pagePrintWriter == null)
+            pagePrintWriter = new PrintWriter(new OutputStreamWriter(getOutputStream(), getCharacterEncoding()));
+        return pagePrintWriter;
+    }
+
+    /**
+     * Return instance of {@link com.opensymphony.webwork.views.jsp.PageOutputStream}
+     * allowing all data written to stream to be stored in temporary buffer.
+     */
+    public ServletOutputStream getOutputStream() throws IOException {
+        if (pageOutputStream == null) {
+            pageOutputStream = new PageOutputStream();
+        }
+        return pageOutputStream;
+    }
+
+    /**
+     * Return the content buffered inside the {@link com.opensymphony.webwork.views.jsp.PageOutputStream}.
+     * @return
+     * @throws IOException
+     */
+    public FastByteArrayOutputStream getContent() throws IOException {
+        //if we are using a writer, we need to flush the
+        //data to the underlying outputstream.
+        //most containers do this - but it seems Jetty 4.0.5 doesn't
+        if (pagePrintWriter != null)
+            pagePrintWriter.flush();
+
+        return ((PageOutputStream) getOutputStream()).getBuffer();
+    }
+}