blob: be0a36a7abc27030170727482698ccbc4c1571a4 [file]
//
// Tapestry Web Application Framework
// Copyright (c) 2000-2002 by Howard Lewis Ship
//
// Howard Lewis Ship
// http://sf.net/projects/tapestry
// mailto:hship@users.sf.net
//
// This library is free software.
//
// You may redistribute it and/or modify it under the terms of the GNU
// Lesser General Public License as published by the Free Software Foundation.
//
// Version 2.1 of the license should be included with this distribution in
// the file LICENSE, as well as License.html. If the license is not
// included with this distribution, you may find a copy at the FSF web
// site at 'www.gnu.org' or 'www.fsf.org', or you may write to the
// Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139 USA.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied waranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
package net.sf.tapestry;
import java.io.OutputStream;
import java.util.EventListener;
import java.util.Locale;
import javax.swing.event.EventListenerList;
import org.apache.log4j.Category;
import net.sf.tapestry.event.ChangeObserver;
import net.sf.tapestry.event.PageCleanupListener;
import net.sf.tapestry.event.PageDetachListener;
import net.sf.tapestry.event.PageEvent;
import net.sf.tapestry.event.PageRenderListener;
import net.sf.tapestry.util.StringSplitter;
/**
* Abstract base class implementing the {@link IPage} interface.
*
* @version $Id$
* @author Howard Lewis Ship, David Solis
* @since 0.2.9
*
**/
public abstract class AbstractPage extends BaseComponent implements IPage
{
private static final Category CAT = Category.getInstance(AbstractPage.class);
/**
* Object to be notified when a observered property changes. Observered
* properties are the ones that will be persisted between request cycles.
* Unobserved properties are reconstructed.
*
**/
private ChangeObserver _changeObserver;
/**
* The {@link IEngine} the page is currently attached to. This may
* be read, but not changed, but subclasses.
*
* @deprecated this will be removed after 2.1, classes should use {@link #getEngine()}
*
**/
protected IEngine engine;
/**
* The visit object, if any, for the application. Set inside
* {@link #attach(IEngine)} and cleared
* by {@link #detach()}.
*
**/
private Object _visit;
/**
* The name of this page. This may be read, but not changed, by
* subclasses.
*
* @deprecated this will be removed after 2.1, classes should use {@link #getName()}
*
**/
protected String name;
/**
* Set when the page is attached to the engine.
*
**/
private IRequestCycle _requestCycle;
/**
* The locale of the page, initially determined from the {@link IEngine engine}.
*
**/
private Locale _locale;
/**
* A list of listeners for the page.
* @see PageRenderListener
* @see PageDetachListener
*
* @since 1.0.5
**/
private EventListenerList _listenerList;
/**
* Implemented in subclasses to provide a particular kind of
* response writer (and therefore, a particular kind of
* content).
*
**/
abstract public IMarkupWriter getResponseWriter(OutputStream out);
/**
* Prepares the page to be returned to the pool.
* <ul>
* <li>Invokes {@link PageDetachListener#pageDetached(PageEvent)} on all listeners
* <li>Clears the engine, visit and changeObserver properties
* </ul>
*
* <p>Subclasses may override this method, but must invoke this
* implementation (usually, last).
*
**/
public void detach()
{
firePageDetached();
engine = null;
_visit = null;
_changeObserver = null;
_requestCycle = null;
}
public IEngine getEngine()
{
return engine;
}
public ChangeObserver getChangeObserver()
{
return _changeObserver;
}
/**
* Returns the name of the page.
*
**/
public String getExtendedId()
{
return name;
}
/**
* Pages always return null for idPath.
*
**/
public String getIdPath()
{
return null;
}
/**
* Returns the locale for the page, which may be null if the
* locale is not known (null corresponds to the "default locale").
*
**/
public Locale getLocale()
{
return _locale;
}
public void setLocale(Locale value)
{
if (_locale != null)
throw new ApplicationRuntimeException(Tapestry.getString("AbstractPage.attempt-to-change-locale"));
_locale = value;
}
public String getName()
{
return name;
}
public IPage getPage()
{
return this;
}
public IComponent getNestedComponent(String path)
{
StringSplitter splitter;
IComponent current;
String[] elements;
int i;
if (path == null)
return this;
splitter = new StringSplitter('.');
current = this;
elements = splitter.splitToArray(path);
for (i = 0; i < elements.length; i++)
{
current = current.getComponent(elements[i]);
}
return current;
}
/**
* Called by the {@link IEngine engine} to attach the page
* to itself. Does
* <em>not</em> change the locale, but since a page is selected
* from the {@link IPageSource} pool based on its
* locale matching the engine's locale, they should match
* anyway.
*
**/
public void attach(IEngine value)
{
if (engine != null)
CAT.error(this +" attach(" + value + "), but engine = " + engine);
engine = value;
}
/**
*
* <ul>
* <li>Invokes {@link PageRenderListener#pageBeginRender(PageEvent)}
* <li>Invokes {@link #beginResponse(IMarkupWriter, IRequestCycle)}
* <li>Invokes {@link IRequestCycle#commitPageChanges()} (if not rewinding)
* <li>Invokes {@link #render(IMarkupWriter, IRequestCycle)}
* <li>Invokes {@link PageRenderListener#pageEndRender(PageEvent)} (this occurs
* even if a previous step throws an exception)
*
**/
public void renderPage(IMarkupWriter writer, IRequestCycle cycle) throws RequestCycleException
{
try
{
firePageBeginRender();
beginResponse(writer, cycle);
if (!cycle.isRewinding())
cycle.commitPageChanges();
render(writer, cycle);
}
catch (PageRecorderCommitException ex)
{
throw new RequestCycleException(ex.getMessage(), null, ex);
}
finally
{
firePageEndRender();
}
}
public void setChangeObserver(ChangeObserver value)
{
_changeObserver = value;
}
public void setName(String value)
{
if (name != null)
throw new ApplicationRuntimeException(Tapestry.getString("AbstractPage.attempt-to-change-name"));
name = value;
}
/**
* By default, pages are not protected and this method does nothing.
*
**/
public void validate(IRequestCycle cycle) throws RequestCycleException
{
// Does nothing.
}
/**
* Does nothing, subclasses may override as needed.
*
*
**/
public void beginResponse(IMarkupWriter writer, IRequestCycle cycle) throws RequestCycleException
{
}
public IRequestCycle getRequestCycle()
{
return _requestCycle;
}
public void setRequestCycle(IRequestCycle value)
{
_requestCycle = value;
}
/**
* Invokes {@link PageCleanupListener#pageCleanup(PageEvent)} on any
* listener.
*
* <p>Subclasses may override, but should invoke this implementation.
*
**/
public void cleanupPage()
{
firePageCleanup();
}
/**
* Returns the visit object obtained from the engine via
* {@link IEngine#getVisit(IRequestCycle)}.
*
**/
public Object getVisit()
{
if (_visit == null)
_visit = engine.getVisit(_requestCycle);
return _visit;
}
public void addPageDetachListener(PageDetachListener listener)
{
addListener(PageDetachListener.class, listener);
}
private void addListener(Class listenerClass, EventListener listener)
{
if (_listenerList == null)
_listenerList = new EventListenerList();
_listenerList.add(listenerClass, listener);
}
/**
* @seince 2.1-beta-2
*
**/
private void removeListener(Class listenerClass, EventListener listener)
{
if (_listenerList != null)
_listenerList.remove(listenerClass, listener);
}
public void addPageRenderListener(PageRenderListener listener)
{
addListener(PageRenderListener.class, listener);
}
public void addPageCleanupListener(PageCleanupListener listener)
{
addListener(PageCleanupListener.class, listener);
}
/**
* @since 1.0.5
*
**/
protected void firePageDetached()
{
if (_listenerList == null)
return;
PageEvent event = null;
Object[] listeners = _listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2)
{
if (listeners[i] == PageDetachListener.class)
{
PageDetachListener l = (PageDetachListener) listeners[i + 1];
if (event == null)
event = new PageEvent(this, _requestCycle);
l.pageDetached(event);
}
}
}
/**
* @since 1.0.5
*
**/
protected void firePageBeginRender()
{
if (_listenerList == null)
return;
PageEvent event = null;
Object[] listeners = _listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2)
{
if (listeners[i] == PageRenderListener.class)
{
PageRenderListener l = (PageRenderListener) listeners[i + 1];
if (event == null)
event = new PageEvent(this, _requestCycle);
l.pageBeginRender(event);
}
}
}
/**
* @since 1.0.5
*
**/
protected void firePageEndRender()
{
if (_listenerList == null)
return;
PageEvent event = null;
Object[] listeners = _listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2)
{
if (listeners[i] == PageRenderListener.class)
{
PageRenderListener l = (PageRenderListener) listeners[i + 1];
if (event == null)
event = new PageEvent(this, _requestCycle);
l.pageEndRender(event);
}
}
}
protected void firePageCleanup()
{
if (_listenerList == null)
return;
PageEvent event = null;
Object[] listeners = _listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2)
{
if (listeners[i] == PageCleanupListener.class)
{
PageCleanupListener l = (PageCleanupListener) listeners[i + 1];
if (event == null)
event = new PageEvent(this, null);
l.pageCleanup(event);
}
}
}
/**
* @since 2.1-beta-2
*
**/
public void removePageCleanupListener(PageCleanupListener listener)
{
removeListener(PageCleanupListener.class, listener);
}
/**
* @since 2.1-beta-2
*
**/
public void removePageDetachListener(PageDetachListener listener)
{
removeListener(PageDetachListener.class, listener);
}
/**
* @since 2.1-beta-2
*
**/
public void removePageRenderListener(PageRenderListener listener)
{
removeListener(PageRenderListener.class, listener);
}
}