blob: 9db502713553ba749e69de4784380b0956898a8c [file] [log] [blame]
/*
* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Tapestry" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache Tapestry", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package net.sf.tapestry.vlib;
import java.rmi.RemoteException;
import javax.ejb.CreateException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.rmi.PortableRemoteObject;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.tapestry.ApplicationRuntimeException;
import net.sf.tapestry.IRequestCycle;
import net.sf.tapestry.ResponseOutputStream;
import net.sf.tapestry.engine.SimpleEngine;
import net.sf.tapestry.form.IPropertySelectionModel;
import net.sf.tapestry.vlib.ejb.IBookQuery;
import net.sf.tapestry.vlib.ejb.IBookQueryHome;
import net.sf.tapestry.vlib.ejb.IOperations;
import net.sf.tapestry.vlib.ejb.IOperationsHome;
import net.sf.tapestry.vlib.ejb.Person;
import net.sf.tapestry.vlib.ejb.Publisher;
import net.sf.tapestry.vlib.pages.ApplicationUnavailable;
/**
*
* The engine for the Virtual Library.
* This exists to implement the external
* service, which allows the {@link net.sf.tapestry.vlib.pages.ViewBook}
* and {@link net.sf.tapestry.vlib.pages.PersonPage}
* pages to be bookmarked, and to provide
* a way for shutting down the application when the user logs out.
*
*
* @version $Id$
* @author Howard Lewis Ship
*
**/
public class VirtualLibraryEngine extends SimpleEngine
{
public static final Log LOG = LogFactory.getLog(VirtualLibraryEngine.class);
private static final boolean DEBUG_ENABLED = Boolean.getBoolean("net.sf.tapestry.vlib.debug-enabled");
private transient boolean _killSession;
// Home interfaces are static, such that they are only
// looked up once (JNDI lookup is very expensive).
private static IBookQueryHome _bookQueryHome;
private static IOperationsHome _operationsHome;
private transient IOperations _operations;
private static Context _rootNamingContext;
private transient IPropertySelectionModel _publisherModel;
private transient IPropertySelectionModel _personModel;
private transient String _applicationUnavailableMessage;
/**
* Creates an instance of {@link Visit}.
*
**/
public Object createVisit(IRequestCycle cycle)
{
cycle.getRequestContext().createSession();
setStateful();
return new Visit(this);
}
/**
* Removes the operations bean instance, if accessed this request cycle.
*
* <p>May invalidate the {@link HttpSession} (see {@link #logout()}).
**/
protected void cleanupAfterRequest(IRequestCycle cycle)
{
clearCache();
_applicationUnavailableMessage = null;
if (_killSession)
{
try
{
HttpSession session = cycle.getRequestContext().getSession();
if (session != null)
session.invalidate();
}
catch (IllegalStateException ex)
{
// Ignore.
}
}
}
/**
* Sets the visit property to null, and sets a flag that
* invalidates the {@link HttpSession} at the end of the request cycle.
*
**/
public void logout()
{
Visit visit = (Visit) getVisit();
if (visit != null)
visit.setUser(null);
_killSession = true;
}
public boolean isDebugEnabled()
{
return DEBUG_ENABLED;
}
public IBookQueryHome getBookQueryHome()
{
if (_bookQueryHome == null)
_bookQueryHome = (IBookQueryHome) findNamedObject("vlib/BookQuery", IBookQueryHome.class);
return _bookQueryHome;
}
public IOperationsHome getOperationsHome()
{
if (_operationsHome == null)
_operationsHome = (IOperationsHome) findNamedObject("vlib/Operations", IOperationsHome.class);
return _operationsHome;
}
/**
* Returns an instance of the Vlib Operations beans, which is a stateless
* session bean for performing certain operations.
*
* <p>The bean is automatically removed at the end of the request cycle.
*
**/
public IOperations getOperations()
{
IOperationsHome home;
for (int i = 0; i < 2; i++)
{
if (_operations == null)
{
try
{
home = getOperationsHome();
_operations = home.create();
break;
}
catch (CreateException ex)
{
throw new ApplicationRuntimeException("Error creating operations bean.", ex);
}
catch (RemoteException ex)
{
rmiFailure("Remote exception creating operations bean.", ex, i > 0);
}
}
}
return _operations;
}
public Object findNamedObject(String name, Class expectedClass)
{
Object result = null;
for (int i = 0; i < 2; i++)
{
try
{
Object raw = getRootNamingContext().lookup(name);
result = PortableRemoteObject.narrow(raw, expectedClass);
break;
}
catch (ClassCastException ex)
{
throw new ApplicationRuntimeException(
"Object " + name + " is not type " + expectedClass.getName() + ".",
ex);
}
catch (NamingException ex)
{
namingFailure("Unable to resolve object " + name + ".", ex, i > 0);
}
}
return result;
}
public Context getRootNamingContext()
{
for (int i = 0; i < 2; i++)
{
if (_rootNamingContext == null)
{
try
{
_rootNamingContext = new InitialContext();
break;
}
catch (NamingException ex)
{
namingFailure("Unable to locate root naming context.", ex, i > 0);
}
}
}
return _rootNamingContext;
}
/**
* Builds a model for entering in a publisher name, including an intial
* blank option. Problem: thie model is held for a long while, so it won't
* reflect publishers added by this user or others. Solution: coming; perhaps
* we'll age-out the model after a few minutes.
*
**/
public IPropertySelectionModel getPublisherModel()
{
if (_publisherModel == null)
_publisherModel = buildPublisherModel();
return _publisherModel;
}
private IPropertySelectionModel buildPublisherModel()
{
Publisher[] publishers = null;
EntitySelectionModel model = new EntitySelectionModel();
// Add in a default null value, such that the user can
// not select a specific Publisher.
model.add(null, "");
for (int i = 0; i < 2; i++)
{
IOperations operations = getOperations();
try
{
publishers = operations.getPublishers();
// Exit the retry loop
break;
}
catch (RemoteException ex)
{
rmiFailure("Unable to obtain list of publishers.", ex, i > 0);
}
}
// Add in the actual publishers. They are sorted by name.
for (int i = 0; i < publishers.length; i++)
model.add(publishers[i].getPrimaryKey(), publishers[i].getName());
return model;
}
/**
* Invoked from {@link Visit#clearCache()} (and at the end of the request
* cycle) to clear the publisher and person
* {@link IPropertySelectionModel} models.
*
**/
public void clearCache()
{
_publisherModel = null;
_personModel = null;
}
/**
* Returns a model that contains all the known Person's, sorted by last name,
* then first. The label for the model matches the user's natural name.
*
**/
public IPropertySelectionModel getPersonModel()
{
if (_personModel == null)
_personModel = buildPersonModel();
return _personModel;
}
private IPropertySelectionModel buildPersonModel()
{
Person[] persons = null;
for (int i = 0; i < 2; i++)
{
IOperations operations = getOperations();
try
{
persons = operations.getPersons();
break;
}
catch (RemoteException ex)
{
rmiFailure("Unable to obtain list of persons.", ex, i > 0);
}
}
EntitySelectionModel model = new EntitySelectionModel();
// On this one, we don't include a null option.
for (int i = 0; i < persons.length; i++)
model.add(persons[i].getPrimaryKey(), persons[i].getNaturalName());
return model;
}
/**
* Creates a new {@link IBookQuery} EJB instance.
*
**/
public IBookQuery createNewQuery()
{
IBookQuery result = null;
for (int i = 0; i < 2; i++)
{
IBookQueryHome home = getBookQueryHome();
try
{
result = home.create();
break;
}
catch (CreateException ex)
{
throw new ApplicationRuntimeException("Could not create BookQuery bean.", ex);
}
catch (RemoteException ex)
{
rmiFailure("Remote exception creating BookQuery bean.", ex, i > 0);
}
}
return result;
}
/**
* Invoked in various places to present an error message to the user.
* This sets the error property of either the
* {@link net.sf.tapestry.vlib.pages.Home} or
* {@link net.sf.tapestry.vlib.pages.MyLibrary} page
* (the latter only if the user is logged in),
* and sets the selected page for rendering the response.
*
**/
public void presentError(String error, IRequestCycle cycle)
{
String pageName = "Home";
// Get, but don't create, the visit.
Visit visit = (Visit) getVisit();
if (visit != null && visit.isUserLoggedIn())
pageName = "MyLibrary";
IErrorProperty page = (IErrorProperty) cycle.getPage(pageName);
page.setError(error);
cycle.setPage(pageName);
}
/**
* Invoked after an operation on a home or remote interface
* throws a RemoteException; this clears any cache of
* home and remote interfaces.
*
* @param message the message for the exception, or for the log message
* @param ex the exception thrown
* @param finalFailure if true, an {@link ApplicationRuntimeException}
* is thrown after the message is logged.
*
**/
public void rmiFailure(String message, RemoteException ex, boolean finalFailure)
{
LOG.error(message, ex);
clearEJBs();
if (finalFailure)
punt(message, ex);
}
private void punt(String message, Throwable ex)
{
_applicationUnavailableMessage = message;
throw new ApplicationRuntimeException(message, ex);
}
/**
* As with {@link #rmiFailure(String, RemoteException, boolean)}, but for
* {@link NamingException}.
*
**/
public void namingFailure(String message, NamingException ex, boolean finalFailure)
{
LOG.error(message, ex);
clearEJBs();
if (finalFailure)
punt(message, ex);
}
private void clearEJBs()
{
_bookQueryHome = null;
_operations = null;
_operationsHome = null;
_rootNamingContext = null;
}
/**
* Invoked when any kind of runtime exception percolates up to the
* top level service method. Normally, the standard Exception
* page is displayed; we logout and setup our own version of the page
* instead.
*
**/
protected void activateExceptionPage(IRequestCycle cycle, ResponseOutputStream output, Throwable cause)
throws ServletException
{
try
{
logout();
ApplicationUnavailable page = (ApplicationUnavailable) cycle.getPage("ApplicationUnavailable");
String message = _applicationUnavailableMessage;
if (message == null)
message = cause.getMessage();
if (message == null)
message = cause.getClass().getName();
page.activate(message, cause);
cycle.setPage(page);
renderResponse(cycle, output);
}
catch (Throwable t)
{
super.activateExceptionPage(cycle, output, cause);
}
}
}