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    public SetTagHandler(ComponentConfig config) {
079        super(config, null);
080        var = getRequiredAttribute("var");
081        value = getAttribute("value");
082        resolveTwice = getAttribute("resolveTwice");
083        blockPatterns = getAttribute("blockPatterns");
084        blockMerge = getAttribute("blockMerge");
085    }
086
087    @Override
088    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException,
089            ELException {
090        // make sure our parent is not null
091        if (parent == null) {
092            throw new TagException(tag, "Parent UIComponent was null");
093        }
094
095        FaceletHandler nextHandler = this.nextHandler;
096        VariableMapper orig = ctx.getVariableMapper();
097        AliasVariableMapper target = new AliasVariableMapper();
098        // generate id before applying (and before generating next handler, in
099        // case of merge of variables, as parent aliases will be exposed to
100        // request then).
101        target.setId(ctx.generateUniqueId(tagId));
102
103        VariableMapper vm = target.getVariableMapperForBuild(orig);
104        ctx.setVariableMapper(vm);
105        try {
106            nextHandler = getAliasVariableMapper(ctx, target);
107        } finally {
108            ctx.setVariableMapper(orig);
109        }
110        apply(ctx, parent, target, nextHandler);
111    }
112
113    public FaceletHandler getNextHandler() {
114        return nextHandler;
115    }
116
117    public boolean isAcceptingMerge(FaceletContext ctx) {
118        if (blockMerge != null) {
119            if (blockMerge.getBoolean(ctx)) {
120                return false;
121            }
122        }
123        if (blockPatterns != null) {
124            String blocked = blockPatterns.getValue(ctx);
125            if (!StringUtils.isEmpty(blocked)) {
126                return false;
127            }
128        }
129        return true;
130    }
131
132    public FaceletHandler getAliasVariableMapper(FaceletContext ctx, AliasVariableMapper target) {
133        String varStr = var.getValue(ctx);
134        // avoid overriding variable already in the mapper
135        if (target.hasVariables(varStr)) {
136            return nextHandler;
137        }
138
139        // handle variable expression
140        boolean cacheValue = false;
141        if (cache != null) {
142            cacheValue = cache.getBoolean(ctx);
143        }
144        boolean resolveTwiceBool = false;
145        if (resolveTwice != null) {
146            resolveTwiceBool = resolveTwice.getBoolean(ctx);
147        }
148
149        ValueExpression ve;
150        if (cacheValue) {
151            // resolve value and put it as is in variable mapper
152            Object res = value.getObject(ctx);
153            if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) {
154                ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class);
155                res = ve.getValue(ctx);
156            }
157            ve = ctx.getExpressionFactory().createValueExpression(res, Object.class);
158        } else {
159            ve = value.getValueExpression(ctx, Object.class);
160            if (resolveTwiceBool) {
161                ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), ctx.getVariableMapper());
162            }
163        }
164
165        target.setVariable(varStr, ve);
166
167        if (blockPatterns != null) {
168            String blockedValue = blockPatterns.getValue(ctx);
169            if (!StringUtils.isEmpty(blockedValue)) {
170                // split on "," character
171                target.setBlockedPatterns(resolveBlockPatterns(blockedValue));
172            }
173        }
174
175        FaceletHandler nextHandler = this.nextHandler;
176        if (nextHandler instanceof SetTagHandler) {
177            // try merging with next handler
178            SetTagHandler next = (SetTagHandler) nextHandler;
179            if (next.isAcceptingMerge(ctx)) {
180                // make sure referenced vars will be resolved in this context
181                ctx.getVariableMapper().setVariable(varStr, ve);
182                try {
183                    AliasVariableMapper.exposeAliasesToRequest(ctx.getFacesContext(), target);
184                    nextHandler = next.getAliasVariableMapper(ctx, target);
185                } finally {
186                    AliasVariableMapper.removeAliasesExposedToRequest(ctx.getFacesContext(), target.getId());
187                }
188            }
189        }
190
191        return nextHandler;
192    }
193
194    protected List<String> resolveBlockPatterns(String value) {
195        List<String> res = new ArrayList<String>();
196        if (value != null) {
197            String[] split = StringUtils.split(value, ',');
198            if (split != null) {
199                for (String item : split) {
200                    res.add(item.trim());
201                }
202            }
203        }
204        return res;
205    }
206
207}