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