package org.hdiv.helper; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.faces.application.ConfigurableNavigationHandler; import javax.faces.application.NavigationCase; import javax.faces.application.NavigationHandler; import javax.faces.application.ViewHandler; import javax.faces.component.UIComponent; import javax.faces.component.UIOutcomeTarget; import javax.faces.component.UIParameter; import javax.faces.context.FacesContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * This class has been mostly copied from: * com.sun.faces.renderkit.html_basic.OutcomeTargetRenderer * * Copied method are responsible for generating the url of the * link/button from the outcome of the component. * * @author Gotzon Illarramendi */ public class OutcomeTargetComponentHelper { private static Log log = LogFactory.getLog(OutcomeTargetComponentHelper.class); protected static final Param[] EMPTY_PARAMS = new Param[0]; /** * Search component children for UIParam components. * @param component * @return */ public boolean hasUIParamChilds(UIOutcomeTarget component){ boolean hasParams = false; for (UIComponent comp : component.getChildren()) { if (comp instanceof UIParameter){ hasParams = true; break; } } return hasParams; } /** * Returns the url that the component would generate if it was a * UIOutcomeTarget * @param context * @param component * @return */ public String getUrl(FacesContext context, UIOutcomeTarget component){ NavigationCase navCase = getNavigationCase(context, component); String url = getEncodedTargetURL(context, component, navCase); return url; } /** * Invoke the {@link NavigationHandler} preemptively to resolve a {@link NavigationCase} * for the outcome declared on the {@link UIOutcomeTarget} component. The current view id * is used as the from-view-id when matching navigation cases and the from-action is * assumed to be null. * * @param context the {@link FacesContext} for the current request * @param component the target {@link UIComponent} * * @return the NavigationCase represeting the outcome target */ protected NavigationCase getNavigationCase(FacesContext context, UIComponent component) { NavigationHandler navHandler = context.getApplication().getNavigationHandler(); if (!(navHandler instanceof ConfigurableNavigationHandler)) { // if (logger.isLoggable(Level.WARNING)) { // logger.log(Level.WARNING, // "jsf.outcome.target.invalid.navigationhandler.type", // component.getId()); // } log.warn("jsf.outcome.target.invalid.navigationhandler.type Componente:"+component.getId()); return null; } String outcome = ((UIOutcomeTarget) component).getOutcome(); if (outcome == null) { outcome = context.getViewRoot().getViewId(); // QUESTION should we avoid the call to getNavigationCase() and instead instantiate one explicitly? //String viewId = context.getViewRoot().getViewId(); //return new NavigationCase(viewId, null, null, null, viewId, false, false); } NavigationCase navCase = ((ConfigurableNavigationHandler) navHandler).getNavigationCase(context, null, outcome); if (navCase == null) { // if (logger.isLoggable(Level.WARNING)) { // logger.log(Level.WARNING, // "jsf.outcometarget.navigation.case.not.resolved", // component.getId()); // } log.warn("jsf.outcometarget.navigation.case.not.resolved Componente:"+component.getId()); } return navCase; } /** * <p>Resolve the target view id and then delegate to * {@link ViewHandler#getBookmarkableURL(javax.faces.context.FacesContext, String, java.util.Map, boolean)} * to produce a redirect URL, which will add the page parameters if necessary * and properly prioritizing the parameter overrides.</p> * * @param context the {@link FacesContext} for the current request * @param component the target {@link UIComponent} * @param navCase the target navigation case * * @return an encoded URL for the provided navigation case */ protected String getEncodedTargetURL(FacesContext context, UIComponent component, NavigationCase navCase) { // FIXME getNavigationCase doesn't resolve the target viewId (it is part of CaseStruct) String toViewId = navCase.getToViewId(context); Map<String,List<String>> params = getParamOverrides(component); addNavigationParams(navCase, params); return context.getApplication().getViewHandler().getBookmarkableURL(context, toViewId, params, isIncludeViewParams(component, navCase)); } protected boolean isIncludeViewParams(UIComponent component, NavigationCase navcase) { return (((UIOutcomeTarget) component).isIncludeViewParams() || navcase.isIncludeViewParams()); } protected Map<String, List<String>> getParamOverrides(UIComponent component) { Map<String, List<String>> params = new LinkedHashMap<String, List<String>>(); Param[] declaredParams = getParamList(component); for (Param candidate : declaredParams) { // QUESTION shouldn't the trimming of name should be done elsewhere? // null value is allowed as a way to suppress page parameter if (candidate.name != null && candidate.name.trim().length() > 0) { candidate.name = candidate.name.trim(); List<String> values = params.get(candidate.name); if (values == null) { values = new ArrayList<String>(); params.put(candidate.name, values); } values.add(candidate.value); } } return params; } /** * @param command the command which may have parameters * * @return an array of parameters */ protected Param[] getParamList(UIComponent command) { if (command.getChildCount() > 0) { ArrayList<Param> parameterList = new ArrayList<Param>(); for (UIComponent kid : command.getChildren()) { if (kid instanceof UIParameter) { UIParameter uiParam = (UIParameter) kid; if (!uiParam.isDisable()) { Object value = uiParam.getValue(); Param param = new Param(uiParam.getName(), (value == null ? null : value.toString())); parameterList.add(param); } } } return parameterList.toArray(new Param[parameterList.size()]); } else { return EMPTY_PARAMS; } } protected void addNavigationParams(NavigationCase navCase, Map<String,List<String>> existingParams) { Map<String,List<String>> navParams = navCase.getParameters(); if (navParams == null || navParams.isEmpty()) { return; } for (Map.Entry<String,List<String>> entry : navParams.entrySet()) { String navParamName = entry.getKey(); // only add the navigation params to the existing params collection // if the parameter name isn't already present within the existing // collection if (!existingParams.containsKey(navParamName)) { existingParams.put(navParamName, entry.getValue()); } } } /** * <p>Simple class to encapsulate the name and value of a * <code>UIParameter</code>. */ public static class Param { public String name; public String value; // -------------------------------------------------------- Constructors public Param(String name, String value) { this.name = name; this.value = value; } } }