| /* |
| * ==================================================================== |
| * 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); |
| } |
| } |
| |
| } |