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}