001/*
002 * (C) Copyright 2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Anahide Tchertchian
018 */
019package org.nuxeo.ecm.platform.ui.web.component.holder;
020
021import java.io.IOException;
022import java.util.ArrayList;
023import java.util.List;
024
025import javax.el.ELException;
026import javax.el.ExpressionFactory;
027import javax.el.ValueExpression;
028import javax.el.VariableMapper;
029import javax.faces.FacesException;
030import javax.faces.component.UIComponent;
031import javax.faces.context.FacesContext;
032import javax.faces.event.PhaseId;
033import javax.faces.view.facelets.ComponentConfig;
034import javax.faces.view.facelets.FaceletContext;
035import javax.faces.view.facelets.TagAttribute;
036
037import org.apache.commons.lang.StringUtils;
038import org.apache.commons.logging.Log;
039import org.apache.commons.logging.LogFactory;
040import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasVariableMapper;
041import org.nuxeo.ecm.platform.ui.web.tag.handler.GenericHtmlComponentHandler;
042import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer;
043
044import com.sun.faces.facelets.tag.jsf.ComponentSupport;
045
046/**
047 * Tag handler for a {@link UIValueHolder} component, that exposes the value kept by the component at build time for
048 * children components.
049 *
050 * @since 5.5
051 */
052public class ValueHolderTagHandler extends GenericHtmlComponentHandler {
053
054    protected final Log log = LogFactory.getLog(ValueHolderTagHandler.class);
055
056    protected final TagAttribute var;
057
058    /**
059     * @since 8.2
060     */
061    protected final TagAttribute skip;
062
063    public ValueHolderTagHandler(ComponentConfig config) {
064        super(config);
065        var = getAttribute("var");
066        skip = getAttribute("skip");
067    }
068
069    @Override
070    public void apply(FaceletContext ctx, UIComponent parent) throws IOException {
071        boolean skipValue = false;
072        if (skip != null) {
073            skipValue = skip.getBoolean(ctx);
074        }
075        if (skipValue) {
076            super.applyNextHandler(ctx, parent);
077        } else {
078            super.apply(ctx, parent);
079        }
080    }
081
082    @Override
083    public void applyNextHandler(FaceletContext ctx, UIComponent c) throws IOException, FacesException, ELException {
084        long start = FaceletDebugTracer.start();
085        String varName = null;
086        try {
087            boolean varSet = false;
088            if (var != null) {
089                varName = var.getValue(ctx);
090            }
091
092            VariableMapper orig = ctx.getVariableMapper();
093            AliasVariableMapper alias = new AliasVariableMapper();
094            // XXX: reuse the component id as the alias variable mapper id so that
095            // the value holder JSF component can reuse it at render time to expose
096            // the value it keeps
097            String aliasId = (String) c.getAttributes().get(ComponentSupport.MARK_CREATED);
098            alias.setId(aliasId);
099
100            if (!StringUtils.isBlank(varName)) {
101                varSet = true;
102                List<String> blockedPatterns = new ArrayList<String>();
103                blockedPatterns.add(varName);
104                alias.setBlockedPatterns(blockedPatterns);
105            }
106
107            try {
108                if (varSet) {
109                    Object valueToExpose = retrieveValueToExpose(ctx, c);
110                    ExpressionFactory eFactory = ctx.getExpressionFactory();
111                    ValueExpression valueVe = eFactory.createValueExpression(valueToExpose, Object.class);
112                    alias.setVariable(varName, valueVe);
113                    VariableMapper vm = alias.getVariableMapperForBuild(orig);
114                    ctx.setVariableMapper(vm);
115                    AliasVariableMapper.exposeAliasesToRequest(ctx.getFacesContext(), alias);
116                }
117                super.applyNextHandler(ctx, c);
118            } finally {
119                if (varSet) {
120                    AliasVariableMapper.removeAliasesExposedToRequest(ctx.getFacesContext(), aliasId);
121                    ctx.setVariableMapper(orig);
122                }
123            }
124        } finally {
125            FaceletDebugTracer.trace(start, getTag(), varName);
126        }
127    }
128
129    /**
130     * Returns the value to expose at build time for this tag handler.
131     * <p>
132     * Value can be retrieved directly from component in most of cases, but should be retrieved from view-scoped managed
133     * bean when the restore phase is called (as component has not been restored yet, so its value is not available to
134     * be exposed in the tree view being built).
135     *
136     * @since 6.0
137     */
138    protected Object retrieveValueToExpose(FaceletContext context, UIComponent comp) {
139        if (comp instanceof UIValueHolder) {
140            UIValueHolder c = (UIValueHolder) comp;
141            FacesContext faces = context.getFacesContext();
142            if (PhaseId.RESTORE_VIEW.equals(faces.getCurrentPhaseId())) {
143                // lookup backing bean
144                NuxeoValueHolderBean bean = c.lookupBean(faces);
145                if (bean != null) {
146                    String fid = c.getFaceletId();
147                    if (fid != null && bean.hasState(fid)) {
148                        return bean.getState(fid);
149                    }
150                }
151            }
152            return c.getValueToExpose();
153        } else {
154            String className = null;
155            if (comp != null) {
156                className = comp.getClass().getName();
157            }
158            log.error("Associated component with class '" + className
159                    + "' is not a UIValueHolder instance => cannot retrieve value to expose.");
160        }
161        return null;
162    }
163
164}