/*
* 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.javaee.jsf.impl.scope.conversation;
import org.apache.myfaces.extensions.cdi.javaee.jsf.api.listener.phase.AfterPhase;
import org.apache.myfaces.extensions.cdi.javaee.jsf.api.listener.phase.PhaseId;
import org.apache.myfaces.extensions.cdi.javaee.jsf.api.request.RequestTypeResolver;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.RequestCache;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.ConversationUtils;
import static org.apache.myfaces.extensions.cdi.javaee.jsf.impl.util.ConversationUtils.*;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.WindowHandler;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.JsfAwareWindowContextConfig;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableWindowContext;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableWindowContextManager;
import static org.apache.myfaces.extensions.cdi.core.impl.scope.conversation.spi.WindowContextManager
.WINDOW_CONTEXT_ID_PARAMETER_KEY;
import org.apache.myfaces.extensions.cdi.javaee.jsf.impl.scope.conversation.spi.EditableConversation;
import org.apache.myfaces.extensions.cdi.core.api.resolver.ConfigResolver;
import javax.enterprise.event.Observes;
import javax.faces.event.PhaseEvent;
import javax.faces.context.FacesContext;
import java.io.IOException;
/**
* @author Gerhard Petracek
*/
@SuppressWarnings({"UnusedDeclaration"})
final class WindowContextManagerObserver
{
//don't change/optimize this observer!!!
protected void cleanup(@Observes @AfterPhase(PhaseId.RESTORE_VIEW) PhaseEvent phaseEvent,
RequestTypeResolver requestTypeResolver,
EditableWindowContextManager windowContextManager,
ConfigResolver configResolver)
{
if (!requestTypeResolver.isPostRequest() && !requestTypeResolver.isPartialRequest())
{
//don't use the config of the current window context - it would trigger a touch
boolean continueRequest = processGetRequest(
phaseEvent.getFacesContext(), configResolver.resolve(JsfAwareWindowContextConfig.class));
if (!continueRequest)
{
return;
}
}
EditableWindowContext windowContext = (EditableWindowContext)windowContextManager.getCurrentWindowContext();
//don't refactor it to a lazy restore
storeCurrentViewIdAsNewViewId(phaseEvent.getFacesContext(), windowContext);
//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);
}
protected void cleanupAndRecordCurrentViewAsOldViewId(
@Observes @AfterPhase(PhaseId.RENDER_RESPONSE) PhaseEvent phaseEvent,
EditableWindowContextManager windowContextManager)
{
storeCurrentViewIdAsOldViewId(phaseEvent.getFacesContext());
cleanupInactiveWindowContexts(windowContextManager);
RequestCache.resetCache();
}
/**
* 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 config window config
* @return true if the current request should be continued
*/
private boolean processGetRequest(FacesContext facesContext, JsfAwareWindowContextConfig config)
{
boolean urlParameterSupported = config.isUrlParameterSupported();
boolean useWindowIdForFirstPage = !config.isInitialRedirectDisable();
if(!urlParameterSupported)
{
useWindowIdForFirstPage = false;
}
if(useWindowIdForFirstPage)
{
String windowId = facesContext.getExternalContext()
.getRequestParameterMap().get(WINDOW_CONTEXT_ID_PARAMETER_KEY);
if("automatedEntryPoint".equalsIgnoreCase(windowId))
{
return true;
}
WindowHandler windowHandler = ConversationUtils.getWindowHandler();
windowId = resolveWindowContextId(windowHandler, urlParameterSupported, config.isUnknownWindowIdsAllowed());
if(windowId == null)
{
redirect(facesContext, windowHandler);
return false;
}
}
return true;
}
private void redirect(FacesContext facesContext, WindowHandler windowHandler)
{
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 RuntimeException(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.end();
}
}
windowContext.removeInactiveConversations();
}
}