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