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.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; 044import org.nuxeo.ecm.platform.ui.web.binding.MetaValueExpression; 045import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasTagHandler; 046import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasVariableMapper; 047import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 048import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; 049 050/** 051 * Tag handler that exposes a variable to the variable map. Behaviour is close to the c:set tag handler except: 052 * <ul> 053 * <li>It allows caching a variable using cache parameter: variable will be resolved the first time is is called and 054 * will be put in the context after</li> 055 * <li>The resolved variable is removed from context when tag is closed to avoid filling the context with it</li> 056 * <li>Since 5.4, variables are made available in the request context after the JSF component tree build thanks to a 057 * backing component.</li> 058 * </ul> 059 * 060 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 061 * @since 5.3.1 062 */ 063public class SetTagHandler extends AliasTagHandler { 064 065 private static final Log log = LogFactory.getLog(SetTagHandler.class); 066 067 protected final TagAttribute var; 068 069 protected final TagAttribute value; 070 071 /** 072 * @since 5.5 073 */ 074 protected final TagAttribute resolveTwice; 075 076 /** 077 * @since 5.6 078 */ 079 protected final TagAttribute blockPatterns; 080 081 /** 082 * @since 5.9.2 083 */ 084 protected final TagAttribute blockMerge; 085 086 /** 087 * Determines in which context expression will be evaluated when expression is not cached and resolved twice (build 088 * time by default, render time if local). 089 * 090 * @since 7.10 091 */ 092 protected final TagAttribute local; 093 094 /** 095 * Force using of {@link AliasVariableMapper} logics, exposing a reference of to a value that might change between 096 * "restore view" and "render response" phase, to make sure it's not cached by components and resolved again at 097 * "render response" phase. 098 * 099 * @since 8.2 100 */ 101 protected final TagAttribute useAlias; 102 103 public SetTagHandler(ComponentConfig config) { 104 super(config, null); 105 var = getRequiredAttribute("var"); 106 value = getAttribute("value"); 107 resolveTwice = getAttribute("resolveTwice"); 108 blockPatterns = getAttribute("blockPatterns"); 109 blockMerge = getAttribute("blockMerge"); 110 local = getAttribute("local"); 111 useAlias = getAttribute("useAlias"); 112 } 113 114 @Override 115 public void apply(FaceletContext ctx, UIComponent parent) 116 throws IOException, FacesException, FaceletException, ELException { 117 long start = FaceletDebugTracer.start(); 118 String varStr = null; 119 try { 120 121 // make sure our parent is not null 122 if (parent == null) { 123 throw new TagException(tag, "Parent UIComponent was null"); 124 } 125 126 boolean useAliasBool = false; 127 if (useAlias != null) { 128 useAliasBool = useAlias.getBoolean(ctx); 129 } 130 131 if (!useAliasBool && isOptimizedAgain()) { 132 varStr = var.getValue(ctx); 133 VariableMapper orig = ctx.getVariableMapper(); 134 boolean done = false; 135 if (orig instanceof BlockingVariableMapper) { 136 BlockingVariableMapper vm = (BlockingVariableMapper) orig; 137 if (isAcceptingMerge(ctx, vm, varStr)) { 138 FaceletHandler next = applyOptimized(ctx, parent, vm, varStr); 139 next.apply(ctx, parent); 140 done = true; 141 } 142 } 143 if (!done) { 144 try { 145 BlockingVariableMapper vm = new BlockingVariableMapper(orig); 146 ctx.setVariableMapper(vm); 147 FaceletHandler next = applyOptimized(ctx, parent, vm, varStr); 148 next.apply(ctx, parent); 149 } finally { 150 ctx.setVariableMapper(orig); 151 } 152 } 153 } else { 154 applyAlias(ctx, parent); 155 } 156 } finally { 157 FaceletDebugTracer.trace(start, getTag(), var.getValue()); 158 } 159 } 160 161 public FaceletHandler getNextHandler() { 162 return nextHandler; 163 } 164 165 public boolean isAcceptingMerge(FaceletContext ctx, BlockingVariableMapper vm, String var) { 166 // avoid overriding variable already in the mapper 167 if (vm.hasVariable(var)) { 168 return false; 169 } 170 return isAcceptingMerge(ctx); 171 } 172 173 public boolean isAcceptingMerge(FaceletContext ctx) { 174 if (useAlias != null && useAlias.getBoolean(ctx)) { 175 return false; 176 } 177 if (blockMerge != null && blockMerge.getBoolean(ctx)) { 178 return false; 179 } 180 if (blockPatterns != null) { 181 String blocked = blockPatterns.getValue(ctx); 182 if (!StringUtils.isEmpty(blocked)) { 183 return false; 184 } 185 } 186 return true; 187 } 188 189 public FaceletHandler applyOptimized(FaceletContext ctx, UIComponent parent, BlockingVariableMapper vm) 190 throws IOException { 191 String varStr = var.getValue(ctx); 192 return applyOptimized(ctx, parent, vm, varStr); 193 } 194 195 public FaceletHandler applyOptimized(FaceletContext ctx, UIComponent parent, BlockingVariableMapper vm, 196 String varStr) throws IOException { 197 198 // handle variable expression 199 boolean cacheValue = false; 200 if (cache != null) { 201 cacheValue = cache.getBoolean(ctx); 202 } 203 boolean resolveTwiceBool = false; 204 if (resolveTwice != null) { 205 resolveTwiceBool = resolveTwice.getBoolean(ctx); 206 } 207 208 ValueExpression ve; 209 if (cacheValue) { 210 // resolve value and put it as is in variable mapper 211 Object res = value.getObject(ctx); 212 if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) { 213 ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class); 214 res = ve.getValue(ctx); 215 } 216 ve = ctx.getExpressionFactory().createValueExpression(res, Object.class); 217 } else { 218 ve = value.getValueExpression(ctx, Object.class); 219 if (resolveTwiceBool) { 220 boolean localBool = false; 221 if (local != null) { 222 localBool = local.getBoolean(ctx); 223 } 224 if (localBool) { 225 ve = new MetaValueExpression(ve); 226 } else { 227 ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), vm); 228 } 229 } 230 } 231 232 vm.setVariable(varStr, ve); 233 234 if (blockPatterns != null) { 235 String blockedValue = blockPatterns.getValue(ctx); 236 if (!StringUtils.isEmpty(blockedValue)) { 237 // split on "," character 238 vm.setBlockedPatterns(resolveBlockPatterns(blockedValue)); 239 } 240 } 241 242 FaceletHandler nextHandler = this.nextHandler; 243 if (nextHandler instanceof SetTagHandler) { 244 // try merging with next handler 245 SetTagHandler next = (SetTagHandler) nextHandler; 246 if (next.isAcceptingMerge(ctx)) { 247 nextHandler = next.applyOptimized(ctx, parent, vm); 248 } 249 } 250 251 return nextHandler; 252 } 253 254 public void applyAlias(FaceletContext ctx, UIComponent parent) throws IOException { 255 FaceletHandler nextHandler = this.nextHandler; 256 VariableMapper orig = ctx.getVariableMapper(); 257 AliasVariableMapper target = new AliasVariableMapper(); 258 // generate id before applying (and before generating next handler, in 259 // case of merge of variables, as parent aliases will be exposed to 260 // request then). 261 target.setId(ctx.generateUniqueId(tagId)); 262 263 VariableMapper vm = target.getVariableMapperForBuild(orig); 264 ctx.setVariableMapper(vm); 265 try { 266 nextHandler = getAliasVariableMapper(ctx, target); 267 } finally { 268 ctx.setVariableMapper(orig); 269 } 270 applyAliasHandler(ctx, parent, target, nextHandler); 271 } 272 273 public FaceletHandler getAliasVariableMapper(FaceletContext ctx, AliasVariableMapper target) { 274 String varStr = var.getValue(ctx); 275 // avoid overriding variable already in the mapper 276 if (target.hasVariables(varStr)) { 277 return nextHandler; 278 } 279 280 // handle variable expression 281 boolean cacheValue = false; 282 if (cache != null) { 283 cacheValue = cache.getBoolean(ctx); 284 } 285 boolean resolveTwiceBool = false; 286 if (resolveTwice != null) { 287 resolveTwiceBool = resolveTwice.getBoolean(ctx); 288 } 289 290 ValueExpression ve; 291 if (cacheValue) { 292 // resolve value and put it as is in variable mapper 293 Object res = value.getObject(ctx); 294 if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) { 295 ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class); 296 res = ve.getValue(ctx); 297 } 298 ve = ctx.getExpressionFactory().createValueExpression(res, Object.class); 299 } else { 300 ve = value.getValueExpression(ctx, Object.class); 301 if (resolveTwiceBool) { 302 boolean localBool = false; 303 if (local != null) { 304 localBool = local.getBoolean(ctx); 305 } 306 if (localBool) { 307 ve = new MetaValueExpression(ve); 308 } else { 309 ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), ctx.getVariableMapper()); 310 } 311 } 312 } 313 314 target.setVariable(varStr, ve); 315 316 if (blockPatterns != null) { 317 String blockedValue = blockPatterns.getValue(ctx); 318 if (!StringUtils.isEmpty(blockedValue)) { 319 // split on "," character 320 target.setBlockedPatterns(resolveBlockPatterns(blockedValue)); 321 } 322 } 323 324 FaceletHandler nextHandler = this.nextHandler; 325 if (nextHandler instanceof SetTagHandler) { 326 // try merging with next handler 327 SetTagHandler next = (SetTagHandler) nextHandler; 328 if (next.isAcceptingMerge(ctx)) { 329 // make sure referenced vars will be resolved in this context 330 ctx.getVariableMapper().setVariable(varStr, ve); 331 try { 332 AliasVariableMapper.exposeAliasesToRequest(ctx.getFacesContext(), target); 333 nextHandler = next.getAliasVariableMapper(ctx, target); 334 } finally { 335 AliasVariableMapper.removeAliasesExposedToRequest(ctx.getFacesContext(), target.getId()); 336 } 337 } 338 } 339 340 return nextHandler; 341 } 342 343 protected List<String> resolveBlockPatterns(String value) { 344 List<String> res = new ArrayList<String>(); 345 if (value != null) { 346 String[] split = StringUtils.split(value, ','); 347 if (split != null) { 348 for (String item : split) { 349 res.add(item.trim()); 350 } 351 } 352 } 353 return res; 354 } 355 356}