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 }