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