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 if (!cycle.isRewinding())
246 cycle.commitPageChanges();
247
248 builder.render(cycle.isRewinding() ? NullWriter.getSharedInstance() : null, this, cycle);
249 }
250 finally
251 {
252 firePageEndRender();
253 }
254 }
255
256 public void setChangeObserver(ChangeObserver value)
257 {
258 _changeObserver = value;
259 }
260
261 /** @since 3.0 * */
262
263 public void setPageName(String pageName)
264 {
265 if (_pageName != null)
266 throw new ApplicationRuntimeException(Tapestry
267 .getMessage("AbstractPage.attempt-to-change-name"));
268
269 _pageName = pageName;
270 }
271
272 /**
273 * By default, pages are not protected and this method does nothing.
274 */
275
276 public void validate(IRequestCycle cycle)
277 {
278 Tapestry.addMethodInvocation(Tapestry.ABSTRACTPAGE_VALIDATE_METHOD_ID);
279
280 firePageValidate();
281 }
282
283 public IRequestCycle getRequestCycle()
284 {
285 return _requestCycle;
286 }
287
288 public void addPageDetachListener(PageDetachListener listener)
289 {
290 addListener(PageDetachListener.class, listener);
291 }
292
293 private void addListener(Class listenerClass, EventListener listener)
294 {
295 if (_listenerList == null)
296 _listenerList = new EventListenerList();
297
298 _listenerList.add(listenerClass, listener);
299 }
300
301 /**
302 * @since 2.1-beta-2
303 */
304
305 private void removeListener(Class listenerClass, EventListener listener)
306 {
307 if (_listenerList != null)
308 _listenerList.remove(listenerClass, listener);
309 }
310
311 /** @since 4.0 */
312 public void addPageBeginRenderListener(PageBeginRenderListener listener)
313 {
314 addListener(PageBeginRenderListener.class, listener);
315 }
316
317 /** @since 4.0 */
318 public void addPageEndRenderListener(PageEndRenderListener listener)
319 {
320 addListener(PageEndRenderListener.class, listener);
321 }
322
323 /** @since 4.0 */
324 public void removePageBeginRenderListener(PageBeginRenderListener listener)
325 {
326 removeListener(PageBeginRenderListener.class, listener);
327 }
328
329 /** @since 4.0 */
330 public void removePageEndRenderListener(PageEndRenderListener listener)
331 {
332 removeListener(PageEndRenderListener.class, listener);
333 }
334
335 /**
336 * @since 4.0
337 */
338
339 public void firePageAttached()
340 {
341 if (_listenerList == null)
342 return;
343
344 PageEvent event = null;
345 Object[] listeners = _listenerList.getListenerList();
346
347 for(int i = listeners.length-2; i >= 0; i -= 2)
348 {
349 if (listeners[i] == PageAttachListener.class)
350 {
351 PageAttachListener l = (PageAttachListener) listeners[i + 1];
352
353 if (event == null)
354 event = new PageEvent(this, _requestCycle);
355
356 l.pageAttached(event);
357 }
358 }
359 }
360
361 /**
362 * @since 1.0.5
363 */
364
365 protected void firePageDetached()
366 {
367 if (_listenerList == null)
368 return;
369
370 PageEvent event = null;
371 Object[] listeners = _listenerList.getListenerList();
372
373 for (int i = 0; i < listeners.length; i += 2)
374 {
375 if (listeners[i] == PageDetachListener.class)
376 {
377 PageDetachListener l = (PageDetachListener) listeners[i + 1];
378
379 if (event == null)
380 event = new PageEvent(this, _requestCycle);
381
382 l.pageDetached(event);
383 }
384 }
385 }
386
387 /**
388 * @since 1.0.5
389 */
390
391 protected void firePageBeginRender()
392 {
393 if (_listenerList == null)
394 return;
395
396 PageEvent event = null;
397 Object[] listeners = _listenerList.getListenerList();
398
399 for(int i = listeners.length-2; i >= 0; i -= 2)
400 {
401 if (listeners[i] == PageBeginRenderListener.class)
402 {
403 PageBeginRenderListener l = (PageBeginRenderListener)listeners[i + 1];
404
405 if (event == null)
406 event = new PageEvent(this, _requestCycle);
407
408 l.pageBeginRender(event);
409 }
410 }
411 }
412
413 /**
414 * @since 1.0.5
415 */
416
417 protected void firePageEndRender()
418 {
419 if (_listenerList == null)
420 return;
421
422 PageEvent event = null;
423 Object[] listeners = _listenerList.getListenerList();
424
425 for (int i = 0; i < listeners.length; i += 2)
426 {
427 if (listeners[i] == PageEndRenderListener.class)
428 {
429 PageEndRenderListener l = (PageEndRenderListener) listeners[i + 1];
430
431 if (event == null)
432 event = new PageEvent(this, _requestCycle);
433
434 l.pageEndRender(event);
435 }
436 }
437 }
438
439 /**
440 * @since 2.1-beta-2
441 */
442
443 public void removePageDetachListener(PageDetachListener listener)
444 {
445 removeListener(PageDetachListener.class, listener);
446 }
447
448 /** @since 2.2 * */
449
450 public void beginPageRender()
451 {
452 firePageBeginRender();
453 }
454
455 /** @since 2.2 * */
456
457 public void endPageRender()
458 {
459 firePageEndRender();
460 }
461
462 /** @since 3.0 * */
463
464 public String getPageName()
465 {
466 return _pageName;
467 }
468
469 public void addPageValidateListener(PageValidateListener listener)
470 {
471 addListener(PageValidateListener.class, listener);
472 }
473
474 public void removePageValidateListener(PageValidateListener listener)
475 {
476 removeListener(PageValidateListener.class, listener);
477 }
478
479 /** @since 4.0 */
480 public void addPageAttachListener(PageAttachListener listener)
481 {
482 addListener(PageAttachListener.class, listener);
483 }
484
485 /** @since 4.0 */
486 public void removePageAttachListener(PageAttachListener listener)
487 {
488 removeListener(PageAttachListener.class, listener);
489 }
490
491 protected void firePageValidate()
492 {
493 if (_listenerList == null)
494 return;
495
496 PageEvent event = null;
497 Object[] listeners = _listenerList.getListenerList();
498
499 for (int i = 0; i < listeners.length; i += 2)
500 {
501 if (listeners[i] == PageValidateListener.class)
502 {
503 PageValidateListener l = (PageValidateListener) listeners[i + 1];
504
505 if (event == null)
506 event = new PageEvent(this, _requestCycle);
507
508 l.pageValidate(event);
509 }
510 }
511 }
512
513 /**
514 * Returns the output encoding to be used when rendering this page. This value is usually cached
515 * from the Engine.
516 *
517 * @since 3.0
518 */
519 protected String getOutputEncoding()
520 {
521 if (_outputEncoding == null)
522 _outputEncoding = getEngine().getOutputEncoding();
523
524 return _outputEncoding;
525 }
526 }