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("Date", "[now]"); 176 * response.setHeader("Expires", "[0]"); 177 * response.setHeader("Pragma", "no-cache"); 178 * response.setHeader("Cache-Control", "no-cache"); 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 <head> and/or <body> 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 <head> 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}