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