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 }