package org.jboss.seam.core;
import java.util.Map;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.intercept.BypassInterceptors;
import org.jboss.seam.contexts.Contexts;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.navigation.ConversationIdParameter;
import org.jboss.seam.navigation.Page;
import org.jboss.seam.navigation.Pages;
/**
* Overrideable component for extracting the conversation id
* from a request.
*
* @author Gavin King
* @author Pete Muir
* @author Shane Bryzak
*
*/
@Name("org.jboss.seam.core.conversationPropagation")
@Scope(ScopeType.EVENT)
@BypassInterceptors
@Install(precedence=Install.BUILT_IN)
public class ConversationPropagation
{
private static final LogProvider log = Logging.getLogProvider(ConversationPropagation.class);
public static final String CONVERSATION_NAME_PARAMETER = "conversationName";
public static final String CONVERSATION_PROPAGATION_PARAMETER = "conversationPropagation";
private String conversationName;
private String conversationId;
private String parentConversationId;
private boolean validateLongRunningConversation;
private PropagationType propagationType;
private String pageflow;
/**
* Initialize the request conversation id, taking
* into account conversation propagation style, and
* any conversation id passed as a request parameter
* or in the PAGE context.
*
* @param parameters the request parameters
*/
public void restoreConversationId(Map parameters)
{
restoreNaturalConversationId(parameters);
restoreSyntheticConversationId(parameters);
restorePageContextConversationId();
getPropagationFromRequestParameter(parameters);
handlePropagationType(parameters);
}
private void handlePropagationType(Map parameters)
{
if ( propagationType == PropagationType.NONE )
{
conversationId = null;
parentConversationId = null;
validateLongRunningConversation = false;
}
else if (( propagationType == PropagationType.END ) || ( propagationType == PropagationType.ENDROOT ))
{
validateLongRunningConversation = false;
}
}
private void restorePageContextConversationId()
{
if ( Contexts.isPageContextActive() && isMissing(conversationId) )
{
//checkPageContext is a workaround for a bug in MySQL server-side state saving
//if it is not passed as a request parameter,
//try to get it from the page context
org.jboss.seam.faces.FacesPage page = org.jboss.seam.faces.FacesPage.instance();
conversationId = page.getConversationId();
parentConversationId = null;
validateLongRunningConversation = page.isConversationLongRunning();
}
else
{
log.trace("Found conversation id in request parameter: " + conversationId);
}
}
private void restoreNaturalConversationId(Map parameters)
{
conversationName = getRequestParameterValue(parameters, CONVERSATION_NAME_PARAMETER);
if (conversationName != null && conversationName.contains(":"))
{
int idx = conversationName.indexOf(':');
conversationId = conversationName;
conversationName = conversationName.substring(0, idx);
return;
}
//First, try to get the conversation id from the request parameter defined for the page
String viewId = Pages.getCurrentViewId();
if ( viewId!=null )
{
Page page = Pages.instance().getPage(viewId);
if(conversationName != null)
{
ConversationIdParameter currentConversationIdParameter = Pages.instance().getConversationIdParameter(conversationName);
if(currentConversationIdParameter == null)
{
throw new IllegalStateException("The conversationName specified: " + conversationName + ", does not exist.");
}
// Try to restore the conversation from parameters (the user has specified the exact conversation to restore using f:param)
conversationId = currentConversationIdParameter.getRequestConversationId(parameters);
// NOTE: If conversationId is still null, don't try to resolve the EL here because we don't yet
// have a conversation and therefore things may blow up; resolve EL in getInitialConversationId()
}
else
{
conversationId = page.getConversationIdParameter().getRequestConversationId(parameters);
}
//TODO: how about the parent conversation id?
}
}
private void restoreSyntheticConversationId(Map parameters)
{
//Next, try to get the conversation id from the globally defined request parameters
Manager manager = Manager.instance(); //TODO: move the conversationIdParameter to this class!
if ( isMissing(conversationId) )
{
conversationId = getRequestParameterValue( parameters, manager.getConversationIdParameter() );
}
if ( isMissing(parentConversationId) )
{
parentConversationId = getRequestParameterValue( parameters, manager.getParentConversationIdParameter() );
}
}
private void getPropagationFromRequestParameter(Map parameters)
{
String value = getRequestParameterValue(parameters, CONVERSATION_PROPAGATION_PARAMETER);
if (value == null)
{
return;
}
if (value.startsWith("begin"))
{
propagationType = PropagationType.BEGIN;
if ( value.length() > 6 )
{
pageflow = value.substring(6);
}
}
else if (value.startsWith("join"))
{
propagationType = PropagationType.JOIN;
if ( value.length() > 5 )
{
pageflow = value.substring(5);
}
}
else if (value.startsWith("nested"))
{
propagationType = PropagationType.NESTED;
if ( value.length() > 7 )
{
pageflow = value.substring(7);
}
}
else
{
propagationType = PropagationType.valueOf(value.toUpperCase());
}
}
private static boolean isMissing(String storedConversationId)
{
return storedConversationId==null || "".equals(storedConversationId);
}
/**
* Retrieve a parameter value from the request
*
* @param parameters the request parameters
* @param parameterName the name of the parameter to retrieve
* @return the parameter value or null if not in the map
*/
public static String getRequestParameterValue(Map parameters, String parameterName)
{
Object object = parameters.get(parameterName);
if (object==null)
{
return null;
}
else
{
if ( object instanceof String )
{
//when it comes from JSF it is (usually?) a plain string
return (String) object;
}
else
{
//in a servlet it is a string array
String[] values = (String[]) object;
if (values.length!=1)
{
throw new IllegalArgumentException("expected exactly one value for " + parameterName + " request parameter");
}
return values[0];
}
}
}
/**
* @return the id of the current conversation
*/
public String getConversationId()
{
return conversationId;
}
public void setConversationId(String conversationId)
{
this.conversationId = conversationId;
}
/**
* @return the id of the parent of the current conversation
*/
public String getParentConversationId()
{
return parentConversationId;
}
public void setParentConversationId(String parentConversationId)
{
this.parentConversationId = parentConversationId;
}
/**
* Specifies that a redirect will occur if there is no
* conversation found on the server.
*/
public boolean isValidateLongRunningConversation()
{
return validateLongRunningConversation;
}
public void setValidateLongRunningConversation(boolean validateLongRunningConversation)
{
this.validateLongRunningConversation = validateLongRunningConversation;
}
public static ConversationPropagation instance()
{
if ( !Contexts.isEventContextActive() )
{
throw new IllegalStateException("No active event context");
}
return (ConversationPropagation) Component.getInstance(ConversationPropagation.class, ScopeType.EVENT);
}
/**
* @return the conversation propagation type specified in the request
*/
public PropagationType getPropagationType()
{
return propagationType;
}
public void setPropagationType(PropagationType propagationType)
{
this.propagationType = propagationType;
}
public String getPageflow()
{
return pageflow;
}
public String getConversationName()
{
return this.conversationName;
}
}