001 // Copyright 2004, 2005 The Apache Software Foundation 002 // 003 // Licensed under the Apache License, Version 2.0 (the "License"); 004 // you may not use this file except in compliance with the License. 005 // You may obtain a copy of the License at 006 // 007 // http://www.apache.org/licenses/LICENSE-2.0 008 // 009 // Unless required by applicable law or agreed to in writing, software 010 // distributed under the License is distributed on an "AS IS" BASIS, 011 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 012 // See the License for the specific language governing permissions and 013 // limitations under the License. 014 015 package org.apache.tapestry.html; 016 017 import org.apache.commons.lang.StringUtils; 018 import org.apache.hivemind.HiveMind; 019 import org.apache.tapestry.*; 020 import org.apache.tapestry.coerce.ValueConverter; 021 import org.apache.tapestry.engine.IEngineService; 022 import org.apache.tapestry.engine.ILink; 023 import org.apache.tapestry.services.ResponseBuilder; 024 import org.apache.tapestry.spec.IApplicationSpecification; 025 026 import java.util.ArrayList; 027 import java.util.Date; 028 import java.util.Iterator; 029 import java.util.List; 030 031 /** 032 * Component for creating a standard 'shell' for a page, which comprises the <html> and 033 * <head> portions of the page. [ <a 034 * href="../../../../../ComponentReference/Shell.html">Component Reference </a>] 035 * <p> 036 * Specifically does <em>not</em> provide a <body> tag, that is usually accomplished using a 037 * {@link Body} component. 038 * 039 * @author Howard Lewis Ship 040 */ 041 042 public abstract class Shell extends AbstractComponent 043 { 044 public static final String SHELL_ATTRIBUTE = "org.apache.tapestry.html.Shell"; 045 046 private static final String GENERATOR_CONTENT = "Tapestry Application Framework, version " + Tapestry.VERSION; 047 048 protected void renderComponent(IMarkupWriter writer, IRequestCycle cycle) 049 { 050 TapestryUtils.storeUniqueAttribute(cycle, SHELL_ATTRIBUTE, this); 051 052 long startTime = System.currentTimeMillis(); 053 boolean rewinding = cycle.isRewinding(); 054 boolean dynamic = getBuilder().isDynamic(); 055 056 if (!rewinding && !dynamic) 057 { 058 writeDocType(writer, cycle); 059 060 IPage page = getPage(); 061 062 if (!isDisableTapestryMeta()) 063 { 064 writer.comment("Application: " + getApplicationSpecification().getName()); 065 066 writer.comment("Page: " + page.getPageName()); 067 writer.comment("Generated: " + new Date()); 068 } 069 070 writer.begin("html"); 071 renderInformalParameters(writer, cycle); 072 writer.println(); 073 writer.begin("head"); 074 writer.println(); 075 076 if (!isDisableTapestryMeta()) 077 writeMetaTag(writer, "name", "generator", GENERATOR_CONTENT); 078 079 if (isDisableCaching()) 080 writeMetaTag(writer, "http-equiv", "content", "no-cache"); 081 082 if (getRenderContentType()) 083 writeMetaTag(writer, "http-equiv", "Content-Type", writer.getContentType()); 084 085 writeRefresh(writer, cycle); 086 087 if (getRenderBaseTag()) 088 getBaseTagWriter().render(writer, cycle); 089 090 writer.begin("title"); 091 092 writer.print(getTitle(), getRaw()); 093 writer.end(); // title 094 writer.println(); 095 096 IRender delegate = getDelegate(); 097 098 if (delegate != null) 099 delegate.render(writer, cycle); 100 101 IRender ajaxDelegate = getAjaxDelegate(); 102 103 if (ajaxDelegate != null) 104 ajaxDelegate.render(writer, cycle); 105 106 IAsset stylesheet = getStylesheet(); 107 108 if (stylesheet != null) 109 writeStylesheetLink(writer, stylesheet); 110 111 Iterator i = (Iterator) getValueConverter().coerceValue(getStylesheets(), Iterator.class); 112 113 while (i.hasNext()) 114 { 115 stylesheet = (IAsset) i.next(); 116 117 writeStylesheetLink(writer, stylesheet); 118 } 119 } 120 121 // Render the body, the actual page content 122 123 IMarkupWriter nested = !dynamic ? writer.getNestedWriter() : writer; 124 125 renderBody(nested, cycle); 126 127 if (!rewinding) 128 { 129 List relations = getRelations(); 130 if (relations != null) 131 writeRelations(writer, relations); 132 133 StringBuffer additionalContent = getContentBuffer(); 134 if (additionalContent != null) 135 writer.printRaw(additionalContent.toString()); 136 137 writer.end(); // head 138 } 139 140 if (!dynamic) 141 nested.close(); 142 143 if (!rewinding && !dynamic) 144 { 145 writer.end(); // html 146 writer.println(); 147 148 if (!isDisableTapestryMeta()) 149 { 150 long endTime = System.currentTimeMillis(); 151 152 writer.comment("Render time: ~ " + (endTime - startTime) + " ms"); 153 } 154 } 155 156 } 157 158 protected void cleanupAfterRender(IRequestCycle cycle) 159 { 160 super.cleanupAfterRender(cycle); 161 162 cycle.removeAttribute(SHELL_ATTRIBUTE); 163 } 164 165 private void writeDocType(IMarkupWriter writer, IRequestCycle cycle) 166 { 167 // This is the real code 168 String doctype = getDoctype(); 169 if (HiveMind.isNonBlank(doctype)) 170 { 171 writer.printRaw("<!DOCTYPE " + doctype + ">"); 172 writer.println(); 173 } 174 } 175 176 private void writeStylesheetLink(IMarkupWriter writer, IAsset stylesheet) 177 { 178 writer.beginEmpty("link"); 179 writer.attribute("rel", "stylesheet"); 180 writer.attribute("type", "text/css"); 181 writer.attribute("href", stylesheet.buildURL()); 182 writer.println(); 183 } 184 185 private void writeRefresh(IMarkupWriter writer, IRequestCycle cycle) 186 { 187 int refresh = getRefresh(); 188 189 if (refresh <= 0) 190 return; 191 192 // Here comes the tricky part ... have to assemble a complete URL 193 // for the current page. 194 195 IEngineService pageService = getPageService(); 196 String pageName = getPage().getPageName(); 197 198 ILink link = pageService.getLink(false, pageName); 199 200 StringBuffer buffer = new StringBuffer(); 201 buffer.append(refresh); 202 buffer.append("; URL="); 203 buffer.append(StringUtils.replace(link.getAbsoluteURL(), "&", "&")); 204 205 writeMetaTag(writer, "http-equiv", "Refresh", buffer.toString()); 206 } 207 208 private void writeMetaTag(IMarkupWriter writer, String key, String value, String content) 209 { 210 writer.beginEmpty("meta"); 211 writer.attribute(key, value); 212 writer.attribute("content", content); 213 writer.println(); 214 } 215 216 private void writeRelations(IMarkupWriter writer, List relations) 217 { 218 Iterator i = relations.iterator(); 219 220 while (i.hasNext()) 221 { 222 RelationBean relationBean = (RelationBean) i.next(); 223 if (relationBean != null) 224 writeRelation(writer, relationBean); 225 } 226 } 227 228 private void writeRelation(IMarkupWriter writer, RelationBean relationBean) 229 { 230 writer.beginEmpty("link"); 231 232 writeAttributeIfNotNull(writer, "rel", relationBean.getRel()); 233 writeAttributeIfNotNull(writer, "rev", relationBean.getRev()); 234 writeAttributeIfNotNull(writer, "type", relationBean.getType()); 235 writeAttributeIfNotNull(writer, "media", relationBean.getMedia()); 236 writeAttributeIfNotNull(writer, "title", relationBean.getTitle()); 237 writeAttributeIfNotNull(writer, "href", relationBean.getHref()); 238 239 writer.println(); 240 } 241 242 private void writeAttributeIfNotNull(IMarkupWriter writer, String name, String value) 243 { 244 if (value != null) 245 writer.attribute(name, value); 246 } 247 248 /** 249 * Retrieves the {@link Shell} that was stored into the request 250 * cycle. This allows components wrapped by the {@link Shell} to 251 * locate it and access the services it provides. 252 * 253 * @since 4.1.1 254 */ 255 public static Shell get(IRequestCycle cycle) 256 { 257 return (Shell) cycle.getAttribute(SHELL_ATTRIBUTE); 258 } 259 260 /** 261 * Adds a relation (stylesheets, favicon, e.t.c.) to the page. 262 * 263 * @since 4.1.1 264 */ 265 public void addRelation(RelationBean relation) 266 { 267 List relations = getRelations(); 268 if (relations == null) 269 relations = new ArrayList(); 270 271 if (!relations.contains(relation)) 272 relations.add(relation); 273 274 setRelations(relations); 275 } 276 277 /** 278 * Include additional content in the header of a page. 279 * 280 * @param content 281 * 282 * @since 4.1.1 283 */ 284 public void includeAdditionalContent(String content) 285 { 286 if (HiveMind.isBlank(content)) 287 return; 288 289 StringBuffer buffer = getContentBuffer(); 290 291 if (buffer == null) 292 buffer = new StringBuffer(); 293 294 buffer.append(content); 295 296 setContentBuffer(buffer); 297 } 298 299 public abstract boolean isDisableCaching(); 300 301 public abstract IRender getAjaxDelegate(); 302 303 public abstract IRender getDelegate(); 304 305 public abstract int getRefresh(); 306 307 public abstract IAsset getStylesheet(); 308 309 public abstract Object getStylesheets(); 310 311 public abstract String getTitle(); 312 313 public abstract String getDoctype(); 314 315 public abstract boolean getRenderContentType(); 316 317 public abstract boolean isDisableTapestryMeta(); 318 319 public abstract ResponseBuilder getBuilder(); 320 321 /** @since 4.0 */ 322 public abstract ValueConverter getValueConverter(); 323 324 /** @since 4.0 */ 325 326 public abstract IEngineService getPageService(); 327 328 /** @since 4.0 */ 329 330 public abstract IApplicationSpecification getApplicationSpecification(); 331 332 /** @since 4.0 */ 333 334 public abstract IRender getBaseTagWriter(); 335 336 /** @since 4.0.1 */ 337 338 public abstract boolean getRenderBaseTag(); 339 340 /** @since 4.0.3 */ 341 342 public abstract boolean getRaw(); 343 344 /** @since 4.1.1 */ 345 346 public abstract List getRelations(); 347 348 /** @since 4.1.1 */ 349 350 public abstract void setRelations(List relations); 351 352 /** @since 4.1.1 */ 353 354 public abstract StringBuffer getContentBuffer(); 355 356 /** @since 4.1.1 */ 357 358 public abstract void setContentBuffer(StringBuffer buffer); 359 360 /** @since 4.1.4 */ 361 public abstract String getSearchIds(); 362 363 /** @since 4.1.4 */ 364 public abstract void setSearchIds(String ids); 365 366 }