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