James Moger
2013-11-25 3a9e76b63f09a32e0b6812e18ffff00fab8e58e6
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.
699e71 38  *
b2abac 39  * The trick is to embed "wicket:bookmarkablePage" as a hidden field of the form.
JM 40  * Wicket already has logic to extract this parameter when it is trying
41  * to determine which page should receive the request.
699e71 42  *
b2abac 43  * The parameters of the containing page can optionally be included as hidden
699e71 44  * fields in this form.  Note that if a page parameter's name collides with any
b2abac 45  * child's wicket:id in this form then the page parameter is excluded.
699e71 46  *
b2abac 47  * @author James Moger
JM 48  *
49  */
50 public class SessionlessForm<T> extends StatelessForm<T> {
699e71 51
b2abac 52     private static final long serialVersionUID = 1L;
JM 53
54     private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
699e71 55
b2abac 56     private final Class<? extends BasePage> pageClass;
699e71 57
b2abac 58     private final PageParameters pageParameters;
699e71 59
b2abac 60     private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);
JM 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.
699e71 66      *
b2abac 67      * @param id
JM 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.
699e71 78      *
b2abac 79      * @param id
JM 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
699e71 90
b2abac 91     /**
JM 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.
699e71 95      *
b2abac 96      * @param markupStream
JM 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     }
699e71 136
b2abac 137     /**
JM 138      * Take URL-encoded query string value, unencode it and return HTML-escaped version
699e71 139      *
b2abac 140      * @param s
JM 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 }