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: LayoutTagHandler.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.ComponentConfig; 039import javax.faces.view.facelets.ComponentHandler; 040import javax.faces.view.facelets.FaceletContext; 041import javax.faces.view.facelets.FaceletHandler; 042import javax.faces.view.facelets.TagAttribute; 043import javax.faces.view.facelets.TagAttributes; 044import javax.faces.view.facelets.TagConfig; 045import javax.faces.view.facelets.TagException; 046import javax.faces.view.facelets.TagHandler; 047 048import org.apache.commons.lang.StringUtils; 049import org.apache.commons.logging.Log; 050import org.apache.commons.logging.LogFactory; 051import org.nuxeo.ecm.platform.forms.layout.api.Layout; 052import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition; 053import org.nuxeo.ecm.platform.forms.layout.api.Widget; 054import org.nuxeo.ecm.platform.forms.layout.facelets.dev.DevTagHandler; 055import org.nuxeo.ecm.platform.forms.layout.facelets.dev.LayoutDevTagHandler; 056import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager; 057import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; 058import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory; 059import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils; 060import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; 061import org.nuxeo.runtime.api.Framework; 062 063import com.sun.faces.facelets.el.VariableMapperWrapper; 064import com.sun.faces.facelets.tag.TagAttributeImpl; 065import com.sun.faces.facelets.tag.TagAttributesImpl; 066import com.sun.faces.facelets.tag.ui.ComponentRef; 067import com.sun.faces.facelets.tag.ui.ComponentRefHandler; 068import com.sun.faces.facelets.tag.ui.DecorateHandler; 069 070/** 071 * Layout tag handler. 072 * <p> 073 * Computes a layout in given facelet context, for given mode and value attributes. The layout can either be computed 074 * from a layout definition, or by a layout name, where the layout service will lookup the corresponding definition. 075 * <p> 076 * If a template is found for this layout, include the corresponding facelet and use facelet template features to 077 * iterate over rows and widgets. 078 * <p> 079 * Since 5.6, the layout name attribute also accepts a comma separated list of layout names. 080 * 081 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> 082 */ 083public class LayoutTagHandler extends TagHandler { 084 085 private static final Log log = LogFactory.getLog(LayoutTagHandler.class); 086 087 protected final TagConfig config; 088 089 /** 090 * The layout instance to render, instead of resolving it from a name or definition 091 * 092 * @since 5.7 093 */ 094 protected final TagAttribute layout; 095 096 protected final TagAttribute name; 097 098 /** 099 * @since 5.5. 100 */ 101 protected final TagAttribute category; 102 103 /** 104 * @since 5.4.2 105 */ 106 protected final TagAttribute definition; 107 108 protected final TagAttribute mode; 109 110 protected final TagAttribute value; 111 112 protected final TagAttribute template; 113 114 protected final TagAttribute selectedRows; 115 116 protected final TagAttribute selectedColumns; 117 118 protected final TagAttribute selectAllByDefault; 119 120 /** 121 * Parameter used to specify that layout should not be rendered, only resolved and exposed to the context. 122 * 123 * @since 5.7 124 */ 125 protected final TagAttribute resolveOnly; 126 127 protected final TagAttribute[] vars; 128 129 protected final String[] reservedVarsArray = { "id", "layout", "name", "category", "definition", "mode", "value", 130 "template", "selectedRows", "selectedColumns", "selectAllByDefault", "resolveOnly" }; 131 132 public LayoutTagHandler(TagConfig config) { 133 super(config); 134 this.config = config; 135 name = getAttribute("name"); 136 category = getAttribute("category"); 137 definition = getAttribute("definition"); 138 layout = getAttribute("layout"); 139 if (name == null && definition == null && layout == null) { 140 throw new TagException(this.tag, "At least one of attributes 'name', 'layout' or 'definition' is required"); 141 } 142 mode = getAttribute("mode"); 143 value = getRequiredAttribute("value"); 144 if (layout == null && (name != null || definition != null)) { 145 if (mode == null) { 146 throw new TagException(this.tag, "Attribute 'mode' is required when using attribute" 147 + " 'name' or 'definition' so that the layout instance can be resolved"); 148 } 149 } 150 template = getAttribute("template"); 151 selectedRows = getAttribute("selectedRows"); 152 selectedColumns = getAttribute("selectedColumns"); 153 if (selectedRows != null && selectedColumns != null) { 154 throw new TagException(this.tag, "Attributes 'selectedRows' " 155 + "and 'selectedColumns' are aliases: only one of them should be filled"); 156 } 157 selectAllByDefault = getAttribute("selectAllByDefault"); 158 resolveOnly = getAttribute("resolveOnly"); 159 vars = tag.getAttributes().getAll(); 160 } 161 162 @SuppressWarnings("unchecked") 163 // TODO: add javadoc about variables exposed 164 public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException { 165 if (!FaceletHandlerHelper.isAliasOptimEnabled()) { 166 applyCompat(ctx, parent); 167 return; 168 } 169 170 long start = FaceletDebugTracer.start(); 171 String logId = null; 172 try { 173 174 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 175 176 // add additional properties put on tag 177 Map<String, Serializable> additionalProps = new HashMap<String, Serializable>(); 178 List<String> reservedVars = Arrays.asList(reservedVarsArray); 179 for (TagAttribute var : vars) { 180 String localName = var.getLocalName(); 181 if (!reservedVars.contains(localName)) { 182 // resolve value as there's no alias value expression exposed 183 // for layout properties 184 additionalProps.put(localName, (Serializable) var.getObject(ctx)); 185 } 186 } 187 188 VariableMapper orig = ctx.getVariableMapper(); 189 190 try { 191 // expose some layout variables before layout creation so that they 192 // can be used in mode expressions 193 BlockingVariableMapper vm = new BlockingVariableMapper(orig); 194 ctx.setVariableMapper(vm); 195 196 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 197 ExpressionFactory eFactory = ctx.getExpressionFactory(); 198 Layout layoutInstance = null; 199 200 String valueName = value.getValue(); 201 if (ComponentTagUtils.isStrictValueReference(valueName)) { 202 valueName = ComponentTagUtils.getBareValueName(valueName); 203 } 204 205 String templateValue = null; 206 if (template != null) { 207 templateValue = template.getValue(ctx); 208 } 209 210 boolean resolveOnlyValue = false; 211 if (resolveOnly != null) { 212 resolveOnlyValue = resolveOnly.getBoolean(ctx); 213 } 214 215 if (layout != null) { 216 // resolve layout instance given as attribute 217 layoutInstance = (Layout) layout.getObject(ctx, Layout.class); 218 if (layoutInstance == null) { 219 String errMsg = "Layout instance not found"; 220 applyErrorHandler(ctx, parent, helper, errMsg); 221 } else { 222 fillVariablesForLayoutBuild(ctx, eFactory, vm, layoutInstance.getMode()); 223 layoutInstance.setValueName(valueName); 224 applyLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 225 additionalProps, vm, resolveOnlyValue); 226 } 227 } else { 228 // build layout instance from other attributes 229 String modeValue = mode.getValue(ctx); 230 231 List<String> selectedRowsValue = null; 232 boolean selectAllByDefaultValue = false; 233 234 fillVariablesForLayoutBuild(ctx, eFactory, vm, modeValue); 235 236 if (selectedRows != null || selectedColumns != null) { 237 if (selectedRows != null) { 238 selectedRowsValue = (List<String>) selectedRows.getObject(ctx, List.class); 239 } else if (selectedColumns != null) { 240 List<String> selectedColumnsList = (List<String>) selectedColumns.getObject(ctx, 241 List.class); 242 // Handle empty selected columns list as null to 243 // display all columns. 244 if (selectedColumnsList != null && selectedColumnsList.isEmpty()) { 245 selectedColumnsList = null; 246 } 247 selectedRowsValue = selectedColumnsList; 248 } 249 } 250 if (selectAllByDefault != null) { 251 selectAllByDefaultValue = selectAllByDefault.getBoolean(ctx); 252 } 253 254 if (name != null) { 255 String layoutCategory = null; 256 if (category != null) { 257 layoutCategory = category.getValue(ctx); 258 } 259 260 String nameValue = name.getValue(ctx); 261 List<String> layoutNames = resolveLayoutNames(nameValue); 262 logId = layoutNames.toString(); 263 for (String layoutName : layoutNames) { 264 layoutInstance = layoutService.getLayout(ctx, layoutName, layoutCategory, modeValue, 265 valueName, selectedRowsValue, selectAllByDefaultValue); 266 if (layoutInstance == null) { 267 String errMsg = "Layout '" + layoutName + "' not found"; 268 applyErrorHandler(ctx, parent, helper, errMsg); 269 } else { 270 applyLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 271 additionalProps, vm, resolveOnlyValue); 272 } 273 } 274 } 275 276 if (definition != null) { 277 LayoutDefinition layoutDef = (LayoutDefinition) definition.getObject(ctx, 278 LayoutDefinition.class); 279 280 if (layoutDef == null) { 281 String errMsg = "Layout definition resolved to null"; 282 applyErrorHandler(ctx, parent, helper, errMsg); 283 } else { 284 layoutInstance = layoutService.getLayout(ctx, layoutDef, modeValue, valueName, 285 selectedRowsValue, selectAllByDefaultValue); 286 applyLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 287 additionalProps, vm, resolveOnlyValue); 288 if (layoutInstance != null) { 289 logId = layoutInstance.getId(); 290 } else { 291 logId = layoutDef.getName() + " (def)"; 292 } 293 } 294 } 295 } 296 297 } finally { 298 // layout resolved => cleanup variable mapper 299 ctx.setVariableMapper(orig); 300 } 301 302 } finally { 303 FaceletDebugTracer.trace(start, config.getTag(), logId); 304 } 305 } 306 307 /** 308 * Resolves layouts names, splitting on character "," and trimming resulting names, and allowing empty strings if 309 * the whole string is not empty to ease up rendering of layout names using variables. 310 * <p> 311 * For instance, if value is null or empty, will return a single empty layout name "". If value is "," it will 312 * return an empty list, triggering no error for usage like <nxl:layout name="#{myLayout}, #{myOtherLayout}" [...] 313 * /> 314 */ 315 protected List<String> resolveLayoutNames(String nameValue) { 316 List<String> res = new ArrayList<String>(); 317 if (nameValue != null) { 318 String[] split = nameValue.split(",|\\s"); 319 if (split != null) { 320 for (String item : split) { 321 if (!StringUtils.isBlank(item)) { 322 res.add(item.trim()); 323 } 324 } 325 } 326 } 327 return res; 328 } 329 330 protected void applyLayoutHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 331 WebLayoutManager layoutService, Layout layoutInstance, String templateValue, 332 Map<String, Serializable> additionalProps, BlockingVariableMapper vm, boolean resolveOnly) 333 throws IOException, FacesException, ELException { 334 335 // set unique id on layout, unless layout is only resolved 336 if (!resolveOnly) { 337 layoutInstance.setId(FaceletHandlerHelper.generateLayoutId(ctx, layoutInstance.getName())); 338 } 339 340 // add additional properties put on tag 341 Map<String, Serializable> layoutProps = layoutInstance.getProperties(); 342 if (additionalProps != null && !additionalProps.isEmpty()) { 343 for (Map.Entry<String, Serializable> entry : additionalProps.entrySet()) { 344 // XXX: do not override with empty property values if already 345 // set on the layout properties 346 String key = entry.getKey(); 347 Serializable value = entry.getValue(); 348 if (layoutProps.containsKey(key) 349 && (value == null || ((value instanceof String) && StringUtils.isBlank((String) value)))) { 350 // do not override property on layout 351 if (log.isDebugEnabled()) { 352 log.debug(String.format( 353 "Do not override property '%s' with " + "empty value on layout named '%s'", key, 354 layoutInstance.getName())); 355 } 356 } else { 357 layoutInstance.setProperty(key, value); 358 } 359 } 360 } 361 362 if (StringUtils.isBlank(templateValue)) { 363 templateValue = layoutInstance.getTemplate(); 364 } 365 366 if (!resolveOnly) { 367 boolean scaffold = Boolean.parseBoolean(String.valueOf(layoutInstance.getProperty("scaffold"))); 368 if (scaffold) { 369 // generate ids on widgets 370 Map<String, Widget> widgetMap = layoutInstance.getWidgetMap(); 371 if (widgetMap != null) { 372 for (Widget widget : widgetMap.values()) { 373 if (widget != null && (widget.getId() == null)) { 374 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 375 } 376 } 377 } 378 } 379 } 380 381 // expose rendering variables 382 fillVariablesForLayoutRendering(ctx, ctx.getExpressionFactory(), layoutService, vm, layoutInstance); 383 384 final String layoutTagConfigId = layoutInstance.getTagConfigId(); 385 386 if (resolveOnly) { 387 nextHandler.apply(ctx, parent); 388 } else { 389 if (!StringUtils.isBlank(templateValue)) { 390 TagAttribute srcAttr = helper.createAttribute("template", templateValue); 391 TagConfig config = TagConfigFactory.createTagConfig(this.config, layoutTagConfigId, 392 FaceletHandlerHelper.getTagAttributes(srcAttr), nextHandler); 393 FaceletHandler templateHandler = new DecorateHandler(config); 394 // NXP-18639: always wrap next include handler in a component ref for tagConfigId to be taken into 395 // account and anchored in the view with this id. 396 ComponentConfig ref = TagConfigFactory.createComponentConfig(this.config, layoutTagConfigId, 397 new TagAttributesImpl(new TagAttributeImpl[] {}), templateHandler, ComponentRef.COMPONENT_TYPE, 398 null); 399 FaceletHandler includeHandler = new ComponentRefHandler(ref); 400 if (FaceletHandlerHelper.isDevModeEnabled(ctx)) { 401 // decorate handler with dev handler 402 FaceletHandler devHandler = getDevFaceletHandler(ctx, helper, config, layoutInstance); 403 FaceletHandler nextHandler; 404 if (devHandler == null) { 405 nextHandler = includeHandler; 406 } else { 407 nextHandler = new DevTagHandler(config, layoutInstance.getName(), includeHandler, devHandler); 408 } 409 nextHandler.apply(ctx, parent); 410 } else { 411 includeHandler.apply(ctx, parent); 412 } 413 } else { 414 String errMsg = "Missing template property for layout '" + layoutInstance.getName() + "'"; 415 applyErrorHandler(ctx, parent, helper, errMsg); 416 } 417 } 418 } 419 420 protected void fillVariablesForLayoutBuild(FaceletContext ctx, ExpressionFactory eFactory, 421 BlockingVariableMapper vm, String modeValue) { 422 ValueExpression valueExpr = value.getValueExpression(ctx, Object.class); 423 vm.setVariable(RenderVariables.globalVariables.value.name(), valueExpr); 424 vm.setVariable(RenderVariables.globalVariables.layoutValue.name(), valueExpr); 425 ValueExpression modeVe = eFactory.createValueExpression(modeValue, String.class); 426 vm.setVariable(RenderVariables.globalVariables.layoutMode.name(), modeVe); 427 // mode as alias to layoutMode 428 vm.setVariable(RenderVariables.globalVariables.mode.name(), modeVe); 429 } 430 431 /** 432 * Computes variables for rendering, making available the layout instance and its properties to the context. 433 */ 434 protected void fillVariablesForLayoutRendering(FaceletContext ctx, ExpressionFactory eFactory, 435 WebLayoutManager layoutService, BlockingVariableMapper vm, Layout layoutInstance) { 436 // expose layout value 437 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 438 vm.setVariable(RenderVariables.layoutVariables.layout.name(), layoutVe); 439 vm.addBlockedPattern(RenderVariables.layoutVariables.layout.name()); 440 441 // expose layout properties too 442 for (Map.Entry<String, Serializable> prop : layoutInstance.getProperties().entrySet()) { 443 String key = prop.getKey(); 444 String name = RenderVariables.layoutVariables.layoutProperty.name() + "_" + key; 445 vm.setVariable(name, eFactory.createValueExpression(prop.getValue(), Object.class)); 446 } 447 vm.addBlockedPattern(RenderVariables.layoutVariables.layoutProperty.name() + "_*"); 448 449 // expose layout row count for row variables reference 450 Integer rowCount = null; 451 if (layoutInstance.getRows() != null) { 452 rowCount = layoutInstance.getRows().length; 453 } 454 vm.setVariable(RenderVariables.layoutVariables.layoutRowCount.name(), 455 eFactory.createValueExpression(rowCount, Integer.class)); 456 vm.addBlockedPattern(RenderVariables.layoutVariables.layoutRowCount.name()); 457 } 458 459 protected void applyErrorHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 460 String message) throws IOException { 461 log.error(message); 462 ComponentHandler output = helper.getErrorComponentHandler(null, message); 463 output.apply(ctx, parent); 464 } 465 466 protected FaceletHandler getDevFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, TagConfig config, 467 Layout layout) { 468 if (StringUtils.isBlank(layout.getDevTemplate())) { 469 return null; 470 } 471 // use the default dev handler for widget types 472 TagAttribute attr = helper.createAttribute("layout", 473 "#{" + RenderVariables.layoutVariables.layout.name() + "}"); 474 TagAttributes devWidgetAttributes = FaceletHandlerHelper.getTagAttributes(attr); 475 TagConfig devWidgetConfig = TagConfigFactory.createTagConfig(config, layout.getTagConfigId(), 476 devWidgetAttributes, new org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler()); 477 return new LayoutDevTagHandler(devWidgetConfig); 478 } 479 480 /** 481 * Compatibility methods 482 */ 483 484 @SuppressWarnings("unchecked") 485 public void applyCompat(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException { 486 long start = FaceletDebugTracer.start(); 487 String logId = null; 488 try { 489 490 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 491 492 // add additional properties put on tag 493 Map<String, Serializable> additionalProps = new HashMap<String, Serializable>(); 494 List<String> reservedVars = Arrays.asList(reservedVarsArray); 495 for (TagAttribute var : vars) { 496 String localName = var.getLocalName(); 497 if (!reservedVars.contains(localName)) { 498 // resolve value as there's no alias value expression exposed 499 // for layout properties 500 additionalProps.put(localName, (Serializable) var.getObject(ctx)); 501 } 502 } 503 504 // expose some layout variables before layout creation so that they 505 // can be used in mode expressions 506 VariableMapper orig = ctx.getVariableMapper(); 507 508 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 509 try { 510 VariableMapper vm = new VariableMapperWrapper(orig); 511 ctx.setVariableMapper(vm); 512 513 Layout layoutInstance = null; 514 515 String valueName = value.getValue(); 516 if (ComponentTagUtils.isStrictValueReference(valueName)) { 517 valueName = ComponentTagUtils.getBareValueName(valueName); 518 } 519 520 String templateValue = null; 521 if (template != null) { 522 templateValue = template.getValue(ctx); 523 } 524 525 boolean resolveOnlyValue = false; 526 if (resolveOnly != null) { 527 resolveOnlyValue = resolveOnly.getBoolean(ctx); 528 } 529 530 if (layout != null) { 531 // resolve layout instance given as attribute 532 layoutInstance = (Layout) layout.getObject(ctx, Layout.class); 533 if (layoutInstance == null) { 534 String errMsg = "Layout instance not found"; 535 applyErrorHandler(ctx, parent, helper, errMsg); 536 } else { 537 Map<String, ValueExpression> vars = getVariablesForLayoutBuild(ctx, layoutInstance.getMode()); 538 for (Map.Entry<String, ValueExpression> var : vars.entrySet()) { 539 vm.setVariable(var.getKey(), var.getValue()); 540 } 541 layoutInstance.setValueName(valueName); 542 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 543 additionalProps, vars, resolveOnlyValue); 544 } 545 } else { 546 // build layout instance from other attributes 547 String modeValue = mode.getValue(ctx); 548 549 List<String> selectedRowsValue = null; 550 boolean selectAllByDefaultValue = false; 551 552 Map<String, ValueExpression> vars = getVariablesForLayoutBuild(ctx, modeValue); 553 for (Map.Entry<String, ValueExpression> var : vars.entrySet()) { 554 vm.setVariable(var.getKey(), var.getValue()); 555 } 556 557 if (selectedRows != null || selectedColumns != null) { 558 if (selectedRows != null) { 559 selectedRowsValue = (List<String>) selectedRows.getObject(ctx, List.class); 560 } else if (selectedColumns != null) { 561 List<String> selectedColumnsList = (List<String>) selectedColumns.getObject(ctx, 562 List.class); 563 // Handle empty selected columns list as null to 564 // display all columns. 565 if (selectedColumnsList != null && selectedColumnsList.isEmpty()) { 566 selectedColumnsList = null; 567 } 568 selectedRowsValue = selectedColumnsList; 569 } 570 } 571 if (selectAllByDefault != null) { 572 selectAllByDefaultValue = selectAllByDefault.getBoolean(ctx); 573 } 574 575 if (name != null) { 576 String layoutCategory = null; 577 if (category != null) { 578 layoutCategory = category.getValue(ctx); 579 } 580 581 String nameValue = name.getValue(ctx); 582 List<String> layoutNames = resolveLayoutNames(nameValue); 583 logId = layoutNames.toString(); 584 for (String layoutName : layoutNames) { 585 layoutInstance = layoutService.getLayout(ctx, layoutName, layoutCategory, modeValue, 586 valueName, selectedRowsValue, selectAllByDefaultValue); 587 if (layoutInstance == null) { 588 String errMsg = "Layout '" + layoutName + "' not found"; 589 applyErrorHandler(ctx, parent, helper, errMsg); 590 } else { 591 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, 592 templateValue, additionalProps, vars, resolveOnlyValue); 593 } 594 } 595 } 596 597 if (definition != null) { 598 LayoutDefinition layoutDef = (LayoutDefinition) definition.getObject(ctx, 599 LayoutDefinition.class); 600 601 if (layoutDef == null) { 602 String errMsg = "Layout definition resolved to null"; 603 applyErrorHandler(ctx, parent, helper, errMsg); 604 } else { 605 layoutInstance = layoutService.getLayout(ctx, layoutDef, modeValue, valueName, 606 selectedRowsValue, selectAllByDefaultValue); 607 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 608 additionalProps, vars, resolveOnlyValue); 609 if (layoutInstance != null) { 610 logId = layoutInstance.getId(); 611 } else { 612 logId = layoutDef.getName() + " (def)"; 613 } 614 } 615 } 616 } 617 618 } finally { 619 // layout resolved => cleanup variable mapper 620 ctx.setVariableMapper(orig); 621 } 622 623 } finally { 624 FaceletDebugTracer.trace(start, config.getTag(), logId); 625 } 626 } 627 628 protected void applyCompatLayoutHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 629 WebLayoutManager layoutService, Layout layoutInstance, String templateValue, 630 Map<String, Serializable> additionalProps, Map<String, ValueExpression> vars, boolean resolveOnly) 631 throws IOException, FacesException, ELException { 632 633 // set unique id on layout, unless layout is only resolved 634 if (!resolveOnly) { 635 layoutInstance.setId(FaceletHandlerHelper.generateLayoutId(ctx, layoutInstance.getName())); 636 } 637 638 // add additional properties put on tag 639 Map<String, Serializable> layoutProps = layoutInstance.getProperties(); 640 if (additionalProps != null && !additionalProps.isEmpty()) { 641 for (Map.Entry<String, Serializable> entry : additionalProps.entrySet()) { 642 // XXX: do not override with empty property values if already 643 // set on the layout properties 644 String key = entry.getKey(); 645 Serializable value = entry.getValue(); 646 if (layoutProps.containsKey(key) 647 && (value == null || ((value instanceof String) && StringUtils.isBlank((String) value)))) { 648 // do not override property on layout 649 if (log.isDebugEnabled()) { 650 log.debug(String.format( 651 "Do not override property '%s' with " + "empty value on layout named '%s'", key, 652 layoutInstance.getName())); 653 } 654 } else { 655 layoutInstance.setProperty(key, value); 656 } 657 } 658 } 659 660 if (StringUtils.isBlank(templateValue)) { 661 templateValue = layoutInstance.getTemplate(); 662 } 663 664 if (!resolveOnly) { 665 boolean scaffold = Boolean.parseBoolean(String.valueOf(layoutInstance.getProperty("scaffold"))); 666 if (scaffold) { 667 // generate ids on widgets 668 Map<String, Widget> widgetMap = layoutInstance.getWidgetMap(); 669 if (widgetMap != null) { 670 for (Widget widget : widgetMap.values()) { 671 if (widget != null && (widget.getId() == null)) { 672 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 673 } 674 } 675 } 676 } 677 } 678 679 // expose layout instance to variable mapper to ensure good 680 // resolution of properties 681 ExpressionFactory eFactory = ctx.getExpressionFactory(); 682 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 683 ctx.getVariableMapper().setVariable(RenderVariables.layoutVariables.layout.name(), layoutVe); 684 685 // expose all variables through an alias tag handler 686 vars.putAll(getVariablesForLayoutRendering(ctx, layoutService, layoutInstance)); 687 688 List<String> blockedPatterns = new ArrayList<String>(); 689 blockedPatterns.add(RenderVariables.layoutVariables.layout.name()); 690 blockedPatterns.add(RenderVariables.layoutVariables.layoutProperty.name() + "_*"); 691 692 final String layoutTagConfigId = layoutInstance.getTagConfigId(); 693 694 if (resolveOnly) { 695 FaceletHandler handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, 696 nextHandler); 697 // apply 698 handler.apply(ctx, parent); 699 } else { 700 if (!StringUtils.isBlank(templateValue)) { 701 TagAttribute srcAttr = helper.createAttribute("template", templateValue); 702 TagConfig config = TagConfigFactory.createTagConfig(this.config, layoutTagConfigId, 703 FaceletHandlerHelper.getTagAttributes(srcAttr), nextHandler); 704 FaceletHandler includeHandler = new DecorateHandler(config); 705 FaceletHandler handler; 706 if (FaceletHandlerHelper.isDevModeEnabled(ctx)) { 707 // decorate handler with dev handler 708 FaceletHandler devHandler = getDevFaceletHandler(ctx, helper, config, layoutInstance); 709 FaceletHandler nextHandler; 710 if (devHandler == null) { 711 nextHandler = includeHandler; 712 } else { 713 nextHandler = new DevTagHandler(config, layoutInstance.getName(), includeHandler, devHandler); 714 } 715 handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, nextHandler); 716 } else { 717 handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, includeHandler); 718 } 719 // apply 720 handler.apply(ctx, parent); 721 } else { 722 String errMsg = "Missing template property for layout '" + layoutInstance.getName() + "'"; 723 applyErrorHandler(ctx, parent, helper, errMsg); 724 } 725 } 726 } 727 728 protected Map<String, ValueExpression> getVariablesForLayoutBuild(FaceletContext ctx, String modeValue) { 729 Map<String, ValueExpression> vars = new HashMap<String, ValueExpression>(); 730 ValueExpression valueExpr = value.getValueExpression(ctx, Object.class); 731 vars.put(RenderVariables.globalVariables.value.name(), valueExpr); 732 // vars.put(RenderVariables.globalVariables.document.name(), 733 // valueExpr); 734 vars.put(RenderVariables.globalVariables.layoutValue.name(), valueExpr); 735 ExpressionFactory eFactory = ctx.getExpressionFactory(); 736 ValueExpression modeVe = eFactory.createValueExpression(modeValue, String.class); 737 vars.put(RenderVariables.globalVariables.layoutMode.name(), modeVe); 738 // mode as alias to layoutMode 739 vars.put(RenderVariables.globalVariables.mode.name(), modeVe); 740 return vars; 741 } 742 743 /** 744 * Computes variables for rendering, making available the layout instance and its properties to the context. 745 */ 746 protected Map<String, ValueExpression> getVariablesForLayoutRendering(FaceletContext ctx, 747 WebLayoutManager layoutService, Layout layoutInstance) { 748 Map<String, ValueExpression> vars = new HashMap<String, ValueExpression>(); 749 ExpressionFactory eFactory = ctx.getExpressionFactory(); 750 751 // expose layout value 752 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 753 vars.put(RenderVariables.layoutVariables.layout.name(), layoutVe); 754 755 // expose layout properties too 756 for (Map.Entry<String, Serializable> prop : layoutInstance.getProperties().entrySet()) { 757 String key = prop.getKey(); 758 String name = RenderVariables.layoutVariables.layoutProperty.name() + "_" + key; 759 String value; 760 Serializable valueInstance = prop.getValue(); 761 if (!layoutService.referencePropertyAsExpression(key, valueInstance, null, null, null, null)) { 762 // FIXME: this will not be updated correctly using ajax 763 value = (String) valueInstance; 764 } else { 765 // create a reference so that it's a real expression and it's 766 // not kept (cached) in a component value on ajax refresh 767 value = "#{" + RenderVariables.layoutVariables.layout.name() + ".properties." + key + "}"; 768 } 769 vars.put(name, eFactory.createValueExpression(ctx, value, Object.class)); 770 } 771 772 return vars; 773 } 774 775 /** 776 * End of compatibility methods 777 */ 778 779}