001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.wicket.markup.html;
018
019import org.apache.wicket.Component;
020import org.apache.wicket.Page;
021import org.apache.wicket.markup.MarkupType;
022import org.apache.wicket.markup.head.IHeaderResponse;
023import org.apache.wicket.markup.html.internal.HtmlHeaderContainer;
024import org.apache.wicket.markup.html.link.BookmarkablePageLink;
025import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
026import org.apache.wicket.markup.renderStrategy.AbstractHeaderRenderStrategy;
027import org.apache.wicket.model.IModel;
028import org.apache.wicket.protocol.http.WebApplication;
029import org.apache.wicket.request.Request;
030import org.apache.wicket.request.Response;
031import org.apache.wicket.request.cycle.RequestCycle;
032import org.apache.wicket.request.http.WebRequest;
033import org.apache.wicket.request.http.WebResponse;
034import org.apache.wicket.request.mapper.parameter.PageParameters;
035import org.apache.wicket.response.StringResponse;
036import org.apache.wicket.util.string.Strings;
037import org.apache.wicket.util.visit.IVisit;
038import org.apache.wicket.util.visit.IVisitor;
039import org.slf4j.Logger;
040import org.slf4j.LoggerFactory;
041
042
043/**
044 * Base class for HTML pages. This subclass of Page simply returns HTML when asked for its markup
045 * type. It also has a method which subclasses can use to retrieve a bookmarkable link to the
046 * application's home page.
047 * <p>
048 * WebPages can be constructed with any constructor when they are being used in a Wicket session,
049 * but if you wish to link to a Page using a URL that is "bookmarkable" (which implies that the URL
050 * will not have any session information encoded in it, and that you can call this page directly
051 * without having a session first directly from your browser), you need to implement your Page with
052 * a no-arg constructor or with a constructor that accepts a PageParameters argument (which wraps
053 * any query string parameters for a request). In case the page has both constructors, the
054 * constructor with PageParameters will be used.
055 * 
056 * @author Jonathan Locke
057 * @author Eelco Hillenius
058 * @author Juergen Donnerstag
059 * @author Gwyn Evans
060 */
061public class WebPage extends Page
062{
063        private static final Logger log = LoggerFactory.getLogger(WebPage.class);
064
065        private static final long serialVersionUID = 1L;
066
067        /**
068         * Constructor.
069         *
070         * Having this constructor public means that your page is 'bookmarkable' and hence
071         * can be called/ created from anywhere.
072         */
073        protected WebPage()
074        {
075        }
076
077        protected WebPage(final IModel<?> model)
078        {
079                super(model);
080        }
081
082        /**
083         * Constructor which receives wrapped query string parameters for a request. Having this
084         * constructor public means that your page is 'bookmarkable' and hence can be called/ created
085         * from anywhere. For bookmarkable pages (as opposed to when you construct page instances
086         * yourself, this constructor will be used in preference to a no-arg constructor, if both exist.
087         * Note that nothing is done with the page parameters argument. This constructor is provided so
088         * that tools such as IDEs will include it their list of suggested constructors for derived
089         * classes.
090         * 
091         * Please call this constructor if you want to remember the pageparameters
092         * {@link #getPageParameters()}. So that they are reused for stateless links.
093         * 
094         * @param parameters
095         *            Wrapped query string parameters.
096         */
097        protected WebPage(final PageParameters parameters)
098        {
099                super(parameters);
100        }
101
102        /**
103         * Gets the markup type for a WebPage, which is "html" by default. Support for pages in another
104         * markup language, such as VXML, would require the creation of a different Page subclass in an
105         * appropriate package under org.apache.wicket.markup. To support VXML (voice markup), one might
106         * create the package org.apache.wicket.markup.vxml and a subclass of Page called VoicePage.
107         * 
108         * @return Markup type for HTML
109         */
110        @Override
111        public MarkupType getMarkupType()
112        {
113                return MarkupType.HTML_MARKUP_TYPE;
114        }
115
116        @Override
117        protected void onRender()
118        {
119                // Configure the response such as headers etc.
120                configureResponse((WebResponse)RequestCycle.get().getResponse());
121
122                // The rules if and when to insert an xml decl in the response are a bit tricky. Allow the
123                // user to replace the default per page and per application.
124                renderXmlDecl();
125
126                super.onRender();
127        }
128
129        /**
130         * The rules if and when to insert an xml decl in the response are a bit tricky. Allow the user
131         * to replace the default per page and per application.
132         */
133        protected void renderXmlDecl()
134        {
135                WebApplication.get().renderXmlDecl(this, false);
136        }
137
138        /**
139         * Set-up response with appropriate content type, locale and encoding. The locale is set equal
140         * to the session's locale. The content type header contains information about the markup type
141         * (@see #getMarkupType()) and the encoding. The response (and request) encoding is determined
142         * by an application setting (@see ApplicationSettings#getResponseRequestEncoding()). If null,
143         * no xml decl will be written.
144         * 
145         * @param response
146         *            The WebResponse object
147         */
148        protected void configureResponse(final WebResponse response)
149        {
150                // Users may subclass setHeader() to set there own headers
151                setHeaders(response);
152
153                // The response encoding is an application setting
154                final String encoding = getApplication().getRequestCycleSettings()
155                        .getResponseRequestEncoding();
156                final boolean validEncoding = (Strings.isEmpty(encoding) == false);
157                final String contentType;
158                if (validEncoding)
159                {
160                        contentType = getMarkupType().getMimeType() + "; charset=" + encoding;
161                }
162                else
163                {
164                        contentType = getMarkupType().getMimeType();
165                }
166
167                response.setContentType(contentType);
168        }
169
170        /**
171         * Subclasses can override this to set there headers when the Page is being served. By default
172         * these headers are set:
173         * 
174         * <pre>
175         * response.setHeader(&quot;Date&quot;, &quot;[now]&quot;);
176         * response.setHeader(&quot;Expires&quot;, &quot;[0]&quot;);
177         * response.setHeader(&quot;Pragma&quot;, &quot;no-cache&quot;);
178         * response.setHeader(&quot;Cache-Control&quot;, &quot;no-cache&quot;);
179         * </pre>
180         * 
181         * So if a Page wants to control this or doesn't want to set this info it should override this
182         * method and don't call super.
183         * 
184         * @param response
185         *            The WebResponse where set(Date)Header can be called on.
186         */
187        protected void setHeaders(WebResponse response)
188        {
189                response.disableCaching();
190        }
191
192        /**
193         * 
194         * @see org.apache.wicket.Component#onAfterRender()
195         */
196        @Override
197        protected void onAfterRender()
198        {
199                // only in development mode validate the headers
200                if (getApplication().usesDevelopmentConfig())
201                {
202                        // check headers only when page was completely rendered
203                        if (wasRendered(this))
204                        {
205                                validateHeaders();
206                        }
207                }
208
209                super.onAfterRender();
210        }
211
212        /**
213         * Validate that each component which wanted to contribute to the header section actually was
214         * able to do so.
215         */
216        private void validateHeaders()
217        {
218                // search for HtmlHeaderContainer in the first level of children or deeper
219                // if there are transparent resolvers used
220                HtmlHeaderContainer header = visitChildren(new IVisitor<Component, HtmlHeaderContainer>()
221                {
222                        @Override
223                        public void component(final Component component, final IVisit<HtmlHeaderContainer> visit)
224                        {
225                                if (component instanceof HtmlHeaderContainer)
226                                {
227                                        visit.stop((HtmlHeaderContainer)component);
228                                }
229                                else if (component instanceof TransparentWebMarkupContainer == false)
230                                {
231                                        visit.dontGoDeeper();
232                                }
233                        }
234                });
235
236                if (header == null)
237                {
238                        // the markup must at least contain a <body> tag for wicket to automatically
239                        // create a HtmlHeaderContainer. Log an error if no header container
240                        // was created but any of the components or behaviors want to contribute
241                        // something to the header.
242                        header = new HtmlHeaderContainer(HtmlHeaderSectionHandler.HEADER_ID);
243                        add(header);
244
245                        RequestCycle requestCycle = getRequestCycle();
246                        Response orgResponse = requestCycle.getResponse();
247                        try
248                        {
249                                StringResponse tempResponse = new StringResponse();
250                                requestCycle.setResponse(tempResponse);
251
252                                // Render all header sections of all components on the page
253                                AbstractHeaderRenderStrategy.get().renderHeader(header, null, getPage());
254
255                                IHeaderResponse headerResponse = header.getHeaderResponse();
256                                headerResponse.close();
257                                CharSequence collectedHeaderOutput = tempResponse.getBuffer();
258                                if (collectedHeaderOutput.length() > 0)
259                                {
260                                        reportMissingHead(collectedHeaderOutput);
261                                }
262                        }
263                        catch (Exception e)
264                        {
265                                // just swallow this exception, there isn't much we can do about.
266                                log.error("header/body check throws exception", e);
267                        }
268                        finally
269                        {
270                                this.remove(header);
271                                requestCycle.setResponse(orgResponse);
272                        }
273                }
274        }
275
276        /**
277         * Reports an error that there is no &lt;head&gt; and/or &lt;body&gt; in the page and
278         * there is no where to write the header response.
279         * 
280         * @param collectedHeaderOutput
281         *          The collected response that should have been written to the &lt;head&gt;
282         * @see #validateHeaders()
283         */
284        protected void reportMissingHead(final CharSequence collectedHeaderOutput)
285        {
286                log.error("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
287                log.error("You probably forgot to add a <body> or <head> tag to your markup since no Header Container was \n" +
288                                "found but components were found which want to write to the <head> section.\n" +
289                                collectedHeaderOutput);
290                log.error("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
291        }
292
293        /**
294         * Creates and returns a bookmarkable link to this application's home page.
295         * 
296         * @param id
297         *            Name of link
298         * @return Link to home page for this application
299         */
300        protected final BookmarkablePageLink<Void> homePageLink(final String id)
301        {
302                return new BookmarkablePageLink<>(id, getApplication().getHomePage());
303        }
304
305        /**
306         * Prevents page from get dirty inside an AJAX request.
307         */
308        @Override
309        public final void dirty(boolean isInitialization)
310        {
311                Request request = getRequest();
312                if (isInitialization == false && request instanceof WebRequest &&
313                        ((WebRequest)request).isAjax())
314                {
315                        return;
316                }
317                super.dirty(isInitialization);
318        }
319}