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;
016
017 import org.apache.commons.logging.Log;
018 import org.apache.commons.logging.LogFactory;
019 import org.apache.hivemind.ApplicationRuntimeException;
020 import org.apache.tapestry.engine.NullWriter;
021 import org.apache.tapestry.event.*;
022 import org.apache.tapestry.services.ResponseBuilder;
023 import org.apache.tapestry.util.StringSplitter;
024
025 import javax.swing.event.EventListenerList;
026 import java.util.EventListener;
027 import java.util.Locale;
028
029 /**
030 * Abstract base class implementing the {@link IPage}interface.
031 *
032 * @author Howard Lewis Ship, David Solis
033 * @since 0.2.9
034 */
035
036 public abstract class AbstractPage extends BaseComponent implements IPage
037 {
038 private static final Log LOG = LogFactory.getLog(AbstractPage.class);
039
040 /**
041 * Object to be notified when a observered property changes. Observered properties are the ones
042 * that will be persisted between request cycles. Unobserved properties are reconstructed.
043 */
044
045 private ChangeObserver _changeObserver;
046
047 /**
048 * The {@link IEngine}the page is currently attached to.
049 */
050
051 private IEngine _engine;
052
053 /**
054 * The qualified name of the page, which may be prefixed by the namespace.
055 *
056 * @since 2.3
057 */
058
059 private String _pageName;
060
061 /**
062 * Set when the page is attached to the engine.
063 */
064
065 private IRequestCycle _requestCycle;
066
067 /**
068 * The locale of the page, initially determined from the {@link IEngine engine}.
069 */
070
071 private Locale _locale;
072
073 /**
074 * A list of listeners for the page.
075 *
076 * @see PageBeginRenderListener
077 * @see PageEndRenderListener
078 * @see PageDetachListener
079 * @since 1.0.5
080 */
081
082 private EventListenerList _listenerList;
083
084 /**
085 * The output encoding to be used when rendering this page. This value is cached from the
086 * engine.
087 *
088 * @since 3.0
089 */
090 private String _outputEncoding;
091
092 /**
093 * Used to dynamically include script content automatically for form specific includes.
094 * @since 4.1.2
095 */
096 private boolean _hasForms;
097
098 /**
099 * Dynamically causes widget dojo layer to be included if set to true.
100 * @since 4.1.2.
101 */
102 private boolean _hasWidgets;
103
104 /**
105 * Standard constructor. Does nothing.
106 *
107 * @since 2.2
108 */
109
110 public AbstractPage()
111 {
112 }
113
114 /**
115 * Prepares the page to be returned to the pool.
116 * <ul>
117 * <li>Clears the changeObserved property
118 * <li>Invokes {@link PageDetachListener#pageDetached(PageEvent)}on all listeners
119 * <li>Clears the engine and requestCycle properties
120 * </ul>
121 * <p>
122 * Subclasses may override this method, but must invoke this implementation (usually, last).
123 *
124 * @see PageDetachListener
125 */
126
127 public void detach()
128 {
129 Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_DETACH_METHOD_ID);
130
131 // Do this first,so that any changes to persistent properties do not
132 // cause errors.
133
134 _changeObserver = null;
135
136 firePageDetached();
137
138 _engine = null;
139 _requestCycle = null;
140 }
141
142 public IEngine getEngine()
143 {
144 return _engine;
145 }
146
147 public ChangeObserver getChangeObserver()
148 {
149 return _changeObserver;
150 }
151
152 /**
153 * Returns the name of the page.
154 */
155
156 public String getExtendedId()
157 {
158 return _pageName;
159 }
160
161 /**
162 * Pages always return null for idPath.
163 */
164
165 public String getIdPath()
166 {
167 return null;
168 }
169
170 /**
171 * Returns the locale for the page, which may be null if the locale is not known (null
172 * corresponds to the "default locale").
173 */
174
175 public Locale getLocale()
176 {
177 return _locale;
178 }
179
180 public void setLocale(Locale value)
181 {
182 if (_locale != null)
183 throw new ApplicationRuntimeException(Tapestry
184 .getMessage("AbstractPage.attempt-to-change-locale"));
185
186 _locale = value;
187 }
188
189 public IComponent getNestedComponent(String path)
190 {
191 StringSplitter splitter;
192 IComponent current;
193 String[] elements;
194 int i;
195
196 if (path == null)
197 return this;
198
199 splitter = new StringSplitter('.');
200 current = this;
201
202 elements = splitter.splitToArray(path);
203 for (i = 0; i < elements.length; i++)
204 {
205 current = current.getComponent(elements[i]);
206 }
207
208 return current;
209
210 }
211
212 /**
213 * Called by the {@link IEngine engine} to attach the page to itself. Does <em>not</em> change
214 * the locale, but since a page is selected from the
215 * {@link org.apache.tapestry.engine.IPageSource} pool based on its locale matching the engine's
216 * locale, they should match anyway.
217 */
218
219 public void attach(IEngine engine, IRequestCycle cycle)
220 {
221 if (_engine != null)
222 LOG.error(this + " attach(" + engine + "), but engine = " + _engine);
223
224 _engine = engine;
225 _requestCycle = cycle;
226 }
227
228 /**
229 * Renders the page.
230 * <ul>
231 * <li>Invokes {@link PageBeginRenderListener#pageBeginRender(PageEvent)}
232 * <li>Invokes {@link #beginPageRender()}
233 * <li>Invokes {@link IRequestCycle#commitPageChanges()}(if not rewinding)
234 * <li>Invokes {@link #render(IMarkupWriter, IRequestCycle)}
235 * <li>Invokes {@link PageEndRenderListener#pageEndRender(PageEvent)}(this occurs even if a
236 * previous step throws an exception)
237 * </ul>
238 */
239
240 public void renderPage(ResponseBuilder builder, IRequestCycle cycle)
241 {
242 try
243 {
244 firePageBeginRender();
245
246 if (!cycle.isRewinding())
247 cycle.commitPageChanges();
248
249 builder.render(cycle.isRewinding() ? NullWriter.getSharedInstance() : null, this, cycle);
250 }
251 finally
252 {
253 firePageEndRender();
254 }
255 }
256
257 public void setChangeObserver(ChangeObserver value)
258 {
259 _changeObserver = value;
260 }
261
262 /** @since 3.0 * */
263
264 public void setPageName(String pageName)
265 {
266 if (_pageName != null)
267 throw new ApplicationRuntimeException(Tapestry
268 .getMessage("AbstractPage.attempt-to-change-name"));
269
270 _pageName = pageName;
271 }
272
273 /**
274 * By default, pages are not protected and this method does nothing.
275 */
276
277 public void validate(IRequestCycle cycle)
278 {
279 Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID);
280
281 firePageValidate();
282 }
283
284 public IRequestCycle getRequestCycle()
285 {
286 return _requestCycle;
287 }
288
289 public void addPageDetachListener(PageDetachListener listener)
290 {
291 addListener(PageDetachListener.class, listener);
292 }
293
294 private void addListener(Class listenerClass, EventListener listener)
295 {
296 if (_listenerList == null)
297 _listenerList = new EventListenerList();
298
299 _listenerList.add(listenerClass, listener);
300 }
301
302 /**
303 * @since 2.1-beta-2
304 */
305
306 private void removeListener(Class listenerClass, EventListener listener)
307 {
308 if (_listenerList != null)
309 _listenerList.remove(listenerClass, listener);
310 }
311
312 /** @since 4.0 */
313 public void addPageBeginRenderListener(PageBeginRenderListener listener)
314 {
315 addListener(PageBeginRenderListener.class, listener);
316 }
317
318 /** @since 4.0 */
319 public void addPageEndRenderListener(PageEndRenderListener listener)
320 {
321 addListener(PageEndRenderListener.class, listener);
322 }
323
324 /** @since 4.0 */
325 public void removePageBeginRenderListener(PageBeginRenderListener listener)
326 {
327 removeListener(PageBeginRenderListener.class, listener);
328 }
329
330 /** @since 4.0 */
331 public void removePageEndRenderListener(PageEndRenderListener listener)
332 {
333 removeListener(PageEndRenderListener.class, listener);
334 }
335
336 /**
337 * @since 4.0
338 */
339
340 public void firePageAttached()
341 {
342 if (_listenerList == null)
343 return;
344
345 PageEvent event = null;
346 Object[] listeners = _listenerList.getListenerList();
347
348 for(int i = listeners.length-2; i >= 0; i -= 2)
349 {
350 if (listeners[i] == PageAttachListener.class)
351 {
352 PageAttachListener l = (PageAttachListener) listeners[i + 1];
353
354 if (event == null)
355 event = new PageEvent(this, _requestCycle);
356
357 l.pageAttached(event);
358 }
359 }
360 }
361
362 /**
363 * @since 1.0.5
364 */
365
366 protected void firePageDetached()
367 {
368 if (_listenerList == null)
369 return;
370
371 PageEvent event = null;
372 Object[] listeners = _listenerList.getListenerList();
373
374 for (int i = 0; i < listeners.length; i += 2)
375 {
376 if (listeners[i] == PageDetachListener.class)
377 {
378 PageDetachListener l = (PageDetachListener) listeners[i + 1];
379
380 if (event == null)
381 event = new PageEvent(this, _requestCycle);
382
383 l.pageDetached(event);
384 }
385 }
386 }
387
388 /**
389 * @since 1.0.5
390 */
391
392 protected void firePageBeginRender()
393 {
394 if (_listenerList == null)
395 return;
396
397 PageEvent event = null;
398 Object[] listeners = _listenerList.getListenerList();
399
400 for(int i = listeners.length-2; i >= 0; i -= 2)
401 {
402 if (listeners[i] == PageBeginRenderListener.class)
403 {
404 PageBeginRenderListener l = (PageBeginRenderListener)listeners[i + 1];
405
406 if (event == null)
407 event = new PageEvent(this, _requestCycle);
408
409 l.pageBeginRender(event);
410 }
411 }
412 }
413
414 /**
415 * @since 1.0.5
416 */
417
418 protected void firePageEndRender()
419 {
420 if (_listenerList == null)
421 return;
422
423 PageEvent event = null;
424 Object[] listeners = _listenerList.getListenerList();
425
426 for (int i = 0; i < listeners.length; i += 2)
427 {
428 if (listeners[i] == PageEndRenderListener.class)
429 {
430 PageEndRenderListener l = (PageEndRenderListener) listeners[i + 1];
431
432 if (event == null)
433 event = new PageEvent(this, _requestCycle);
434
435 l.pageEndRender(event);
436 }
437 }
438 }
439
440 /**
441 * @since 2.1-beta-2
442 */
443
444 public void removePageDetachListener(PageDetachListener listener)
445 {
446 removeListener(PageDetachListener.class, listener);
447 }
448
449 /** @since 2.2 * */
450
451 public void beginPageRender()
452 {
453 firePageBeginRender();
454 }
455
456 /** @since 2.2 * */
457
458 public void endPageRender()
459 {
460 firePageEndRender();
461 }
462
463 protected void cleanupAfterRender(IRequestCycle cycle)
464 {
465 }
466
467 /** @since 3.0 * */
468
469 public String getPageName()
470 {
471 return _pageName;
472 }
473
474 public void addPageValidateListener(PageValidateListener listener)
475 {
476 addListener(PageValidateListener.class, listener);
477 }
478
479 public void removePageValidateListener(PageValidateListener listener)
480 {
481 removeListener(PageValidateListener.class, listener);
482 }
483
484 /** @since 4.0 */
485 public void addPageAttachListener(PageAttachListener listener)
486 {
487 addListener(PageAttachListener.class, listener);
488 }
489
490 /** @since 4.0 */
491 public void removePageAttachListener(PageAttachListener listener)
492 {
493 removeListener(PageAttachListener.class, listener);
494 }
495
496 protected void firePageValidate()
497 {
498 if (_listenerList == null)
499 return;
500
501 PageEvent event = null;
502 Object[] listeners = _listenerList.getListenerList();
503
504 for (int i = 0; i < listeners.length; i += 2)
505 {
506 if (listeners[i] == PageValidateListener.class)
507 {
508 PageValidateListener l = (PageValidateListener) listeners[i + 1];
509
510 if (event == null)
511 event = new PageEvent(this, _requestCycle);
512
513 l.pageValidate(event);
514 }
515 }
516 }
517
518 /**
519 * Returns the output encoding to be used when rendering this page. This value is usually cached
520 * from the Engine.
521 *
522 * @since 3.0
523 */
524 protected String getOutputEncoding()
525 {
526 if (_outputEncoding == null)
527 _outputEncoding = getEngine().getOutputEncoding();
528
529 return _outputEncoding;
530 }
531
532 public boolean hasFormComponents()
533 {
534 return _hasForms;
535 }
536
537 public void setHasFormComponents(boolean value)
538 {
539 _hasForms = value;
540 }
541
542 public boolean hasWidgets()
543 {
544 return _hasWidgets;
545 }
546
547 public void setHasWidgets(boolean value)
548 {
549 _hasWidgets = value;
550 }
551 }