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