/* * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and contributors. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Lesser General Public License * (LGPL) version 2.1 which accompanies this distribution, and is available at * http://www.gnu.org/licenses/lgpl.html * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * Contributors: * Anahide Tchertchian */ package org.nuxeo.ecm.platform.ui.web.component.holder; import java.io.IOException; import java.util.List; import javax.el.ELException; import javax.el.ValueExpression; import javax.faces.FacesException; import javax.faces.component.ContextCallback; import javax.faces.component.UIComponent; import javax.faces.component.UIInput; import javax.faces.component.html.HtmlInputText; import javax.faces.component.visit.VisitCallback; import javax.faces.component.visit.VisitContext; import javax.faces.context.FacesContext; import javax.faces.event.FacesEvent; import javax.faces.event.PhaseId; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasEvent; import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasVariableMapper; import org.nuxeo.ecm.platform.ui.web.component.ResettableComponent; import com.sun.faces.facelets.tag.jsf.ComponentSupport; /** * Component that keeps and exposes a value to the context during each JSF * phase. * <p> * Can be bound to a value as an input component, or not submit the value and * still expose it to the context at build time as well as at render time. * * @since 5.5 */ public class UIValueHolder extends HtmlInputText implements ResettableComponent { private static final Log log = LogFactory.getLog(UIValueHolder.class); public static final String COMPONENT_TYPE = UIValueHolder.class.getName(); public static final String COMPONENT_FAMILY = UIInput.COMPONENT_FAMILY; protected String var; /** * <p> * The submittedValue value of this {@link UIInput} component. * </p> */ protected transient Object submittedValue = null; protected Boolean submitValue; @Override public String getFamily() { return COMPONENT_FAMILY; } @Override public String getRendererType() { return COMPONENT_TYPE; } public boolean getRendersChildren() { return true; } @Override public void broadcast(FacesEvent event) { if (event instanceof AliasEvent) { FacesContext context = getFacesContext(); AliasVariableMapper alias = getAliasVariableMapper(context); try { AliasVariableMapper.exposeAliasesToRequest(context, alias); FacesEvent origEvent = ((AliasEvent) event).getOriginalEvent(); origEvent.getComponent().broadcast(origEvent); } finally { if (alias != null) { AliasVariableMapper.removeAliasesExposedToRequest(context, alias.getId()); } } } else { super.broadcast(event); } } @Override public void queueEvent(FacesEvent event) { event = new AliasEvent(this, event); super.queueEvent(event); } public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback) throws FacesException { AliasVariableMapper alias = getAliasVariableMapper(context); try { AliasVariableMapper.exposeAliasesToRequest(context, alias); return super.invokeOnComponent(context, clientId, callback); } finally { if (alias != null) { AliasVariableMapper.removeAliasesExposedToRequest(context, alias.getId()); } } } @Override public void encodeBegin(FacesContext context) throws IOException { AliasVariableMapper alias = getAliasVariableMapper(context); AliasVariableMapper.exposeAliasesToRequest(context, alias); super.encodeBegin(context); } @Override public void encodeChildren(final FacesContext context) throws IOException { // no need to expose variables: already done in #encodeBegin processFacetsAndChildren(context, PhaseId.RENDER_RESPONSE); } @Override public void encodeEnd(FacesContext context) throws IOException { super.encodeEnd(context); AliasVariableMapper alias = getAliasVariableMapper(context); if (alias != null) { AliasVariableMapper.removeAliasesExposedToRequest(context, alias.getId()); } } @Override public void processDecodes(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } // XXX: decode component itself first, so that potential submitted // value is accurately exposed in context for facets and children try { decode(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } processFacetsAndChildrenWithVariable(context, PhaseId.APPLY_REQUEST_VALUES); if (isImmediate()) { executeValidate(context); } } @Override public void processValidators(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } processFacetsAndChildrenWithVariable(context, PhaseId.PROCESS_VALIDATIONS); if (!isImmediate()) { executeValidate(context); } } /** * Executes validation logic. */ private void executeValidate(FacesContext context) { try { validate(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } if (!isValid()) { context.renderResponse(); } } @Override public void processUpdates(FacesContext context) { if (context == null) { throw new NullPointerException(); } // Skip processing if our rendered flag is false if (!isRendered()) { return; } processFacetsAndChildrenWithVariable(context, PhaseId.UPDATE_MODEL_VALUES); if (Boolean.TRUE.equals(getSubmitValue())) { try { updateModel(context); } catch (RuntimeException e) { context.renderResponse(); throw e; } } if (!isValid()) { context.renderResponse(); } } protected final void processFacetsAndChildren(final FacesContext context, final PhaseId phaseId) { List<UIComponent> stamps = getChildren(); for (UIComponent stamp : stamps) { processComponent(context, stamp, phaseId); } } protected final void processFacetsAndChildrenWithVariable( final FacesContext context, final PhaseId phaseId) { AliasVariableMapper alias = getAliasVariableMapper(context); try { AliasVariableMapper.exposeAliasesToRequest(context, alias); processFacetsAndChildren(context, phaseId); } finally { if (alias != null) { AliasVariableMapper.removeAliasesExposedToRequest(context, alias.getId()); } } } protected final void processComponent(FacesContext context, UIComponent component, PhaseId phaseId) { if (component != null) { if (phaseId == PhaseId.APPLY_REQUEST_VALUES) { component.processDecodes(context); } else if (phaseId == PhaseId.PROCESS_VALIDATIONS) { component.processValidators(context); } else if (phaseId == PhaseId.UPDATE_MODEL_VALUES) { component.processUpdates(context); } else if (phaseId == PhaseId.RENDER_RESPONSE) { try { ComponentSupport.encodeRecursive(context, component); } catch (IOException err) { log.error("Error while rendering component " + component); } } else { throw new IllegalArgumentException("Bad PhaseId:" + phaseId); } } } // properties management public String getVar() { if (var != null) { return var; } ValueExpression ve = getValueExpression("var"); if (ve != null) { try { return (String) ve.getValue(getFacesContext().getELContext()); } catch (ELException e) { throw new FacesException(e); } } else { return null; } } public void setVar(String var) { this.var = var; } public Boolean getSubmitValue() { if (submitValue != null) { return submitValue; } ValueExpression ve = getValueExpression("submitValue"); if (ve != null) { try { return Boolean.valueOf(Boolean.TRUE.equals(ve.getValue(getFacesContext().getELContext()))); } catch (ELException e) { throw new FacesException(e); } } else { return Boolean.TRUE; } } public void setSubmitValue(Boolean submitValue) { this.submitValue = submitValue; } public Object getValueToExpose() { Object value = getSubmittedValue(); if (value == null) { // get original value bound value = super.getValue(); } return value; } protected AliasVariableMapper getAliasVariableMapper(FacesContext ctx) { String var = getVar(); Object value = getValueToExpose(); AliasVariableMapper alias = new AliasVariableMapper(); // reuse facelets id set on component String aliasId = getFaceletId(); alias.setId(aliasId); alias.setVariable( var, ctx.getApplication().getExpressionFactory().createValueExpression( value, Object.class)); return alias; } // state holder @Override public void restoreState(FacesContext context, Object state) { Object[] values = (Object[]) state; super.restoreState(context, values[0]); var = (String) values[1]; submitValue = (Boolean) values[2]; submittedValue = values[3]; } /** * Saves the locally set literal values kept on the component (from * standard tags attributes) and since 5.6, also saves the submitted value * as {@link UIInput#saveState(FacesContext)} does not do it (see * NXP-8898). */ @Override public Object saveState(FacesContext context) { return new Object[] { super.saveState(context), var, submitValue, getSubmittedValue() }; } /** * Resets the value holder local values * * @since 5.7 */ @Override public void resetCachedModel() { if (getValueExpression("value") != null) { setValue(null); setLocalValueSet(false); } setSubmittedValue(null); } @Override public boolean visitTree(VisitContext visitContext, VisitCallback callback) { FacesContext facesContext = visitContext.getFacesContext(); AliasVariableMapper alias = getAliasVariableMapper(facesContext); try { AliasVariableMapper.exposeAliasesToRequest(facesContext, alias); return super.visitTree(visitContext, callback); } finally { if (alias != null) { AliasVariableMapper.removeAliasesExposedToRequest(facesContext, alias.getId()); } } } public String getFaceletId() { return (String) getAttributes().get(ComponentSupport.MARK_CREATED); } public NuxeoValueHolderBean lookupBean(FacesContext ctx) { String expr = "#{" + NuxeoValueHolderBean.NAME + "}"; NuxeoValueHolderBean bean = (NuxeoValueHolderBean) ctx.getApplication().evaluateExpressionGet( ctx, expr, Object.class); if (bean == null) { log.error("Managed bean not found: " + expr); return null; } return bean; } protected void saveToBean(Object value) { if (getFaceletId() == null) { // not added to the view yet, do not bother return; } FacesContext ctx = FacesContext.getCurrentInstance(); if (ctx != null) { NuxeoValueHolderBean bean = lookupBean(ctx); if (bean != null) { bean.saveState(this, value); } } } @Override public void setSubmittedValue(Object submittedValue) { this.submittedValue = submittedValue; saveToBean(submittedValue); } @Override public Object getSubmittedValue() { return submittedValue; } @Override public void setValue(Object value) { super.setValue(value); saveToBean(value); } }