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.binding.alias; 023 024import java.io.IOException; 025import java.util.List; 026import java.util.Map; 027 028import javax.el.ELException; 029import javax.el.ExpressionFactory; 030import javax.el.ValueExpression; 031import javax.el.VariableMapper; 032import javax.faces.FacesException; 033import javax.faces.component.UIComponent; 034import javax.faces.component.UIViewRoot; 035import javax.faces.context.FacesContext; 036import javax.faces.view.facelets.ComponentConfig; 037import javax.faces.view.facelets.ComponentHandler; 038import javax.faces.view.facelets.FaceletContext; 039import javax.faces.view.facelets.FaceletException; 040import javax.faces.view.facelets.FaceletHandler; 041import javax.faces.view.facelets.TagAttribute; 042import javax.faces.view.facelets.TagException; 043 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; 047import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; 048import org.nuxeo.runtime.api.Framework; 049import org.nuxeo.runtime.services.config.ConfigurationService; 050 051import com.sun.faces.facelets.tag.jsf.ComponentSupport; 052 053/** 054 * Tag handler that exposes variables to the variable map. Behaviour is close to the c:set tag handler except: 055 * <ul> 056 * <li>It handles several variables</li> 057 * <li>It allows caching a variable using cache parameter: variable will be resolved the first time is is called and 058 * will be put in the context after</li> 059 * <li>The resolved variable is removed from context when tag is closed to avoid filling the context with it</li> 060 * <li>Variables are made available in the request context after the JSF component tree build thanks to a backing 061 * component.</li> 062 * </ul> 063 * <p> 064 * The backing component value expressions are changed even if the component was found to ensure a good resolution even 065 * when re-rendering the tag using ajax. 066 * 067 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 068 * @since 5.4 069 */ 070public class AliasTagHandler extends ComponentHandler { 071 072 private static final Log log = LogFactory.getLog(AliasTagHandler.class); 073 074 /** 075 * @since 6.0 076 */ 077 public static String ANCHOR_ENABLED_VARIABLE = "nuxeoAliasAnchorEnabled"; 078 079 protected final TagAttribute cache; 080 081 protected final TagAttribute id; 082 083 protected final Map<String, ValueExpression> variables; 084 085 protected final List<String> blockedPatterns; 086 087 /** 088 * @since 6.0 089 */ 090 protected final TagAttribute anchor; 091 092 public AliasTagHandler(ComponentConfig config, Map<String, ValueExpression> variables) { 093 this(config, variables, null); 094 } 095 096 /** 097 * @since 5.6 098 */ 099 public AliasTagHandler(ComponentConfig config, Map<String, ValueExpression> variables, 100 List<String> blockedPatterns) { 101 super(config); 102 id = getAttribute("id"); 103 cache = getAttribute("cache"); 104 anchor = getAttribute("anchor"); 105 this.variables = variables; 106 this.blockedPatterns = blockedPatterns; 107 } 108 109 @Override 110 public void apply(FaceletContext ctx, UIComponent parent) 111 throws IOException, FacesException, FaceletException, ELException { 112 long start = FaceletDebugTracer.start(); 113 try { 114 // make sure our parent is not null 115 if (parent == null) { 116 throw new TagException(tag, "Parent UIComponent was null"); 117 } 118 119 // handle variable expression 120 boolean cacheValue = false; 121 if (cache != null) { 122 cacheValue = cache.getBoolean(ctx); 123 } 124 125 if (isOptimizedAgain()) { 126 applyOptimized(ctx, parent, cacheValue); 127 } else { 128 applyAlias(ctx, parent, cacheValue); 129 } 130 131 } finally { 132 FaceletDebugTracer.trace(start, getTag(), "alias"); 133 } 134 } 135 136 protected boolean isOptimizedAgain() { 137 ConfigurationService cs = Framework.getService(ConfigurationService.class); 138 return !cs.isBooleanPropertyTrue("nuxeo.jsf.removeAliasOptimsReloaded"); 139 } 140 141 protected void applyOptimized(FaceletContext ctx, UIComponent parent, boolean cache) 142 throws IOException, FacesException, FaceletException, ELException { 143 VariableMapper orig = ctx.getVariableMapper(); 144 try { 145 BlockingVariableMapper vm = new BlockingVariableMapper(orig); 146 vm.setBlockedPatterns(blockedPatterns); 147 if (variables != null) { 148 for (Map.Entry<String, ValueExpression> var : variables.entrySet()) { 149 if (cache) { 150 // resolve value and put it as is in variables 151 Object res = var.getValue().getValue(ctx); 152 ValueExpression ve = ctx.getExpressionFactory().createValueExpression(res, Object.class); 153 vm.setVariable(var.getKey(), ve); 154 } else { 155 vm.setVariable(var.getKey(), var.getValue()); 156 } 157 } 158 } 159 ctx.setVariableMapper(vm); 160 nextHandler.apply(ctx, parent); 161 } finally { 162 ctx.setVariableMapper(orig); 163 } 164 } 165 166 protected void applyAlias(FaceletContext ctx, UIComponent parent, boolean cacheValue) 167 throws IOException, FacesException, FaceletException, ELException { 168 AliasVariableMapper target = new AliasVariableMapper(); 169 target.setBlockedPatterns(blockedPatterns); 170 if (variables != null) { 171 for (Map.Entry<String, ValueExpression> var : variables.entrySet()) { 172 if (cacheValue) { 173 // resolve value and put it as is in variables 174 Object res = var.getValue().getValue(ctx); 175 target.setVariable(var.getKey(), 176 ctx.getExpressionFactory().createValueExpression(res, Object.class)); 177 } else { 178 target.setVariable(var.getKey(), var.getValue()); 179 } 180 } 181 } 182 183 // generate id before applying 184 target.setId(ctx.generateUniqueId(tagId)); 185 apply(ctx, parent, target, nextHandler); 186 } 187 188 protected void apply(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, FaceletHandler nextHandler) 189 throws IOException, FacesException, FaceletException, ELException { 190 ConfigurationService configurationService = Framework.getService(ConfigurationService.class); 191 if (configurationService.isBooleanPropertyTrue("nuxeo.jsf.removeAliasOptims")) { 192 applyCompat(ctx, parent, alias, nextHandler); 193 return; 194 } 195 196 applyAliasHandler(ctx, parent, alias, nextHandler); 197 } 198 199 protected boolean isAnchored(FaceletContext ctx) { 200 if (cache != null && cache.getBoolean(ctx)) { 201 return false; 202 } 203 ExpressionFactory eFactory = ctx.getExpressionFactory(); 204 ValueExpression ve = eFactory.createValueExpression(ctx, "#{" + ANCHOR_ENABLED_VARIABLE + "}", Boolean.class); 205 if (Boolean.TRUE.equals(ve.getValue(ctx))) { 206 return true; 207 } 208 if (anchor != null) { 209 return anchor.getBoolean(ctx); 210 } 211 return false; 212 } 213 214 protected void applyAliasHandler(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, 215 FaceletHandler nextHandler) throws IOException, FacesException, FaceletException, ELException { 216 // resolve the "anchor" attribute to decide whether variable should be 217 // anchored in the tree as a UIAliasHolder 218 boolean createComponent = isAnchored(ctx); 219 if (createComponent) { 220 // start by removing component from tree if it is already there, to 221 // make sure it's recreated next 222 String id = ctx.generateUniqueId(getTagId()); 223 UIComponent c = ComponentSupport.findChildByTagId(parent, id); 224 if (c != null && c.getParent() != parent) { 225 c.getParent().getChildren().remove(c); 226 } 227 } 228 229 String id = alias.getId(); 230 VariableMapper orig = ctx.getVariableMapper(); 231 VariableMapper vm = alias.getVariableMapperForBuild(orig); 232 ctx.setVariableMapper(vm); 233 FacesContext facesContext = ctx.getFacesContext(); 234 try { 235 AliasVariableMapper.exposeAliasesToRequest(facesContext, alias); 236 if (createComponent) { 237 super.apply(ctx, parent); 238 } else { 239 nextHandler.apply(ctx, parent); 240 } 241 } finally { 242 AliasVariableMapper.removeAliasesExposedToRequest(facesContext, id); 243 ctx.setVariableMapper(orig); 244 } 245 } 246 247 /** 248 * Compatibility application of facelet handler, used to preserve behaviour while optimizing and improving variables 249 * exposure and resolution. 250 */ 251 protected void applyCompat(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, 252 FaceletHandler nextHandler) throws IOException, FacesException, FaceletException, ELException { 253 String id = alias.getId(); 254 255 VariableMapper orig = ctx.getVariableMapper(); 256 VariableMapper vm = alias.getVariableMapperForBuild(orig); 257 ctx.setVariableMapper(vm); 258 259 // create component 260 UIComponent c = ComponentSupport.findChildByTagId(parent, id); 261 boolean componentFound = false; 262 if (c != null) { 263 componentFound = true; 264 // mark all children for cleaning 265 ComponentSupport.markForDeletion(c); 266 } else { 267 c = new UIAliasHolder(); 268 269 // mark it owned by a facelet instance 270 c.getAttributes().put(ComponentSupport.MARK_CREATED, id); 271 272 // assign our unique id 273 if (this.id != null) { 274 c.setId(this.id.getValue(ctx)); 275 } else { 276 UIViewRoot root = ComponentSupport.getViewRoot(ctx, parent); 277 if (root != null) { 278 String uid = root.createUniqueId(); 279 c.setId(uid); 280 } 281 } 282 } 283 284 // update value held by component 285 ((UIAliasHolder) c).setAlias(alias); 286 287 FacesContext facesContext = ctx.getFacesContext(); 288 try { 289 AliasVariableMapper.exposeAliasesToRequest(facesContext, alias); 290 // first allow c to get populated 291 nextHandler.apply(ctx, c); 292 } finally { 293 AliasVariableMapper.removeAliasesExposedToRequest(facesContext, id); 294 ctx.setVariableMapper(orig); 295 } 296 297 // finish cleaning up orphaned children 298 if (componentFound) { 299 ComponentSupport.finalizeForDeletion(c); 300 } 301 302 // add to the tree afterwards 303 // this allows children to determine if it's 304 // been part of the tree or not yet 305 parent.getChildren().add(c); 306 } 307 308}