org.nuxeo.ecm.platform.ui.web.component.holder.UIValueHolder.java Source code

Java tutorial

Introduction

Here is the source code for org.nuxeo.ecm.platform.ui.web.component.holder.UIValueHolder.java

Source

/*
 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others.
 *
 * Licensed 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.
 *
 * 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;
    }

    @Override
    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);
    }

    @Override
    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) {
        if (!isVisitable(visitContext)) {
            return false;
        }
        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);
    }

}