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}