package org.jboss.seam.navigation;
import static org.jboss.seam.annotations.Install.BUILT_IN;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.faces.application.FacesMessage;
import javax.faces.application.ViewHandler;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.convert.ConverterException;
import javax.faces.model.DataModel;
import javax.faces.validator.ValidatorException;
import javax.servlet.http.HttpServletRequest;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.FlushModeType;
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.core.Events;
import org.jboss.seam.core.Expressions;
import org.jboss.seam.core.Init;
import org.jboss.seam.core.Manager;
import org.jboss.seam.core.ResourceLoader;
import org.jboss.seam.core.Expressions.MethodExpression;
import org.jboss.seam.core.Expressions.ValueExpression;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.faces.Validation;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
import org.jboss.seam.pageflow.Pageflow;
import org.jboss.seam.security.Identity;
import org.jboss.seam.security.NotLoggedInException;
import org.jboss.seam.util.Strings;
import org.jboss.seam.util.XML;
import org.jboss.seam.web.Parameters;
/**
* Holds metadata for pages defined in pages.xml, including
* page actions and page descriptions.
*
* @author Gavin King
*/
@Scope(ScopeType.APPLICATION)
@BypassInterceptors
@Name("org.jboss.seam.navigation.pages")
@Install(precedence=BUILT_IN, classDependencies="javax.faces.context.FacesContext")
public class Pages
{
private static final LogProvider log = Logging.getLogProvider(Pages.class);
private Map<String, Page> pagesByViewId = Collections.synchronizedMap( new HashMap<String, Page>() );
private Map<String, List<Page>> pageStacksByViewId = Collections.synchronizedMap( new HashMap<String, List<Page>>() );
private String noConversationViewId;
private String loginViewId;
private Map<String, ConversationIdParameter> conversations = Collections.synchronizedMap( new HashMap<String, ConversationIdParameter>() );
private Integer httpPort;
private Integer httpsPort;
private String[] resources = { "/WEB-INF/pages.xml" };
private SortedSet<String> wildcardViewIds = new TreeSet<String>(
new Comparator<String>()
{
public int compare(String x, String y)
{
if ( x.length()<y.length() ) return -1;
if ( x.length()> y.length() ) return 1;
return x.compareTo(y);
}
}
);
@Create
public void initialize()
{
for (String resource: resources)
{
InputStream stream = ResourceLoader.instance().getResourceAsStream(resource);
if (stream==null)
{
log.info("no pages.xml file found: " + resource);
}
else
{
log.debug("reading pages.xml file: " + resource);
parse(stream);
}
}
}
/**
* Run any navigation rule defined in pages.xml
*
* @param actionExpression the action method binding expression
* @param actionOutcomeValue the outcome of the action method
* @return true if a navigation rule was found
*/
public boolean navigate(FacesContext context, String actionExpression, String actionOutcomeValue)
{
String viewId = getViewId(context);
if (viewId!=null)
{
List<Page> stack = getPageStack(viewId);
for (int i=stack.size()-1; i>=0; i--)
{
Page page = stack.get(i);
Navigation navigation = page.getNavigations().get(actionExpression);
if (navigation==null)
{
navigation = page.getDefaultNavigation();
}
if ( navigation!=null && navigation.navigate(context, actionOutcomeValue) ) return true;
}
}
return false;
}
/**
* Get the Page object for the given view id.
*
* @param viewId a JSF view id
*/
public Page getPage(String viewId)
{
if (viewId==null)
{
//for tests
return new Page(viewId);
}
else
{
Page result = getCachedPage(viewId);
if (result==null)
{
return createPage(viewId);
}
else
{
return result;
}
}
}
/**
* Create a new Page object for a JSF view id,
* by searching for a viewId.page.xml file.
*/
private Page createPage(String viewId)
{
String resourceName = replaceExtension(viewId, ".page.xml");
InputStream stream = resourceName==null ?
null : ResourceLoader.instance().getResourceAsStream( resourceName.substring(1) );
if ( stream==null )
{
Page result = new Page(viewId);
pagesByViewId.put(viewId, result);
return result;
}
else
{
parse(stream, viewId);
return getCachedPage(viewId);
}
}
private Page getCachedPage(String viewId)
{
Page result = pagesByViewId.get(viewId);
if (result==null)
{
//workaround for what I believe is a bug in the JSF RI
viewId = replaceExtension( viewId, getSuffix() );
if (viewId!=null)
{
result = pagesByViewId.get(viewId);
}
}
return result;
}
private static String replaceExtension(String viewId, String suffix)
{
int loc = viewId.lastIndexOf('.');
return loc<0 ? null : viewId.substring(0, loc) + suffix;
}
/**
* Get the stack of Page objects, from least specific to
* most specific, that match the given view id.
*
* @param viewId a JSF view id
*/
protected List<Page> getPageStack(String viewId)
{
List<Page> stack = pageStacksByViewId.get(viewId);
if (stack==null)
{
stack = createPageStack(viewId);
pageStacksByViewId.put(viewId, stack);
}
return stack;
}
/**
* Create the stack of pages that match a JSF view id
*/
private List<Page> createPageStack(String viewId)
{
List<Page> stack = new ArrayList<Page>(1);
if ( viewId!=null && !isDebugPage(viewId) )
{
for (String wildcard: wildcardViewIds)
{
if ( viewId.startsWith( wildcard.substring(0, wildcard.length()-1) ) )
{
stack.add( getPage(wildcard) );
}
}
}
Page page = getPage(viewId);
if (page!=null) stack.add(page);
return stack;
}
/**
* Call page actions, check permissions and validate the existence
* of a conversation for pages which require a long-running
* conversation, starting with the most general view id, ending at
* the most specific. Also perform redirection to the required
* scheme if necessary.
*/
public boolean preRender(FacesContext facesContext)
{
String viewId = getViewId(facesContext);
//redirect to HTTPS if necessary
String requestScheme = getRequestScheme(facesContext);
if ( requestScheme!=null )
{
String scheme = getScheme(viewId);
if ( scheme!=null && !requestScheme.equals(scheme) )
{
Manager.instance().redirect(viewId);
return false;
}
}
//apply the datamodelselection passed by s:link or s:button
//before running any actions
selectDataModelRow(facesContext);
//redirect if necessary
List<Page> pageStack = getPageStack(viewId);
for ( Page page: pageStack )
{
if ( isNoConversationRedirectRequired(page) )
{
redirectToNoConversationView();
return false;
}
else if ( isLoginRedirectRequired(viewId, page) )
{
redirectToLoginView();
return false;
}
}
boolean result = callAction(facesContext);
//If responseComplete then we're probably doing a redirect so don't call the page actions now.
if (!facesContext.getResponseComplete()) {
String newViewId = getViewId(facesContext);
for ( Page page: getPageStack(newViewId) ) {
if ( isNoConversationRedirectRequired(page) ) {
redirectToNoConversationView();
return false;
} else if ( isLoginRedirectRequired(newViewId, page) ) {
redirectToLoginView();
return false;
}
}
//run the page actions, check permissions,
//handle conversation begin/end
for ( Page page: getPageStack(newViewId) ) {
result = page.preRender(facesContext) || result;
}
}
return result;
}
/**
* Look for a DataModel row selection in the request parameters,
* and apply it to the DataModel.
*/
private void selectDataModelRow(FacesContext facesContext)
{
String dataModelSelection = facesContext.getExternalContext()
.getRequestParameterMap().get("dataModelSelection");
if (dataModelSelection!=null)
{
int colonLoc = dataModelSelection.indexOf(':');
int bracketLoc = dataModelSelection.indexOf('[');
if (colonLoc>0 && bracketLoc>colonLoc)
{
String var = dataModelSelection.substring(0, colonLoc);
String name = dataModelSelection.substring(colonLoc+1, bracketLoc);
int index = Integer.parseInt( dataModelSelection.substring( bracketLoc+1, dataModelSelection.length()-1 ) );
Object value = Component.getInstance(name, true);
if (value!=null)
{
DataModel dataModel = (DataModel) value;
if ( index<dataModel.getRowCount() )
{
dataModel.setRowIndex(index);
Contexts.getEventContext().set( var, dataModel.getRowData() );
}
else
{
log.warn("DataModel row was unavailable");
Contexts.getEventContext().remove(var);
}
}
}
}
}
/**
* Check permissions and validate the existence of a conversation
* for pages which require a long-running conversation, starting
* with the most general view id, ending at the most specific.
* Finally apply page parameters to the model.
*/
public void postRestore(FacesContext facesContext)
{
//first store the page parameters into the viewroot, so
//that if a login redirect occurs, or if a failure
//occurs while validating of applying to the model, we can
//still make Redirect.captureCurrentView() work.
storeRequestStringValuesInPageContext(facesContext);
//check if we need to redirect
String viewId = getViewId(facesContext);
for ( Page page: getPageStack(viewId) )
{
if ( isLoginRedirectRequired(viewId, page) )
{
redirectToLoginView();
return;
}
else if ( isNoConversationRedirectRequired(page) )
{
redirectToNoConversationView();
return;
}
else
{
//if we are about to proceed to the action
//phase, check the permission.
if ( !facesContext.getRenderResponse() )
{
page.postRestore(facesContext);
}
}
}
//now validate the values we just stored in
//the view root, after the redirect checking
if ( convertAndValidateStringValuesInPageContext(facesContext) )
{
Validation.instance().fail();
//and don't apply them to the model
}
else
{
//finally apply page parameters to the model
//(after checking permissions)
applyConvertedValidatedValuesToModel(facesContext);
}
}
/**
* Check if a login redirect is required for the current FacesContext
*
* @param facesContext The faces context containing the view ID
* @return boolean Returns true if a login redirect is required
*/
public boolean isLoginRedirectRequired(FacesContext facesContext)
{
String viewId = getViewId(facesContext);
for ( Page page: getPageStack(viewId) )
{
if ( isLoginRedirectRequired(viewId, page) ) return true;
}
return false;
}
private boolean isNoConversationRedirectRequired(Page page)
{
return page.isConversationRequired() &&
!Manager.instance().isLongRunningOrNestedConversation();
}
private boolean isLoginRedirectRequired(String viewId, Page page)
{
return page.isLoginRequired() &&
!viewId.equals( getLoginViewId() ) &&
!Identity.instance().isLoggedIn();
}
public static String getRequestScheme(FacesContext facesContext)
{
String requestUrl = getRequestUrl(facesContext);
if (requestUrl==null)
{
return null;
}
else
{
int idx = requestUrl.indexOf(':');
return idx<0 ? null : requestUrl.substring(0, idx);
}
}
public String encodeScheme(String viewId, FacesContext context, String url)
{
String scheme = getScheme(viewId);
if (scheme != null)
{
String requestUrl = getRequestUrl(context);
if (requestUrl!=null)
{
try
{
URL serverUrl = new URL(requestUrl);
StringBuilder sb = new StringBuilder();
sb.append(scheme);
sb.append("://");
sb.append(serverUrl.getHost());
if ("http".equals(scheme) && httpPort != null)
{
sb.append(":");
sb.append(httpPort);
}
else if ("https".equals(scheme) && httpsPort != null)
{
sb.append(":");
sb.append(httpsPort);
}
else if (serverUrl.getPort() != -1)
{
sb.append(":");
sb.append(serverUrl.getPort());
}
if (!url.startsWith("/")) sb.append("/");
sb.append(url);
url = sb.toString();
}
catch (MalformedURLException ex)
{
throw new RuntimeException(ex);
}
}
}
return url;
}
private static String getRequestUrl(FacesContext facesContext)
{
Object request = facesContext.getExternalContext().getRequest();
if (request instanceof HttpServletRequest)
{
return ( (HttpServletRequest) request).getRequestURL().toString();
}
else
{
return null;
}
}
public void redirectToLoginView()
{
notLoggedIn();
String loginViewId = getLoginViewId();
if (loginViewId==null)
{
throw new NotLoggedInException();
}
else
{
Manager.instance().redirect(loginViewId);
}
}
public void redirectToNoConversationView()
{
noConversation();
//stuff from jPDL takes precedence
org.jboss.seam.faces.FacesPage facesPage = org.jboss.seam.faces.FacesPage.instance();
String pageflowName = facesPage.getPageflowName();
String pageflowNodeName = facesPage.getPageflowNodeName();
String noConversationViewId = null;
if (pageflowName==null || pageflowNodeName==null)
{
String viewId = Pages.getCurrentViewId();
noConversationViewId = getNoConversationViewId(viewId);
}
else
{
noConversationViewId = Pageflow.instance().getNoConversationViewId(pageflowName, pageflowNodeName);
}
if (noConversationViewId!=null)
{
Manager.instance().redirect(noConversationViewId);
}
}
public String getScheme(String viewId)
{
List<Page> stack = getPageStack(viewId);
for ( int i = stack.size() - 1; i >= 0; i-- )
{
Page page = stack.get(i);
if (page.getScheme() != null) return page.getScheme();
}
return null;
}
protected void noConversation()
{
Events.instance().raiseEvent("org.jboss.seam.noConversation");
FacesMessages.instance().addFromResourceBundleOrDefault(
FacesMessage.SEVERITY_WARN,
"org.jboss.seam.NoConversation",
"The conversation ended, timed out or was processing another request"
);
}
protected void notLoggedIn()
{
Events.instance().raiseEvent("org.jboss.seam.notLoggedIn");
FacesMessages.instance().addFromResourceBundleOrDefault(
FacesMessage.SEVERITY_WARN,
"org.jboss.seam.NotLoggedIn",
"Please log in first"
);
}
public static String toString(Object returnValue)
{
return returnValue == null ? null : returnValue.toString();
}
/**
* Call the JSF navigation handler
*/
public static void handleOutcome(FacesContext facesContext, String outcome, String fromAction)
{
facesContext.getApplication().getNavigationHandler()
.handleNavigation(facesContext, fromAction, outcome);
//after every time that the view may have changed,
//we need to flush the page context, since the
//attribute map is being discarder
Contexts.getPageContext().flush();
}
public static Pages instance()
{
if ( !Contexts.isApplicationContextActive() )
{
throw new IllegalStateException("No active application context");
}
return (Pages) Component.getInstance(Pages.class, ScopeType.APPLICATION);
}
/**
* Call the action requested by s:link or s:button.
*/
private static boolean callAction(FacesContext facesContext)
{
//TODO: refactor with Pages.instance().callAction()!!
boolean result = false;
String outcome = facesContext.getExternalContext()
.getRequestParameterMap().get("actionOutcome");
String fromAction = outcome;
if (outcome==null)
{
String actionId = facesContext.getExternalContext()
.getRequestParameterMap().get("actionMethod");
if (actionId!=null)
{
if ( !SafeActions.instance().isActionSafe(actionId) ) return result;
String expression = SafeActions.toAction(actionId);
result = true;
MethodExpression actionExpression = Expressions.instance().createMethodExpression(expression);
outcome = toString( actionExpression.invoke() );
fromAction = expression;
handleOutcome(facesContext, outcome, fromAction);
}
}
else
{
handleOutcome(facesContext, outcome, fromAction);
}
return result;
}
/**
* Build a list of page-scoped resource bundles, from most
* specific view id, to most general.
*/
public List<ResourceBundle> getResourceBundles(String viewId)
{
List<ResourceBundle> result = new ArrayList<ResourceBundle>(1);
List<Page> stack = getPageStack(viewId);
for (int i=stack.size()-1; i>=0; i--)
{
Page page = stack.get(i);
ResourceBundle bundle = page.getResourceBundle();
if ( bundle!=null ) result.add(bundle);
}
return result;
}
/**
* Get the values of any page parameters by evaluating the value bindings
* against the model and converting to String.
*
* @param viewId the JSF view id
* @param overridden excluded parameters
* @return a map of page parameter name to String value
*/
public Map<String, Object> getStringValuesFromModel(FacesContext facesContext, String viewId, Set<String> overridden)
{
Map<String, Object> parameters = new HashMap<String, Object>();
for ( Page page: getPageStack(viewId) )
{
for ( Param pageParameter: page.getParameters() )
{
if ( !overridden.contains( pageParameter.getName() ) )
{
String value;
if ( pageParameter.getValueExpression()==null )
{
value = (String) Contexts.getPageContext().get( pageParameter.getName() );
}
else
{
value = pageParameter.getStringValueFromModel(facesContext);
}
if (value!=null)
{
parameters.put( pageParameter.getName(), value );
}
}
}
}
return parameters;
}
private void storeRequestStringValuesInPageContext(FacesContext facesContext)
{
Parameters parameters = Parameters.instance();
if (parameters!=null) //for unit tests
{
Map<String, String[]> requestParameters = parameters.getRequestParameters();
for ( Page page: getPageStack( getViewId(facesContext) ) )
{
for ( Param pageParameter: page.getParameters() )
{
String value = pageParameter.getStringValueFromRequest(facesContext, requestParameters);
if (value==null)
{
//this should not be necessary, were it not for a MyFaces bug
if ( facesContext.getRenderResponse() ) //ie. for a non-faces request
{
Contexts.getPageContext().remove( pageParameter.getName() );
}
}
else
{
Contexts.getPageContext().set( pageParameter.getName(), value );
}
}
}
}
}
/**
* Convert and validate page parameters passed as view root attributes or request parameters
*/
private boolean convertAndValidateStringValuesInPageContext(FacesContext facesContext)
{
boolean validationFailed = false;
for ( Page page: getPageStack( getViewId(facesContext) ) )
{
for ( Param pageParameter: page.getParameters() )
{
try
{
String value = (String) Contexts.getPageContext().get( pageParameter.getName() );
if (value!=null)
{
pageParameter.validateConvertedValue(facesContext, value);
Object convertedValue = pageParameter.convertValueFromString(facesContext, value);
Contexts.getEventContext().set( pageParameter.getName(), convertedValue );
}
}
catch (ValidatorException ve)
{
facesContext.addMessage( null, ve.getFacesMessage() );
validationFailed = true;
}
catch (ConverterException ce)
{
facesContext.addMessage( null, ce.getFacesMessage() );
validationFailed = true;
}
}
}
return validationFailed;
}
/**
* Apply page parameters passed as view root attributes or request parameters to the model
*/
private void applyConvertedValidatedValuesToModel(FacesContext facesContext)
{
String viewId = getViewId(facesContext);
for ( Page page: getPageStack(viewId) )
{
for ( Param pageParameter: page.getParameters() )
{
ValueExpression valueExpression = pageParameter.getValueExpression();
if (valueExpression!=null)
{
Object object = Contexts.getEventContext().get( pageParameter.getName() );
if (object!=null)
{
valueExpression.setValue(object);
}
}
}
}
}
/**
* Get the page parameter values that were passed in the original request from
* the PAGE context
*/
public Map<String, Object> getStringValuesFromPageContext(FacesContext facesContext)
{
Map<String, Object> parameters = new HashMap<String, Object>();
String viewId = getViewId(facesContext);
for ( Page page: getPageStack(viewId) )
{
for ( Param pageParameter: page.getParameters() )
{
Object object = Contexts.getPageContext().get( pageParameter.getName() );
if (object!=null)
{
parameters.put( pageParameter.getName(), object );
}
}
}
return parameters;
}
/**
* Update the page parameter values stored in the PAGE context with the current
* values of the mapped attributes of the model
*/
public void updateStringValuesInPageContextUsingModel(FacesContext facesContext)
{
for ( Page page: getPageStack( getViewId(facesContext) ) )
{
for ( Param pageParameter: page.getParameters() )
{
if ( pageParameter.getValueExpression()!=null )
{
String value = pageParameter.getStringValueFromModel(facesContext);
if (value==null)
{
Contexts.getPageContext().remove( pageParameter.getName() );
}
else
{
Contexts.getPageContext().set( pageParameter.getName(), value );
}
}
}
}
}
/**
* Encode page parameters into a URL
*
* @param url the base URL
* @param viewId the JSF view id of the page
* @return the URL with parameters appended
*/
public String encodePageParameters(FacesContext facesContext, String url, String viewId)
{
return encodePageParameters(facesContext, url, viewId, Collections.EMPTY_SET);
}
/**
* Encode page parameters into a URL
*
* @param url the base URL
* @param viewId the JSF view id of the page
* @param overridden excluded parameters
* @return the URL with parameters appended
*/
public String encodePageParameters(FacesContext facesContext, String url, String viewId, Set<String> overridden)
{
Map<String, Object> parameters = getStringValuesFromModel(facesContext, viewId, overridden);
return Manager.instance().encodeParameters(url, parameters);
}
/**
* Search for a defined no-conversation-view-id, beginning with
* the most specific view id, then wildcarded view ids, and
* finally the global setting
*/
public String getNoConversationViewId(String viewId)
{
List<Page> stack = getPageStack(viewId);
for (int i=stack.size()-1; i>=0; i--)
{
Page page = stack.get(i);
String noConversationViewId = page.getNoConversationViewId();
if (noConversationViewId!=null)
{
return noConversationViewId;
}
}
return this.noConversationViewId;
}
/**
* Search for a defined conversation timeout, beginning with
* the most specific view id, then wildcarded view ids, and
* finally the global setting from Manager
*/
public Integer getTimeout(String viewId)
{
List<Page> stack = getPageStack(viewId);
for (int i=stack.size()-1; i>=0; i--)
{
Page page = stack.get(i);
Integer timeout = page.getTimeout();
if (timeout!=null)
{
return timeout;
}
}
return Manager.instance().getConversationTimeout();
}
public static String getSuffix()
{
String defaultSuffix = FacesContext.getCurrentInstance().getExternalContext()
.getInitParameter(ViewHandler.DEFAULT_SUFFIX_PARAM_NAME);
return defaultSuffix == null ? ViewHandler.DEFAULT_SUFFIX : defaultSuffix;
}
/**
* Parse a pages.xml file
*/
private void parse(InputStream stream)
{
Element root = getDocumentRoot(stream);
if (noConversationViewId==null) //let the setting in components.xml override the pages.xml
{
noConversationViewId = root.attributeValue("no-conversation-view-id");
}
if (loginViewId==null) //let the setting in components.xml override the pages.xml
{
loginViewId = root.attributeValue("login-view-id");
}
List<Element> elements = root.elements("conversation");
for (Element conversation : elements)
{
parseConversation(conversation, conversation.attributeValue("name"));
}
elements = root.elements("page");
for (Element page: elements)
{
parse( page, page.attributeValue("view-id") );
}
}
/**
* Parse a viewId.page.xml file
*/
private void parse(InputStream stream, String viewId)
{
parse( getDocumentRoot(stream), viewId );
}
/**
* Get the root element of the document
*/
private static Element getDocumentRoot(InputStream stream)
{
try
{
return XML.getRootElement(stream);
}
catch (DocumentException de)
{
throw new RuntimeException(de);
}
}
private void parseConversation(Element element, String name)
{
if (name == null)
{
throw new IllegalStateException("Must specify name for <conversation/> declaration");
}
if (conversations.containsKey(name))
{
throw new IllegalStateException("<conversation/> declaration already exists for [" + name + "]");
}
ELConversationIdParameter param = new ELConversationIdParameter(name,
element.attributeValue("parameter-name"),
element.attributeValue("parameter-value"));
conversations.put(name, param);
}
/**
* Parse a page element and add a Page to the map
*/
private void parse(Element element, String viewId)
{
if (viewId==null)
{
throw new IllegalStateException("Must specify view-id for <page/> declaration");
}
if ( viewId.endsWith("*") )
{
wildcardViewIds.add(viewId);
}
Page page = new Page(viewId);
pagesByViewId.put(viewId, page);
parsePage(page, element, viewId);
parseConversationControl( element, page.getConversationControl() );
parseTaskControl(element, page.getTaskControl());
parseProcessControl(element, page.getProcessControl());
List<Element> children = element.elements("param");
for (Element param: children)
{
page.getParameters().add( parseParam(param) );
}
List<Element> moreChildren = element.elements("navigation");
for (Element fromAction: moreChildren)
{
parseActionNavigation(page, fromAction);
}
Element restrict = element.element("restrict");
if (restrict != null)
{
page.setRestricted(true);
String expr = restrict.getTextTrim();
if ( !Strings.isEmpty(expr) ) page.setRestriction(expr);
}
}
public ConversationIdParameter getConversationIdParameter(String conversationName)
{
return conversations.get(conversationName);
}
/**
* Parse the attributes of page
*/
private Page parsePage(Page page, Element element, String viewId)
{
page.setSwitchEnabled( !"disabled".equals( element.attributeValue("switch") ) );
Element optionalElement = element.element("description");
String description = optionalElement==null ?
element.getTextTrim() : optionalElement.getTextTrim();
if (description!=null && description.length()>0)
{
page.setDescription(description);
}
String timeoutString = element.attributeValue("timeout");
if (timeoutString!=null)
{
page.setTimeout(Integer.parseInt(timeoutString));
}
page.setNoConversationViewId( element.attributeValue("no-conversation-view-id") );
page.setConversationRequired( "true".equals( element.attributeValue("conversation-required") ) );
page.setLoginRequired( "true".equals( element.attributeValue("login-required") ) );
page.setScheme( element.attributeValue("scheme") );
ConversationIdParameter param = conversations.get( element.attributeValue("conversation") );
if (param != null) page.setConversationIdParameter(param);
Element eventElement = element.element("raise-event");
if (eventElement!=null)
{
page.setEventType( eventElement.attributeValue("type") );
}
Action action = parseAction(element, "action");
if (action!=null) page.getActions().add(action);
List<Element> childElements = element.elements("action");
for (Element childElement: childElements)
{
page.getActions().add( parseAction(childElement, "execute") );
}
String bundle = element.attributeValue("bundle");
if (bundle!=null)
{
page.setResourceBundleName(bundle);
}
List<Element> moreChildElements = element.elements("in");
for (Element child: moreChildElements)
{
Input input = new Input();
input.setName( child.attributeValue("name") );
input.setValue( Expressions.instance().createValueExpression( child.attributeValue("value") ) );
String scopeName = child.attributeValue("scope");
if (scopeName!=null)
{
input.setScope( ScopeType.valueOf( scopeName.toUpperCase() ) );
}
page.getInputs().add(input);
}
return page;
}
private static Action parseAction(Element element, String actionAtt)
{
Action action = new Action();
String methodExpression = element.attributeValue(actionAtt);
if (methodExpression==null) return null;
if ( methodExpression.startsWith("#{") )
{
action.setMethodExpression( Expressions.instance().createMethodExpression(methodExpression) );
}
else
{
action.setOutcome(methodExpression);
}
String expression = element.attributeValue("if");
if (expression!=null)
{
action.setValueExpression( Expressions.instance().createValueExpression(expression) );
}
return action;
}
/**
* Parse end-conversation (and end-task) and begin-conversation (start-task and begin-task)
*
*/
private static void parseConversationControl(Element element, ConversationControl control)
{
Element endConversation = element.element("end-conversation");
endConversation = endConversation == null ? element.element("end-task") : endConversation;
if ( endConversation!=null )
{
control.setEndConversation(true);
control.setEndConversationBeforeRedirect( "true".equals( endConversation.attributeValue("before-redirect") ) );
String expression = endConversation.attributeValue("if");
if (expression!=null)
{
control.setEndConversationCondition( Expressions.instance().createValueExpression(expression, Boolean.class) );
}
}
Element beginConversation = element.element("begin-conversation");
beginConversation = beginConversation == null ? element.element("begin-task") : beginConversation;
beginConversation = beginConversation == null ? element.element("start-task") : beginConversation;
if ( beginConversation!=null )
{
control.setBeginConversation(true);
control.setJoin( "true".equals( beginConversation.attributeValue("join") ) );
control.setNested( "true".equals( beginConversation.attributeValue("nested") ) );
control.setPageflow( beginConversation.attributeValue("pageflow") );
String flushMode = beginConversation.attributeValue("flush-mode");
if (flushMode!=null)
{
control.setFlushMode( FlushModeType.valueOf( flushMode.toUpperCase() ) );
}
String expression = beginConversation.attributeValue("if");
if (expression!=null)
{
control.setBeginConversationCondition( Expressions.instance().createValueExpression(expression, Boolean.class) );
}
}
if ( control.isBeginConversation() && control.isEndConversation() )
{
throw new IllegalStateException("cannot use both <begin-conversation/> and <end-conversation/>");
}
}
/**
* Parse begin-task, start-task and end-task
*/
private static void parseTaskControl(Element element, TaskControl control)
{
Element endTask = element.element("end-task");
if ( endTask!=null )
{
control.setEndTask(true);
String transition = endTask.attributeValue("transition");
if (transition != null)
{
control.setTransition( Expressions.instance().createValueExpression(transition, String.class) );
}
}
Element beginTask = element.element("begin-task");
if ( beginTask!=null )
{
control.setBeginTask(true);
String taskId = beginTask.attributeValue("task-id");
if (taskId==null)
{
taskId = "#{param.taskId}";
}
control.setTaskId( Expressions.instance().createValueExpression(taskId, Long.class) );
}
Element startTask = element.element("start-task");
if ( startTask!=null )
{
control.setStartTask(true);
String taskId = startTask.attributeValue("task-id");
if (taskId==null)
{
taskId = "#{param.taskId}";
}
control.setTaskId( Expressions.instance().createValueExpression(taskId, Long.class) );
}
if ( control.isBeginTask() && control.isEndTask() )
{
throw new IllegalStateException("cannot use both <begin-task/> and <end-task/>");
}
else if ( control.isBeginTask() && control.isStartTask() )
{
throw new IllegalStateException("cannot use both <start-task/> and <begin-task/>");
}
else if ( control.isStartTask() && control.isEndTask() )
{
throw new IllegalStateException("cannot use both <start-task/> and <end-task/>");
}
}
/**
* Parse create-process and end-process
*/
private static void parseProcessControl(Element element, ProcessControl control)
{
Element createProcess = element.element("create-process");
if ( createProcess!=null )
{
control.setCreateProcess(true);
control.setDefinition( createProcess.attributeValue("definition") );
}
Element resumeProcess = element.element("resume-process");
if ( resumeProcess!=null )
{
control.setResumeProcess(true);
String processId = resumeProcess.attributeValue("process-id");
if (processId==null)
{
processId = "#{param.processId}";
}
control.setProcessId( Expressions.instance().createValueExpression(processId, Long.class) );
}
if ( control.isCreateProcess() && control.isResumeProcess() )
{
throw new IllegalStateException("cannot use both <create-process/> and <resume-process/>");
}
}
private static void parseEvent(Element element, Rule rule)
{
Element eventElement = element.element("raise-event");
if ( eventElement!=null )
{
rule.setEventType( eventElement.attributeValue("type") );
}
}
/**
* Parse navigation
*/
private static void parseActionNavigation(Page entry, Element element)
{
Navigation navigation = new Navigation();
String outcomeExpression = element.attributeValue("evaluate");
if (outcomeExpression!=null)
{
navigation.setOutcome( Expressions.instance().createValueExpression(outcomeExpression) );
}
List<Element> cases = element.elements("rule");
for (Element childElement: cases)
{
navigation.getRules().add( parseRule(childElement) );
}
Rule rule = new Rule();
parseEvent(element, rule);
parseNavigationHandler(element, rule);
parseConversationControl( element, rule.getConversationControl() );
parseTaskControl(element, rule.getTaskControl());
parseProcessControl(element, rule.getProcessControl());
navigation.setRule(rule);
String expression = element.attributeValue("from-action");
if (expression==null)
{
if (entry.getDefaultNavigation()==null)
{
entry.setDefaultNavigation(navigation);
}
else
{
throw new IllegalStateException("multiple catchall <navigation> elements");
}
}
else
{
Object old = entry.getNavigations().put(expression, navigation);
if (old!=null)
{
throw new IllegalStateException("multiple <navigation> elements for action: " + expression);
}
}
}
/**
* Parse param
*/
private static Param parseParam(Element element)
{
String valueExpression = element.attributeValue("value");
String name = element.attributeValue("name");
if (name==null)
{
if (valueExpression==null)
{
throw new IllegalArgumentException("must specify name or value for page <param/> declaration");
}
name = valueExpression.substring(2, valueExpression.length()-1);
}
Param param = new Param(name);
if (valueExpression!=null)
{
param.setValueExpression(Expressions.instance().createValueExpression(valueExpression));
}
param.setConverterId(element.attributeValue("converterId"));
String converterExpression = element.attributeValue("converter");
if (converterExpression!=null)
{
param.setConverterValueExpression(Expressions.instance().createValueExpression(converterExpression));
}
param.setValidatorId(element.attributeValue("validatorId"));
String validatorExpression = element.attributeValue("validator");
if (validatorExpression!=null)
{
param.setValidatorValueExpression(Expressions.instance().createValueExpression(validatorExpression));
}
param.setRequired( "true".equals( element.attributeValue("required") ) );
return param;
}
/**
* Parse rule
*/
private static Rule parseRule(Element element)
{
Rule rule = new Rule();
rule.setOutcomeValue( element.attributeValue("if-outcome") );
String expression = element.attributeValue("if");
if (expression!=null)
{
rule.setCondition( Expressions.instance().createValueExpression(expression) );
}
parseConversationControl( element, rule.getConversationControl() );
parseTaskControl(element, rule.getTaskControl());
parseProcessControl(element, rule.getProcessControl());
parseEvent(element, rule);
parseNavigationHandler(element, rule);
return rule;
}
private static void parseNavigationHandler(Element element, Rule rule)
{
Element render = element.element("render");
if (render!=null)
{
final String viewId = render.attributeValue("view-id");
Element messageElement = render.element("message");
String message = messageElement==null ? null : messageElement.getTextTrim();
String control = messageElement==null ? null : messageElement.attributeValue("for");
String severityName = messageElement==null ? null : messageElement.attributeValue("severity");
Severity severity = severityName==null ?
FacesMessage.SEVERITY_INFO :
getFacesMessageValuesMap().get( severityName.toUpperCase() );
rule.addNavigationHandler( new RenderNavigationHandler(viewId == null ? null :
Expressions.instance().createValueExpression(viewId, String.class), message, severity, control) );
}
Element redirect = element.element("redirect");
if (redirect!=null)
{
List<Element> children = redirect.elements("param");
final List<Param> params = new ArrayList<Param>();
for (Element child: children)
{
params.add( parseParam(child) );
}
final String viewId = redirect.attributeValue("view-id");
Element messageElement = redirect.element("message");
String control = messageElement==null ? null : messageElement.attributeValue("for");
String message = messageElement==null ? null : messageElement.getTextTrim();
String severityName = messageElement==null ? null : messageElement.attributeValue("severity");
Severity severity = severityName==null ?
FacesMessage.SEVERITY_INFO :
getFacesMessageValuesMap().get( severityName.toUpperCase() );
rule.addNavigationHandler( new RedirectNavigationHandler(viewId == null ? null :
Expressions.instance().createValueExpression(viewId, String.class), params, message, severity, control) );
}
List<Element> childElements = element.elements("out");
for (Element child: childElements)
{
Output output = new Output();
output.setName( child.attributeValue("name") );
output.setValue( Expressions.instance().createValueExpression( child.attributeValue("value") ) );
String scopeName = child.attributeValue("scope");
if (scopeName==null)
{
output.setScope(ScopeType.CONVERSATION);
}
else
{
output.setScope( ScopeType.valueOf( scopeName.toUpperCase() ) );
}
rule.getOutputs().add(output);
}
}
public static Map<String, Severity> getFacesMessageValuesMap()
{
Map<String, Severity> result = new HashMap<String, Severity>();
for (Map.Entry<String, Severity> me: (Set<Map.Entry<String, Severity>>) FacesMessage.VALUES_MAP.entrySet())
{
result.put( me.getKey().toUpperCase(), me.getValue() );
}
return result;
}
/**
* The global setting for no-conversation-viewid.
*
* @return a JSF view id
*/
public String getNoConversationViewId()
{
return noConversationViewId;
}
public void setNoConversationViewId(String noConversationViewId)
{
this.noConversationViewId = noConversationViewId;
}
/**
* The global setting for login-viewid.
*
* @return a JSF view id
*/
public String getLoginViewId()
{
return loginViewId;
}
public void setLoginViewId(String loginViewId)
{
this.loginViewId = loginViewId;
}
public static String getCurrentViewId()
{
return getViewId( FacesContext.getCurrentInstance() );
}
public static String getViewId(FacesContext facesContext)
{
if (facesContext!=null)
{
UIViewRoot viewRoot = facesContext.getViewRoot();
if (viewRoot!=null) return viewRoot.getViewId();
}
return null;
}
public Integer getHttpPort()
{
return httpPort;
}
public void setHttpPort(Integer httpPort)
{
this.httpPort = httpPort;
}
public Integer getHttpsPort()
{
return httpsPort;
}
public void setHttpsPort(Integer httpsPort)
{
this.httpsPort = httpsPort;
}
public String[] getResources()
{
return resources;
}
public void setResources(String[] resources)
{
this.resources = resources;
}
private static boolean isDebugPage(String viewId)
{
return Init.instance().isDebug() && viewId.startsWith("/debug.");
}
public static boolean isDebugPage()
{
return Init.instance().isDebug() &&
getCurrentViewId() != null &&
getCurrentViewId().startsWith("/debug.");
}
}