001/* 002 * (C) Copyright 2006-2007 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 * <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 018 * 019 * $Id: WidgetTagHandler.java 30553 2008-02-24 15:51:31Z atchertchian $ 020 */ 021 022package org.nuxeo.ecm.platform.forms.layout.facelets; 023 024import java.io.IOException; 025import java.io.Serializable; 026import java.util.ArrayList; 027import java.util.Arrays; 028import java.util.HashMap; 029import java.util.List; 030import java.util.Map; 031 032import javax.el.ELException; 033import javax.el.ExpressionFactory; 034import javax.el.ValueExpression; 035import javax.el.VariableMapper; 036import javax.faces.FacesException; 037import javax.faces.component.UIComponent; 038import javax.faces.view.facelets.FaceletContext; 039import javax.faces.view.facelets.FaceletHandler; 040import javax.faces.view.facelets.MetaRuleset; 041import javax.faces.view.facelets.MetaTagHandler; 042import javax.faces.view.facelets.TagAttribute; 043import javax.faces.view.facelets.TagConfig; 044import javax.faces.view.facelets.TagException; 045 046import org.apache.commons.logging.Log; 047import org.apache.commons.logging.LogFactory; 048import org.jboss.el.ValueExpressionLiteral; 049import org.nuxeo.ecm.platform.forms.layout.api.Widget; 050import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition; 051import org.nuxeo.ecm.platform.forms.layout.facelets.dev.DevTagHandler; 052import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager; 053import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; 054import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory; 055import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 056import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; 057import org.nuxeo.runtime.api.Framework; 058 059import com.sun.faces.facelets.el.VariableMapperWrapper; 060 061/** 062 * Widget tag handler. 063 * <p> 064 * Applies {@link WidgetTypeHandler} found for given widget, in given mode and for given value. 065 * 066 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 067 */ 068public class WidgetTagHandler extends MetaTagHandler { 069 070 private static final Log log = LogFactory.getLog(WidgetTagHandler.class); 071 072 protected final TagConfig config; 073 074 protected final TagAttribute widget; 075 076 /** 077 * @since 5.6 078 */ 079 protected final TagAttribute name; 080 081 /** 082 * @since 5.6 083 */ 084 protected final TagAttribute category; 085 086 /** 087 * @since 5.6 088 */ 089 protected final TagAttribute definition; 090 091 /** 092 * @since 5.6 093 */ 094 protected final TagAttribute mode; 095 096 /** 097 * @since 5.6 098 */ 099 protected final TagAttribute layoutName; 100 101 /** 102 * @since 5.7 103 */ 104 protected final TagAttribute resolveOnly; 105 106 protected final TagAttribute value; 107 108 protected final TagAttribute[] vars; 109 110 protected final String[] reservedVarsArray = { "id", "widget", "name", "category", "definition", "mode", 111 "layoutName", "value", "resolveOnly" }; 112 113 public WidgetTagHandler(TagConfig config) { 114 super(config); 115 this.config = config; 116 117 widget = getAttribute("widget"); 118 name = getAttribute("name"); 119 definition = getAttribute("definition"); 120 category = getAttribute("category"); 121 mode = getAttribute("mode"); 122 layoutName = getAttribute("layoutName"); 123 resolveOnly = getAttribute("resolveOnly"); 124 125 value = getAttribute("value"); 126 vars = tag.getAttributes().getAll(); 127 128 // additional checks 129 if (name == null && widget == null && definition == null) { 130 throw new TagException(this.tag, 131 "At least one of attributes 'name', 'widget' " + "or 'definition' is required"); 132 } 133 if (widget == null && (name != null || definition != null)) { 134 if (mode == null) { 135 throw new TagException(this.tag, "Attribute 'mode' is required when using attribute" 136 + " 'name' or 'definition' so that the " + "widget instance " + "can be resolved"); 137 } 138 } 139 } 140 141 /** 142 * Renders given widget resolving its {@link FaceletHandler} from {@link WebLayoutManager} configuration. 143 * <p> 144 * Variables exposed: {@link RenderVariables.globalVariables#value}, same variable suffixed with "_n" where n is the 145 * widget level, and {@link RenderVariables.globalVariables#document}. 146 */ 147 public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException { 148 long start = FaceletDebugTracer.start(); 149 Widget widgetInstance = null; 150 151 try { 152 // compute value name to set on widget instance in case it's changed 153 // from first computation 154 String valueName = null; 155 if (value != null) { 156 valueName = value.getValue(); 157 } 158 if (ComponentTagUtils.isStrictValueReference(valueName)) { 159 valueName = ComponentTagUtils.getBareValueName(valueName); 160 } 161 162 // build handler 163 boolean widgetInstanceBuilt = false; 164 if (widget != null) { 165 widgetInstance = (Widget) widget.getObject(ctx, Widget.class); 166 if (widgetInstance != null && valueName != null) { 167 widgetInstance.setValueName(valueName); 168 } 169 } else { 170 // resolve widget according to name and mode (and optional 171 // category) 172 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 173 174 String modeValue = mode.getValue(ctx); 175 String layoutNameValue = null; 176 if (layoutName != null) { 177 layoutNameValue = layoutName.getValue(ctx); 178 } 179 180 if (name != null) { 181 String nameValue = name.getValue(ctx); 182 String catValue = null; 183 if (category != null) { 184 catValue = category.getValue(ctx); 185 } 186 widgetInstance = layoutService.getWidget(ctx, nameValue, catValue, modeValue, valueName, 187 layoutNameValue); 188 widgetInstanceBuilt = true; 189 } else if (definition != null) { 190 WidgetDefinition widgetDef = (WidgetDefinition) definition.getObject(ctx, WidgetDefinition.class); 191 if (widgetDef != null) { 192 widgetInstance = layoutService.getWidget(ctx, widgetDef, modeValue, valueName, layoutNameValue); 193 widgetInstanceBuilt = true; 194 } 195 } 196 197 } 198 if (widgetInstance != null) { 199 // add additional properties put on tag 200 String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_"; 201 List<String> reservedVars = Arrays.asList(reservedVarsArray); 202 for (TagAttribute var : vars) { 203 String localName = var.getLocalName(); 204 if (!reservedVars.contains(localName)) { 205 if (localName != null && localName.startsWith(widgetPropertyMarker)) { 206 localName = localName.substring(widgetPropertyMarker.length()); 207 } 208 widgetInstance.setProperty(localName, var.getValue()); 209 } 210 } 211 212 VariableMapper orig = ctx.getVariableMapper(); 213 try { 214 if (FaceletHandlerHelper.isAliasOptimEnabled()) { 215 applyOptimized(ctx, orig, widgetInstance, widgetInstanceBuilt); 216 } else { 217 applyCompat(ctx, orig, widgetInstance, widgetInstanceBuilt); 218 } 219 220 boolean resolveOnlyBool = false; 221 if (resolveOnly != null) { 222 resolveOnlyBool = resolveOnly.getBoolean(ctx); 223 } 224 225 if (resolveOnlyBool) { 226 nextHandler.apply(ctx, parent); 227 } else { 228 applyWidgetHandler(ctx, parent, config, widgetInstance, value, true, nextHandler); 229 } 230 } finally { 231 ctx.setVariableMapper(orig); 232 } 233 } 234 } finally { 235 FaceletDebugTracer.trace(start, config.getTag(), widgetInstance == null ? null : widgetInstance.getId()); 236 } 237 } 238 239 public static void generateWidgetIdsRecursive(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget) { 240 generateWidgetId(ctx, helper, widget, true); 241 } 242 243 /** 244 * @since 7.2 245 */ 246 public static void generateWidgetId(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget, 247 boolean recursive) { 248 if (widget == null) { 249 return; 250 } 251 widget.setId(FaceletHandlerHelper.generateWidgetId(ctx, widget.getName())); 252 if (recursive) { 253 Widget[] subWidgets = widget.getSubWidgets(); 254 if (subWidgets != null) { 255 for (Widget subWidget : subWidgets) { 256 generateWidgetIdsRecursive(ctx, helper, subWidget); 257 } 258 } 259 } 260 } 261 262 protected void applyOptimized(FaceletContext ctx, VariableMapper orig, Widget widgetInstance, 263 boolean widgetInstanceBuilt) { 264 BlockingVariableMapper vm = new BlockingVariableMapper(orig); 265 ctx.setVariableMapper(vm); 266 267 if (widgetInstanceBuilt) { 268 // expose widget variable to the context as layout row has not done it already, and set unique id on 269 // widget before exposing it to the context 270 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 271 WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false); 272 exposeWidgetVariables(ctx, vm, widgetInstance, null, false); 273 } 274 275 } 276 277 protected void applyCompat(FaceletContext ctx, VariableMapper orig, Widget widgetInstance, 278 boolean widgetInstanceBuilt) { 279 if (!widgetInstanceBuilt) { 280 return; 281 } 282 // expose widget variable to the context as layout row has not done it already, and set unique id on 283 // widget before exposing it to the context 284 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 285 WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false); 286 287 VariableMapper vm = new VariableMapperWrapper(orig); 288 ctx.setVariableMapper(vm); 289 ExpressionFactory eFactory = ctx.getExpressionFactory(); 290 ValueExpression widgetVe = eFactory.createValueExpression(widgetInstance, Widget.class); 291 vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe); 292 // expose widget controls too 293 for (Map.Entry<String, Serializable> ctrl : widgetInstance.getControls().entrySet()) { 294 String key = ctrl.getKey(); 295 String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key; 296 String value = "#{" + RenderVariables.widgetVariables.widget.name() + ".controls." + key + "}"; 297 vm.setVariable(name, eFactory.createValueExpression(ctx, value, Object.class)); 298 } 299 } 300 301 public static void applyWidgetHandler(FaceletContext ctx, UIComponent parent, TagConfig config, Widget widget, 302 TagAttribute value, boolean fillVariables, FaceletHandler nextHandler) throws IOException { 303 if (widget == null) { 304 return; 305 } 306 307 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 308 309 TagConfig wtConfig = TagConfigFactory.createTagConfig(config, widget.getTagConfigId(), null, nextHandler); 310 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 311 WidgetTypeHandler handler = layoutService.getWidgetTypeHandler(wtConfig, widget); 312 313 if (handler == null) { 314 String widgetTypeName = widget.getType(); 315 String widgetTypeCategory = widget.getTypeCategory(); 316 String message = String.format("No widget handler found for type '%s' in category '%s'", widgetTypeName, 317 widgetTypeCategory); 318 log.error(message); 319 FaceletHandler h = helper.getErrorComponentHandler(null, message); 320 h.apply(ctx, parent); 321 return; 322 } 323 324 FaceletHandler fh = handler; 325 if (FaceletHandlerHelper.isDevModeEnabled(ctx)) { 326 // decorate handler with dev handler 327 FaceletHandler devHandler = handler.getDevFaceletHandler(config, widget); 328 if (devHandler != null) { 329 // expose the widget variable to sub dev handler 330 String widgetTagConfigId = widget.getTagConfigId(); 331 Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>(); 332 ExpressionFactory eFactory = ctx.getExpressionFactory(); 333 ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class); 334 variables.put(RenderVariables.widgetVariables.widget.name(), widgetVe); 335 List<String> blockedPatterns = new ArrayList<String>(); 336 blockedPatterns.add(RenderVariables.widgetVariables.widget.name() + "*"); 337 FaceletHandler devAliasHandler = helper.getAliasFaceletHandler(widgetTagConfigId, variables, 338 blockedPatterns, devHandler); 339 String refId = widget.getName(); 340 fh = new DevTagHandler(config, refId, handler, devAliasHandler); 341 } 342 } 343 344 if (FaceletHandlerHelper.isAliasOptimEnabled()) { 345 if (fillVariables) { 346 // expose widget variables 347 VariableMapper cvm = ctx.getVariableMapper(); 348 if (!(cvm instanceof BlockingVariableMapper)) { 349 throw new IllegalArgumentException( 350 "Current context variable mapper should be an instance of MetaVariableMapper"); 351 } 352 BlockingVariableMapper vm = (BlockingVariableMapper) cvm; 353 ValueExpression valueExpr; 354 if (value == null) { 355 valueExpr = new ValueExpressionLiteral(null, Object.class); 356 } else { 357 valueExpr = value.getValueExpression(ctx, Object.class); 358 } 359 360 vm.setVariable(RenderVariables.globalVariables.value.name(), valueExpr); 361 vm.setVariable(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr); 362 } 363 fh.apply(ctx, parent); 364 } else { 365 if (fillVariables) { 366 // expose widget variables 367 Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>(); 368 369 ValueExpression valueExpr; 370 if (value == null) { 371 valueExpr = new ValueExpressionLiteral(null, Object.class); 372 } else { 373 valueExpr = value.getValueExpression(ctx, Object.class); 374 } 375 376 variables.put(RenderVariables.globalVariables.value.name(), valueExpr); 377 variables.put(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr); 378 379 FaceletHandler handlerWithVars = helper.getAliasFaceletHandler(widget.getTagConfigId(), variables, null, 380 fh); 381 // apply 382 handlerWithVars.apply(ctx, parent); 383 384 } else { 385 // just apply 386 fh.apply(ctx, parent); 387 } 388 } 389 390 } 391 392 public static void exposeWidgetVariables(FaceletContext ctx, BlockingVariableMapper vm, Widget widget, 393 Integer widgetIndex, boolean exposeLevel) { 394 ExpressionFactory eFactory = ctx.getExpressionFactory(); 395 ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class); 396 vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe); 397 vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name()); 398 399 ValueExpression widgetIndexVe = null; 400 if (widgetIndex != null) { 401 widgetIndexVe = eFactory.createValueExpression(widgetIndex, Integer.class); 402 vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name(), widgetIndexVe); 403 } 404 405 if (exposeLevel && !FaceletHandlerHelper.isAliasOptimEnabled()) { 406 Integer level = null; 407 if (widget != null) { 408 level = widget.getLevel(); 409 } 410 vm.setVariable(RenderVariables.widgetVariables.widget.name() + "_" + level, widgetVe); 411 if (widgetIndexVe != null) { 412 vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name() + "_" + level, widgetIndexVe); 413 } 414 vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name() + "_*"); 415 vm.addBlockedPattern(RenderVariables.widgetVariables.widgetIndex.name() + "*"); 416 } 417 418 // expose widget controls too 419 if (widget != null) { 420 for (Map.Entry<String, Serializable> ctrl : widget.getControls().entrySet()) { 421 String key = ctrl.getKey(); 422 String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key; 423 ValueExpression ve = eFactory.createValueExpression(ctrl.getValue(), Object.class); 424 vm.setVariable(name, ve); 425 } 426 } 427 vm.addBlockedPattern(RenderVariables.widgetVariables.widgetControl.name() + "_*"); 428 } 429 430 @Override 431 @SuppressWarnings("rawtypes") 432 protected MetaRuleset createMetaRuleset(Class type) { 433 return null; 434 } 435 436}