package org.jboss.seam.wicket; import javax.servlet.http.HttpServletRequest; import org.apache.wicket.IRequestTarget; import org.apache.wicket.MetaDataKey; import org.apache.wicket.Page; import org.apache.wicket.Request; import org.apache.wicket.RequestCycle; import org.apache.wicket.Response; import org.apache.wicket.Session; import org.apache.wicket.protocol.http.WebApplication; import org.apache.wicket.protocol.http.WebRequest; import org.apache.wicket.protocol.http.WebRequestCycle; import org.apache.wicket.protocol.http.WebRequestCycleProcessor; import org.apache.wicket.protocol.http.WebResponse; import org.apache.wicket.request.IRequestCycleProcessor; import org.apache.wicket.request.target.component.BookmarkablePageRequestTarget; import org.apache.wicket.request.target.component.IPageRequestTarget; import org.jboss.seam.contexts.Contexts; import org.jboss.seam.contexts.Lifecycle; import org.jboss.seam.contexts.ServletLifecycle; import org.jboss.seam.core.ConversationPropagation; import org.jboss.seam.core.Events; import org.jboss.seam.core.Manager; import org.jboss.seam.log.LogProvider; import org.jboss.seam.log.Logging; import org.jboss.seam.servlet.ServletRequestSessionMap; import org.jboss.seam.web.ServletContexts; import org.jboss.seam.wicket.international.SeamStatusMessagesListener; /** * The base class for Seam Web Applications * * @author Pete Muir, Clint Popetz * */ public abstract class SeamWebApplication extends WebApplication { private static final LogProvider log = Logging.getLogProvider(SeamWebApplication.class); /** * When operating in tests, it is sometimes useful to leave the contexts extant * after a request, and destroy them upon the next request, so that models that use injections * can be queried post-request to determine their values. */ protected boolean destroyContextsLazily = false; public boolean isDestroyContextsLazily() { return destroyContextsLazily; } public void setDestroyContextsLazily(boolean destroyContextsLazily) { this.destroyContextsLazily = destroyContextsLazily; } /** * Custom session with invalidation override. We can't just let Wicket * invalidate the session as Seam might have to do some cleaning up to do. * We provide SeamWebSession as a separate class to allow for user subclasssing. */ @Override public Session newSession(Request request, Response response) { return new SeamWebSession(request); } /** * This is the key we will use to to store the conversation metadata in the wicket page. */ private static MetaDataKey CID = new MetaDataKey<String>() { }; /** * Seam's hooks into Wicket. Required for proper functioning */ @Override protected IRequestCycleProcessor newRequestCycleProcessor() { return new WebRequestCycleProcessor() { /** * If a long running conversation has been started, store its id into page metadata */ @Override public void respond(RequestCycle requestCycle) { super.respond(requestCycle); if (Manager.instance().isLongRunningConversation()) { Page page = RequestCycle.get().getResponsePage(); if (page != null) { page.setMetaData(CID, Manager.instance().getCurrentConversationId()); } } } }; } /** * Override to set up seam security, seam status messages, and add the SeamEnforceConversationListener */ @Override protected void init() { super.init(); inititializeSeamSecurity(); initializeSeamStatusMessages(); addPreComponentOnBeforeRenderListener(new SeamEnforceConversationListener()); } /** * Add Seam Security to the wicket app. * * This allows you to @Restrict your Wicket components. Override this method * to apply a different scheme * */ protected void inititializeSeamSecurity() { getSecuritySettings().setAuthorizationStrategy(new SeamAuthorizationStrategy(getLoginPage())); } /** * Add Seam status message transport support to your app. */ protected void initializeSeamStatusMessages() { addPreComponentOnBeforeRenderListener(new SeamStatusMessagesListener()); } protected abstract Class getLoginPage(); /* * Override to provide a seam-specific RequestCycle, which sets up seam contexts. */ @Override public RequestCycle newRequestCycle(final Request request, final Response response) { return new SeamWebRequestCycle(this, (WebRequest)request, (WebResponse)response); } /** * A WebRequestCycle that sets up seam requests. Essentially this * is similiar to the work of ContextualHttpServletRequest, but using the wicket API * */ protected static class SeamWebRequestCycle extends WebRequestCycle { public SeamWebRequestCycle(WebApplication application, WebRequest request, Response response) { super(application, request, response); } /** * Override this so that we can pull out the conversation id from page metadata * when a new page is chosen as the target */ @Override protected void onRequestTargetSet(IRequestTarget target) { super.onRequestTargetSet(target); Page page = null; if (target instanceof BookmarkablePageRequestTarget) { page = ((BookmarkablePageRequestTarget)target).getPage(); } else if (target instanceof IPageRequestTarget) { page = ((IPageRequestTarget)target).getPage(); } if (page != null) { String cid = (String) page.getMetaData(CID); if (cid != null) { Manager manager = Manager.instance(); if (manager.isLongRunningConversation()) { if (!cid.equals(manager.getCurrentConversationId())) { manager.switchConversation(cid); } } else { ConversationPropagation cp = ConversationPropagation.instance(); cp.setConversationId(cid); manager.restoreConversation(); } } else { Manager manager = Manager.instance(); if (manager.isLongRunningConversation()) { page.setMetaData(CID, Manager.instance().getCurrentConversationId()); } } } } /** * Override to destroy the old seam contexts if we are destroying lazily and they still exist, and * to set up the new seam contexts. */ @Override protected void onBeginRequest() { HttpServletRequest httpRequest = ((WebRequest)request).getHttpServletRequest(); if (Contexts.getEventContext() != null && ((SeamWebApplication)getApplication()).isDestroyContextsLazily() && ServletContexts.instance().getRequest() != httpRequest) { destroyContexts(); } if (Contexts.getEventContext() == null) { ServletLifecycle.beginRequest(httpRequest); ServletContexts.instance().setRequest(httpRequest); ConversationPropagation.instance().restoreConversationId( request.getParameterMap() ); Manager.instance().restoreConversation(); ServletLifecycle.resumeConversation(httpRequest); Manager.instance().handleConversationPropagation( request.getParameterMap() ); // Force creation of the session if (httpRequest.getSession(false) == null) { httpRequest.getSession(true); } } super.onBeginRequest(); Events.instance().raiseEvent("org.jboss.seam.wicket.beforeRequest"); } /** * Override to tear down seam contexts unless we are destroying lazily */ @Override protected void onEndRequest() { try { super.onEndRequest(); Events.instance().raiseEvent("org.jboss.seam.wicket.afterRequest"); } finally { if (Contexts.getEventContext() != null && !((SeamWebApplication)getApplication()).isDestroyContextsLazily()) { destroyContexts(); } } } /** * The actual work of destroying the seam contexts. */ private void destroyContexts() { try { HttpServletRequest httpRequest = ((WebRequest)request).getHttpServletRequest(); Manager.instance().endRequest( new ServletRequestSessionMap(httpRequest) ); ServletLifecycle.endRequest(httpRequest); } catch (Exception e) { /* Make sure we always clear out the thread locals */ Lifecycle.endRequest(); log.warn("ended request due to exception", e); throw new RuntimeException(e); } } } }