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