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