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 015package org.apache.tapestry; 016 017import java.util.EventListener; 018import java.util.Locale; 019 020import javax.swing.event.EventListenerList; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024import org.apache.hivemind.ApplicationRuntimeException; 025import org.apache.tapestry.event.ChangeObserver; 026import org.apache.tapestry.event.PageAttachListener; 027import org.apache.tapestry.event.PageBeginRenderListener; 028import org.apache.tapestry.event.PageDetachListener; 029import org.apache.tapestry.event.PageEndRenderListener; 030import org.apache.tapestry.event.PageEvent; 031import org.apache.tapestry.event.PageRenderListener; 032import org.apache.tapestry.event.PageValidateListener; 033import 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 042public 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}