001    // Copyright 2004, 2005 The Apache Software Foundation
002    //
003    // Licensed under the Apache License, Version 2.0 (the "License");
004    // you may not use this file except in compliance with the License.
005    // You may obtain a copy of the License at
006    //
007    //     http://www.apache.org/licenses/LICENSE-2.0
008    //
009    // Unless required by applicable law or agreed to in writing, software
010    // distributed under the License is distributed on an "AS IS" BASIS,
011    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012    // See the License for the specific language governing permissions and
013    // limitations under the License.
014    
015    package org.apache.tapestry.contrib.link;
016    
017    import org.apache.hivemind.ApplicationRuntimeException;
018    import org.apache.tapestry.IMarkupWriter;
019    import org.apache.tapestry.IRequestCycle;
020    import org.apache.tapestry.PageRenderSupport;
021    import org.apache.tapestry.Tapestry;
022    import org.apache.tapestry.TapestryUtils;
023    import org.apache.tapestry.components.ILinkComponent;
024    import org.apache.tapestry.engine.ILink;
025    import org.apache.tapestry.link.DefaultLinkRenderer;
026    import org.apache.tapestry.link.ILinkRenderer;
027    
028    /**
029     * A link renderer that ensures that the generated link uses POST instead of GET
030     * request and is therefore no longer limited in size.
031     * <p>
032     * Theoretically, browsers should support very long URLs, but in practice they
033     * often start behaving strangely if the URLs are more than 256 characters. This
034     * renderer uses JavaScript to generate forms containing the requested link
035     * parameters and then "post" them when the link is selected. As a result, the
036     * data is sent to the server using a POST request with a very short URL and
037     * there is no longer a limitation in the size of the parameters.
038     * <p>
039     * In short, simply add the following parameter to your <code>DirectLink</code>,
040     * <code>ExternalLink</code>, or other such link components:
041     * 
042     * <pre>
043     * renderer = &quot;ognl: @org.apache.tapestry.contrib.link.FormLinkRenderer@RENDERER&quot;
044     * </pre>
045     * 
046     * and they will automatically start using POST rather than GET requests. Their
047     * parameters will no longer be limited in size.
048     * @author mb
049     * @since 4.0
050     */
051    public class FormLinkRenderer extends DefaultLinkRenderer
052    {
053    
054        /**
055         * A public singleton instance of the <code>FormLinkRenderer</code>.
056         * <p>
057         * Since the <code>FormLinkRenderer</code> is stateless, this instance can
058         * serve all links within your application without interference.
059         */
060        public static final ILinkRenderer RENDERER = new FormLinkRenderer();
061    
062        public void renderLink(IMarkupWriter writer, IRequestCycle cycle,
063                ILinkComponent linkComponent)
064        {
065            IMarkupWriter wrappedWriter = null;
066    
067            if (cycle.getAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME) != null)
068                throw new ApplicationRuntimeException(Tapestry
069                        .getMessage("AbstractLinkComponent.no-nesting"),
070                        linkComponent, null, null);
071    
072            cycle.setAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME,
073                    linkComponent);
074            
075            String formName = cycle.getUniqueId("LinkForm");
076            
077            boolean hasBody = getHasBody();
078    
079            boolean disabled = linkComponent.isDisabled();
080    
081            if (!disabled && !cycle.isRewinding())
082            {
083                ILink l = linkComponent.getLink(cycle);
084                String anchor = linkComponent.getAnchor();
085                
086                PageRenderSupport prs = TapestryUtils.getPageRenderSupport(cycle, linkComponent);
087                
088                String function = generateFormFunction(formName, l, anchor);
089                prs.addBodyScript(linkComponent, function);
090                
091                if (hasBody)
092                    writer.begin(getElement());
093                else 
094                    writer.beginEmpty(getElement());
095                
096                writer.attribute(getUrlAttribute(), "javascript: document."
097                        + formName + ".submit();");
098                
099                beforeBodyRender(writer, cycle, linkComponent);
100                
101                // Allow the wrapped components a chance to render.
102                // Along the way, they may interact with this component
103                // and cause the name variable to get set.
104    
105                wrappedWriter = writer.getNestedWriter();
106            }
107            else wrappedWriter = writer;
108    
109            if (hasBody) linkComponent.renderBody(wrappedWriter, cycle);
110    
111            if (!disabled && !cycle.isRewinding())
112            {
113                afterBodyRender(writer, cycle, linkComponent);
114    
115                linkComponent.renderAdditionalAttributes(writer, cycle);
116    
117                if (hasBody)
118                {
119                    wrappedWriter.close();
120    
121                    // Close the <element> tag
122    
123                    writer.end();
124                }
125                else writer.closeTag();
126            }
127    
128            cycle.removeAttribute(Tapestry.LINK_COMPONENT_ATTRIBUTE_NAME);
129    
130        }
131    
132        private String generateFormFunction(String formName, ILink link,
133                String anchor)
134        {
135            String[] parameterNames = link.getParameterNames();
136    
137            StringBuffer buf = new StringBuffer();
138            buf.append("function prepare" + formName + "() {\n");
139    
140            buf.append("  var html = \"\";\n");
141            buf.append("  html += \"<div style='position: absolute'>\";\n");
142    
143            String url = link.getURL(anchor, false);
144            buf.append("  html += \"<form name='" + formName
145                    + "' method='post' action='" + url + "'>\";\n");
146    
147            for(int i = 0; i < parameterNames.length; i++)
148            {
149                String parameter = parameterNames[i];
150                String[] values = link.getParameterValues(parameter);
151                if (values != null) {               
152                    for (int j = 0; j < values.length; j++) {
153                        String value = values[j];
154                        buf.append("  html += \"<input type='hidden' name='" + parameter + "' value='" + value + "'/>\";\n");
155                    }
156                }
157            }
158            buf.append("  html += \"<\" + \"/form>\";\n");
159            buf.append("  html += \"<\" + \"/div>\";\n");
160            buf.append("  document.write(html);\n");
161            buf.append("}\n");
162    
163            buf.append("prepare" + formName + "();\n\n");
164    
165            return buf.toString();
166        }
167    
168    }