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