James Moger
2013-05-15 bca8c5c52554b6aac65b8e2300675ae8f6af1d6d
commit | author | age
b2abac 1 /*
JM 2  * Copyright 2012 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.wicket;
17
18 import java.text.MessageFormat;
19
20 import org.apache.wicket.Component;
21 import org.apache.wicket.PageParameters;
22 import org.apache.wicket.markup.ComponentTag;
23 import org.apache.wicket.markup.MarkupStream;
24 import org.apache.wicket.markup.html.form.StatelessForm;
25 import org.apache.wicket.protocol.http.WicketURLDecoder;
26 import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
27 import org.apache.wicket.util.string.AppendingStringBuffer;
28 import org.apache.wicket.util.string.Strings;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import com.gitblit.wicket.pages.BasePage;
33
34 /**
35  * This class is used to create a stateless form that can POST or GET to a
36  * bookmarkable page regardless of the pagemap and even after session expiration
37  * or a server restart.
38  * 
39  * The trick is to embed "wicket:bookmarkablePage" as a hidden field of the form.
40  * Wicket already has logic to extract this parameter when it is trying
41  * to determine which page should receive the request.
42  * 
43  * The parameters of the containing page can optionally be included as hidden
44  * fields in this form.  Note that if a page parameter's name collides with any 
45  * child's wicket:id in this form then the page parameter is excluded.
46  * 
47  * @author James Moger
48  *
49  */
50 public class SessionlessForm<T> extends StatelessForm<T> {
51     
52     private static final long serialVersionUID = 1L;
53
54     private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
55     
56     private final Class<? extends BasePage> pageClass;
57     
58     private final PageParameters pageParameters;
59     
60     private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);
61
62     /**
63      * Sessionless forms must have a bookmarkable page class.  A bookmarkable
64      * page is defined as a page that has only a default and/or a PageParameter
65      * constructor.
66      * 
67      * @param id
68      * @param bookmarkablePageClass
69      */
70     public SessionlessForm(String id, Class<? extends BasePage> bookmarkablePageClass) {
71         this(id, bookmarkablePageClass, null);
72     }
73
74     /**
75      * Sessionless forms must have a bookmarkable page class.  A bookmarkable
76      * page is defined as a page that has only a default and/or a PageParameter
77      * constructor.
78      * 
79      * @param id
80      * @param bookmarkablePageClass
81      * @param pageParameters
82      */
83     public SessionlessForm(String id, Class<? extends BasePage> bookmarkablePageClass,
84             PageParameters pageParameters) {
85         super(id);
86         this.pageClass = bookmarkablePageClass;
87         this.pageParameters = pageParameters;
88     }
89
90     
91     /**
92      * Append an additional hidden input tag that forces Wicket to correctly
93      * determine the destination page class even after a session expiration or
94      * a server restart.
95      * 
96      * @param markupStream
97      *            The markup stream
98      * @param openTag
99      *            The open tag for the body
100      */
101     @Override
102     protected void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
103     {
104         // render the hidden bookmarkable page field
105         AppendingStringBuffer buffer = new AppendingStringBuffer(HIDDEN_DIV_START);
106         buffer.append("<input type=\"hidden\" name=\"")
107             .append(WebRequestCodingStrategy.BOOKMARKABLE_PAGE_PARAMETER_NAME)
108             .append("\" value=\":")
109             .append(pageClass.getName())
110             .append("\" />");
111
112         // insert the page parameters, if any, as hidden fields as long as they
113         // do not collide with any child wicket:id of the form.
114         if (pageParameters != null) {
115             for (String key : pageParameters.keySet()) {
116                 Component c = get(key);
117                 if (c != null) {
118                     // this form has a field id which matches the
119                     // parameter name, skip embedding a hidden value
120                     log.warn(MessageFormat.format("Skipping page parameter \"{0}\" from sessionless form hidden fields because it collides with a form child wicket:id", key));
121                     continue;
122                 }
123                 String value = pageParameters.getString(key);
124                 buffer.append("<input type=\"hidden\" name=\"")
125                 .append(recode(key))
126                 .append("\" value=\"")
127                 .append(recode(value))
128                 .append("\" />");
129             }
130         }
131
132         buffer.append("</div>");
133         getResponse().write(buffer);
134         super.onComponentTagBody(markupStream, openTag);
135     }
136     
137     /**
138      * Take URL-encoded query string value, unencode it and return HTML-escaped version
139      * 
140      * @param s
141      *            value to reencode
142      * @return reencoded value
143      */
144     private String recode(String s) {
145         String un = WicketURLDecoder.QUERY_INSTANCE.decode(s);
146         return Strings.escapeMarkup(un).toString();
147     }
148 }