001/*
002 * (C) Copyright 2010 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nuxeo - initial API and implementation
016 *
017 * $Id: $
018 */
019
020package org.nuxeo.ecm.platform.ui.web.tag.handler;
021
022import java.io.IOException;
023import java.util.ArrayList;
024import java.util.List;
025
026import javax.el.ELException;
027import javax.el.ValueExpression;
028import javax.el.VariableMapper;
029import javax.faces.FacesException;
030import javax.faces.component.UIComponent;
031import javax.faces.view.facelets.ComponentConfig;
032import javax.faces.view.facelets.FaceletContext;
033import javax.faces.view.facelets.FaceletException;
034import javax.faces.view.facelets.FaceletHandler;
035import javax.faces.view.facelets.TagAttribute;
036import javax.faces.view.facelets.TagException;
037
038import org.apache.commons.lang.StringUtils;
039import org.nuxeo.ecm.platform.ui.web.binding.MetaValueExpression;
040import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasTagHandler;
041import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasVariableMapper;
042import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
043
044/**
045 * Tag handler that exposes a variable to the variable map. Behaviour is close to the c:set tag handler except:
046 * <ul>
047 * <li>It allows caching a variable using cache parameter: variable will be resolved the first time is is called and
048 * will be put in the context after</li>
049 * <li>The resolved variable is removed from context when tag is closed to avoid filling the context with it</li>
050 * <li>Since 5.4, variables are made available in the request context after the JSF component tree build thanks to a
051 * backing component.</li>
052 * </ul>
053 *
054 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
055 * @since 5.3.1
056 */
057public class SetTagHandler extends AliasTagHandler {
058
059    protected final TagAttribute var;
060
061    protected final TagAttribute value;
062
063    /**
064     * @since 5.5
065     */
066    protected final TagAttribute resolveTwice;
067
068    /**
069     * @since 5.6
070     */
071    protected final TagAttribute blockPatterns;
072
073    /**
074     * @since 5.9.2
075     */
076    protected final TagAttribute blockMerge;
077
078    /**
079     * Determines in which context expression will be evaluated when expression is not cached and resolved twice (build
080     * time by default, render time if local).
081     *
082     * @since 7.10
083     */
084    protected final TagAttribute local;
085
086    public SetTagHandler(ComponentConfig config) {
087        super(config, null);
088        var = getRequiredAttribute("var");
089        value = getAttribute("value");
090        resolveTwice = getAttribute("resolveTwice");
091        blockPatterns = getAttribute("blockPatterns");
092        blockMerge = getAttribute("blockMerge");
093        local = getAttribute("local");
094    }
095
096    @Override
097    public void apply(FaceletContext ctx, UIComponent parent)
098            throws IOException, FacesException, FaceletException, ELException {
099        // make sure our parent is not null
100        if (parent == null) {
101            throw new TagException(tag, "Parent UIComponent was null");
102        }
103
104        FaceletHandler nextHandler = this.nextHandler;
105        VariableMapper orig = ctx.getVariableMapper();
106        AliasVariableMapper target = new AliasVariableMapper();
107        // generate id before applying (and before generating next handler, in
108        // case of merge of variables, as parent aliases will be exposed to
109        // request then).
110        target.setId(ctx.generateUniqueId(tagId));
111
112        VariableMapper vm = target.getVariableMapperForBuild(orig);
113        ctx.setVariableMapper(vm);
114        try {
115            nextHandler = getAliasVariableMapper(ctx, target);
116        } finally {
117            ctx.setVariableMapper(orig);
118        }
119        apply(ctx, parent, target, nextHandler);
120    }
121
122    public FaceletHandler getNextHandler() {
123        return nextHandler;
124    }
125
126    public boolean isAcceptingMerge(FaceletContext ctx) {
127        if (blockMerge != null) {
128            if (blockMerge.getBoolean(ctx)) {
129                return false;
130            }
131        }
132        if (blockPatterns != null) {
133            String blocked = blockPatterns.getValue(ctx);
134            if (!StringUtils.isEmpty(blocked)) {
135                return false;
136            }
137        }
138        return true;
139    }
140
141    public FaceletHandler getAliasVariableMapper(FaceletContext ctx, AliasVariableMapper target) {
142        String varStr = var.getValue(ctx);
143        // avoid overriding variable already in the mapper
144        if (target.hasVariables(varStr)) {
145            return nextHandler;
146        }
147
148        // handle variable expression
149        boolean cacheValue = false;
150        if (cache != null) {
151            cacheValue = cache.getBoolean(ctx);
152        }
153        boolean resolveTwiceBool = false;
154        if (resolveTwice != null) {
155            resolveTwiceBool = resolveTwice.getBoolean(ctx);
156        }
157
158        ValueExpression ve;
159        if (cacheValue) {
160            // resolve value and put it as is in variable mapper
161            Object res = value.getObject(ctx);
162            if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) {
163                ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class);
164                res = ve.getValue(ctx);
165            }
166            ve = ctx.getExpressionFactory().createValueExpression(res, Object.class);
167        } else {
168            ve = value.getValueExpression(ctx, Object.class);
169            if (resolveTwiceBool) {
170                boolean localBool = false;
171                if (local != null) {
172                    localBool = local.getBoolean(ctx);
173                }
174                if (localBool) {
175                    ve = new MetaValueExpression(ve);
176                } else {
177                    ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), ctx.getVariableMapper());
178                }
179            }
180        }
181
182        target.setVariable(varStr, ve);
183
184        if (blockPatterns != null) {
185            String blockedValue = blockPatterns.getValue(ctx);
186            if (!StringUtils.isEmpty(blockedValue)) {
187                // split on "," character
188                target.setBlockedPatterns(resolveBlockPatterns(blockedValue));
189            }
190        }
191
192        FaceletHandler nextHandler = this.nextHandler;
193        if (nextHandler instanceof SetTagHandler) {
194            // try merging with next handler
195            SetTagHandler next = (SetTagHandler) nextHandler;
196            if (next.isAcceptingMerge(ctx)) {
197                // make sure referenced vars will be resolved in this context
198                ctx.getVariableMapper().setVariable(varStr, ve);
199                try {
200                    AliasVariableMapper.exposeAliasesToRequest(ctx.getFacesContext(), target);
201                    nextHandler = next.getAliasVariableMapper(ctx, target);
202                } finally {
203                    AliasVariableMapper.removeAliasesExposedToRequest(ctx.getFacesContext(), target.getId());
204                }
205            }
206        }
207
208        return nextHandler;
209    }
210
211    protected List<String> resolveBlockPatterns(String value) {
212        List<String> res = new ArrayList<String>();
213        if (value != null) {
214            String[] split = StringUtils.split(value, ',');
215            if (split != null) {
216                for (String item : split) {
217                    res.add(item.trim());
218                }
219            }
220        }
221        return res;
222    }
223
224}