blob: eec1a62591c5e87d75ccc6a6d966ba21b3378898 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation;
import org.apache.myfaces.extensions.cdi.core.api.scope.conversation.config.WindowContextConfig;
import org.apache.myfaces.extensions.cdi.core.api.UnhandledException;
import org.apache.myfaces.extensions.cdi.jsf.api.listener.phase.AfterPhase;
import org.apache.myfaces.extensions.cdi.jsf.api.listener.phase.JsfPhaseId;
import org.apache.myfaces.extensions.cdi.jsf.api.listener.phase.BeforePhase;
import org.apache.myfaces.extensions.cdi.jsf.api.request.RequestTypeResolver;
import org.apache.myfaces.extensions.cdi.jsf.api.config.JsfModuleConfig;
import org.apache.myfaces.extensions.cdi.jsf.impl.listener.request.FacesMessageEntry;
import org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation.spi.EditableConversation;
import org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation.spi.EditableWindowContext;
import org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation.spi.EditableWindowContextManager;
import org.apache.myfaces.extensions.cdi.jsf.impl.scope.conversation.spi.WindowHandler;
import org.apache.myfaces.extensions.cdi.jsf.impl.util.ConversationUtils;
import static org.apache.myfaces.extensions.cdi.jsf.impl.util.ConversationUtils.*;
import static org.apache.myfaces.extensions.cdi.jsf.impl.util.ConversationUtils.resolveWindowContextId;
import org.apache.myfaces.extensions.cdi.message.api.Message;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Observes;
import javax.faces.context.FacesContext;
import javax.faces.event.PhaseEvent;
import java.io.IOException;
import java.util.List;
import static org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager
.AUTOMATED_ENTRY_POINT_PARAMETER_KEY;
import static org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager
.WINDOW_CONTEXT_ID_PARAMETER_KEY;
/**
* Observe some JSF phase events and set the appropriate
* states in the EditableWindowContextManager.
* We also use it to cleanup unused or obsolete
* WindowContexts if needed.
*/
@SuppressWarnings({"UnusedDeclaration"})
@ApplicationScoped
public class WindowContextManagerObserver
{
//don't use an event
public static final String INITIAL_REDIRCT_PERFORMED_KEY =
WindowContextManagerObserver.class.getName() + ":initial_redirect";
/**
* tries to restore the window-id and the window-context as early as possible
* @param phaseEvent the current jsf phase-event
* @param windowContextManager the current window-context-manager
* @param windowHandler current window-handler
* @param windowContextConfig the active window-context-config
*/
protected void tryToRestoreWindowContext(@Observes @BeforePhase(JsfPhaseId.RESTORE_VIEW) PhaseEvent phaseEvent,
EditableWindowContextManager windowContextManager,
WindowHandler windowHandler,
WindowContextConfig windowContextConfig)
{
ConversationUtils.tryToRestoreTheWindowIdEagerly(phaseEvent.getFacesContext(),
windowContextManager, windowHandler, windowContextConfig);
}
//don't change/optimize this observer!!!
protected void cleanup(@Observes @AfterPhase(JsfPhaseId.RESTORE_VIEW) PhaseEvent phaseEvent,
RequestTypeResolver requestTypeResolver,
EditableWindowContextManager windowContextManager,
WindowContextConfig windowContextConfig,
JsfModuleConfig jsfModuleConfig)
{
if (!requestTypeResolver.isPostRequest() && !requestTypeResolver.isPartialRequest())
{
FacesContext facesContext = phaseEvent.getFacesContext();
if(facesContext.getViewRoot() == null || facesContext.getViewRoot().getViewId() == null)
{
return;
}
//don't use the config of the current window context - it would trigger a touch
boolean continueRequest =
processGetRequest(facesContext, windowContextConfig, jsfModuleConfig);
if (!continueRequest)
{
return;
}
}
EditableWindowContext windowContext = (EditableWindowContext)windowContextManager.getCurrentWindowContext();
//don't refactor it to a lazy restore
storeCurrentViewIdAsNewViewId(phaseEvent.getFacesContext(), windowContext);
//don't refactor it - the messages have to be restored directly after restoring the window-context(-id)
tryToRestoreMessages(phaseEvent.getFacesContext(), windowContext, jsfModuleConfig);
//for performance reasons + cleanup at the beginning of the request (check timeout,...)
//+ we aren't allowed to cleanup in case of redirects
//we would transfer the restored view-id into the conversation
//don't ignore partial requests - in case of ajax-navigation we wouldn't check for expiration
if (!requestTypeResolver.isPostRequest())
{
return;
}
cleanupInactiveConversations(windowContext);
}
private void tryToRestoreMessages(FacesContext facesContext,
EditableWindowContext windowContext,
JsfModuleConfig jsfModuleConfig)
{
if(!jsfModuleConfig.isAlwaysKeepMessages())
{
return;
}
@SuppressWarnings({"unchecked"})
List<FacesMessageEntry> facesMessageEntryList =
windowContext.getAttribute(Message.class.getName(), List.class);
if(facesMessageEntryList != null)
{
for(FacesMessageEntry facesMessageEntry : facesMessageEntryList)
{
facesContext.addMessage(facesMessageEntry.getComponentId(), facesMessageEntry.getFacesMessage());
facesMessageEntryList.remove(facesMessageEntry);
}
facesMessageEntryList.clear();
}
}
/**
* an external app might call a page without url parameter.
* to support such an use-case it's possible to
* - deactivate the url parameter support (requires a special WindowHandler see e.g.
* ServerSideWindowHandler for jsf2
* - disable the initial redirect
* - use windowId=automatedEntryPoint as url parameter to force a new window context
* @param facesContext current facesContext
* @param windowContextConfig window config
* @param jsfModuleConfig jsf module config
* @return true if the current request should be continued
*/
private boolean processGetRequest(FacesContext facesContext,
WindowContextConfig windowContextConfig,
JsfModuleConfig jsfModuleConfig)
{
boolean urlParameterSupported = windowContextConfig.isUrlParameterSupported();
boolean useWindowIdForFirstPage = jsfModuleConfig.isInitialRedirectEnabled();
if(!urlParameterSupported)
{
useWindowIdForFirstPage = false;
}
if(useWindowIdForFirstPage)
{
String windowId = facesContext.getExternalContext()
.getRequestParameterMap().get(WINDOW_CONTEXT_ID_PARAMETER_KEY);
if(AUTOMATED_ENTRY_POINT_PARAMETER_KEY.equalsIgnoreCase(windowId))
{
return true;
}
WindowHandler windowHandler = ConversationUtils.getWindowHandler();
windowId = resolveWindowContextId(
windowHandler, urlParameterSupported, windowContextConfig.isUnknownWindowIdsAllowed());
if(windowId == null)
{
redirect(facesContext, windowHandler);
return false;
}
}
return true;
}
private void redirect(FacesContext facesContext, WindowHandler windowHandler)
{
if(facesContext.getResponseComplete())
{
return;
}
facesContext.getExternalContext().getRequestMap().put(INITIAL_REDIRCT_PERFORMED_KEY, Boolean.TRUE);
try
{
String targetURL = facesContext.getApplication()
.getViewHandler().getActionURL(facesContext, facesContext.getViewRoot().getViewId());
// add requst-parameters e.g. for f:viewParam handling
windowHandler.sendRedirect(FacesContext.getCurrentInstance().getExternalContext(), targetURL, true);
}
catch (IOException e)
{
throw new UnhandledException(e);
}
}
//don't cleanup all window contexts (it would cause a side-effect with the access-scope and multiple windows
private void cleanupInactiveConversations(EditableWindowContext windowContext)
{
for (EditableConversation conversation : windowContext.getConversations().values())
{
if (!conversation.isActive())
{
conversation.close();
}
}
windowContext.removeInactiveConversations();
}
}