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.lang3.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 logId = layoutInstance.getId(); 289 } 290 } 291 } 292 293 } finally { 294 // layout resolved => cleanup variable mapper 295 ctx.setVariableMapper(orig); 296 } 297 298 } finally { 299 FaceletDebugTracer.trace(start, config.getTag(), logId); 300 } 301 } 302 303 /** 304 * Resolves layouts names, splitting on character "," and trimming resulting names, and allowing empty strings if 305 * the whole string is not empty to ease up rendering of layout names using variables. 306 * <p> 307 * For instance, if value is null or empty, will return a single empty layout name "". If value is "," it will 308 * return an empty list, triggering no error for usage like <nxl:layout name="#{myLayout}, #{myOtherLayout}" [...] 309 * /> 310 */ 311 protected List<String> resolveLayoutNames(String nameValue) { 312 List<String> res = new ArrayList<String>(); 313 if (nameValue != null) { 314 String[] split = nameValue.split(",|\\s"); 315 if (split != null) { 316 for (String item : split) { 317 if (!StringUtils.isBlank(item)) { 318 res.add(item.trim()); 319 } 320 } 321 } 322 } 323 return res; 324 } 325 326 protected void applyLayoutHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 327 WebLayoutManager layoutService, Layout layoutInstance, String templateValue, 328 Map<String, Serializable> additionalProps, BlockingVariableMapper vm, boolean resolveOnly) 329 throws IOException, FacesException, ELException { 330 331 // set unique id on layout, unless layout is only resolved 332 if (!resolveOnly) { 333 layoutInstance.setId(FaceletHandlerHelper.generateLayoutId(ctx, layoutInstance.getName())); 334 } 335 336 // add additional properties put on tag 337 Map<String, Serializable> layoutProps = layoutInstance.getProperties(); 338 if (additionalProps != null && !additionalProps.isEmpty()) { 339 for (Map.Entry<String, Serializable> entry : additionalProps.entrySet()) { 340 // XXX: do not override with empty property values if already 341 // set on the layout properties 342 String key = entry.getKey(); 343 Serializable value = entry.getValue(); 344 if (layoutProps.containsKey(key) 345 && (value == null || ((value instanceof String) && StringUtils.isBlank((String) value)))) { 346 // do not override property on layout 347 if (log.isDebugEnabled()) { 348 log.debug(String.format( 349 "Do not override property '%s' with " + "empty value on layout named '%s'", key, 350 layoutInstance.getName())); 351 } 352 } else { 353 layoutInstance.setProperty(key, value); 354 } 355 } 356 } 357 358 if (StringUtils.isBlank(templateValue)) { 359 templateValue = layoutInstance.getTemplate(); 360 } 361 362 if (!resolveOnly) { 363 boolean scaffold = Boolean.parseBoolean(String.valueOf(layoutInstance.getProperty("scaffold"))); 364 if (scaffold) { 365 // generate ids on widgets 366 Map<String, Widget> widgetMap = layoutInstance.getWidgetMap(); 367 if (widgetMap != null) { 368 for (Widget widget : widgetMap.values()) { 369 if (widget != null && (widget.getId() == null)) { 370 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 371 } 372 } 373 } 374 } 375 } 376 377 // expose rendering variables 378 fillVariablesForLayoutRendering(ctx, ctx.getExpressionFactory(), layoutService, vm, layoutInstance); 379 380 final String layoutTagConfigId = layoutInstance.getTagConfigId(); 381 382 if (resolveOnly) { 383 nextHandler.apply(ctx, parent); 384 } else { 385 if (!StringUtils.isBlank(templateValue)) { 386 TagAttribute srcAttr = helper.createAttribute("template", templateValue); 387 TagConfig config = TagConfigFactory.createTagConfig(this.config, layoutTagConfigId, 388 FaceletHandlerHelper.getTagAttributes(srcAttr), nextHandler); 389 FaceletHandler templateHandler = new DecorateHandler(config); 390 // NXP-18639: always wrap next include handler in a component ref for tagConfigId to be taken into 391 // account and anchored in the view with this id. 392 ComponentConfig ref = TagConfigFactory.createComponentConfig(this.config, layoutTagConfigId, 393 new TagAttributesImpl(new TagAttributeImpl[] {}), templateHandler, ComponentRef.COMPONENT_TYPE, 394 null); 395 FaceletHandler includeHandler = new ComponentRefHandler(ref); 396 if (FaceletHandlerHelper.isDevModeEnabled(ctx)) { 397 // decorate handler with dev handler 398 FaceletHandler devHandler = getDevFaceletHandler(ctx, helper, config, layoutInstance); 399 FaceletHandler nextHandler; 400 if (devHandler == null) { 401 nextHandler = includeHandler; 402 } else { 403 nextHandler = new DevTagHandler(config, layoutInstance.getName(), includeHandler, devHandler); 404 } 405 nextHandler.apply(ctx, parent); 406 } else { 407 includeHandler.apply(ctx, parent); 408 } 409 } else { 410 String errMsg = "Missing template property for layout '" + layoutInstance.getName() + "'"; 411 applyErrorHandler(ctx, parent, helper, errMsg); 412 } 413 } 414 } 415 416 protected void fillVariablesForLayoutBuild(FaceletContext ctx, ExpressionFactory eFactory, 417 BlockingVariableMapper vm, String modeValue) { 418 ValueExpression valueExpr = value.getValueExpression(ctx, Object.class); 419 vm.setVariable(RenderVariables.globalVariables.value.name(), valueExpr); 420 vm.setVariable(RenderVariables.globalVariables.layoutValue.name(), valueExpr); 421 ValueExpression modeVe = eFactory.createValueExpression(modeValue, String.class); 422 vm.setVariable(RenderVariables.globalVariables.layoutMode.name(), modeVe); 423 // mode as alias to layoutMode 424 vm.setVariable(RenderVariables.globalVariables.mode.name(), modeVe); 425 } 426 427 /** 428 * Computes variables for rendering, making available the layout instance and its properties to the context. 429 */ 430 protected void fillVariablesForLayoutRendering(FaceletContext ctx, ExpressionFactory eFactory, 431 WebLayoutManager layoutService, BlockingVariableMapper vm, Layout layoutInstance) { 432 // expose layout value 433 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 434 vm.setVariable(RenderVariables.layoutVariables.layout.name(), layoutVe); 435 vm.addBlockedPattern(RenderVariables.layoutVariables.layout.name()); 436 437 // expose layout properties too 438 for (Map.Entry<String, Serializable> prop : layoutInstance.getProperties().entrySet()) { 439 String key = prop.getKey(); 440 String name = RenderVariables.layoutVariables.layoutProperty.name() + "_" + key; 441 vm.setVariable(name, eFactory.createValueExpression(prop.getValue(), Object.class)); 442 } 443 vm.addBlockedPattern(RenderVariables.layoutVariables.layoutProperty.name() + "_*"); 444 445 // expose layout row count for row variables reference 446 Integer rowCount = null; 447 if (layoutInstance.getRows() != null) { 448 rowCount = layoutInstance.getRows().length; 449 } 450 vm.setVariable(RenderVariables.layoutVariables.layoutRowCount.name(), 451 eFactory.createValueExpression(rowCount, Integer.class)); 452 vm.addBlockedPattern(RenderVariables.layoutVariables.layoutRowCount.name()); 453 } 454 455 protected void applyErrorHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 456 String message) throws IOException { 457 log.error(message); 458 ComponentHandler output = helper.getErrorComponentHandler(null, message); 459 output.apply(ctx, parent); 460 } 461 462 protected FaceletHandler getDevFaceletHandler(FaceletContext ctx, FaceletHandlerHelper helper, TagConfig config, 463 Layout layout) { 464 if (StringUtils.isBlank(layout.getDevTemplate())) { 465 return null; 466 } 467 // use the default dev handler for widget types 468 TagAttribute attr = helper.createAttribute("layout", 469 "#{" + RenderVariables.layoutVariables.layout.name() + "}"); 470 TagAttributes devWidgetAttributes = FaceletHandlerHelper.getTagAttributes(attr); 471 TagConfig devWidgetConfig = TagConfigFactory.createTagConfig(config, layout.getTagConfigId(), 472 devWidgetAttributes, new org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler()); 473 return new LayoutDevTagHandler(devWidgetConfig); 474 } 475 476 /** 477 * Compatibility methods 478 */ 479 480 @SuppressWarnings("unchecked") 481 public void applyCompat(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException { 482 long start = FaceletDebugTracer.start(); 483 String logId = null; 484 try { 485 486 WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class); 487 488 // add additional properties put on tag 489 Map<String, Serializable> additionalProps = new HashMap<String, Serializable>(); 490 List<String> reservedVars = Arrays.asList(reservedVarsArray); 491 for (TagAttribute var : vars) { 492 String localName = var.getLocalName(); 493 if (!reservedVars.contains(localName)) { 494 // resolve value as there's no alias value expression exposed 495 // for layout properties 496 additionalProps.put(localName, (Serializable) var.getObject(ctx)); 497 } 498 } 499 500 // expose some layout variables before layout creation so that they 501 // can be used in mode expressions 502 VariableMapper orig = ctx.getVariableMapper(); 503 504 FaceletHandlerHelper helper = new FaceletHandlerHelper(config); 505 try { 506 VariableMapper vm = new VariableMapperWrapper(orig); 507 ctx.setVariableMapper(vm); 508 509 Layout layoutInstance = null; 510 511 String valueName = value.getValue(); 512 if (ComponentTagUtils.isStrictValueReference(valueName)) { 513 valueName = ComponentTagUtils.getBareValueName(valueName); 514 } 515 516 String templateValue = null; 517 if (template != null) { 518 templateValue = template.getValue(ctx); 519 } 520 521 boolean resolveOnlyValue = false; 522 if (resolveOnly != null) { 523 resolveOnlyValue = resolveOnly.getBoolean(ctx); 524 } 525 526 if (layout != null) { 527 // resolve layout instance given as attribute 528 layoutInstance = (Layout) layout.getObject(ctx, Layout.class); 529 if (layoutInstance == null) { 530 String errMsg = "Layout instance not found"; 531 applyErrorHandler(ctx, parent, helper, errMsg); 532 } else { 533 Map<String, ValueExpression> vars = getVariablesForLayoutBuild(ctx, layoutInstance.getMode()); 534 for (Map.Entry<String, ValueExpression> var : vars.entrySet()) { 535 vm.setVariable(var.getKey(), var.getValue()); 536 } 537 layoutInstance.setValueName(valueName); 538 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 539 additionalProps, vars, resolveOnlyValue); 540 } 541 } else { 542 // build layout instance from other attributes 543 String modeValue = mode.getValue(ctx); 544 545 List<String> selectedRowsValue = null; 546 boolean selectAllByDefaultValue = false; 547 548 Map<String, ValueExpression> vars = getVariablesForLayoutBuild(ctx, modeValue); 549 for (Map.Entry<String, ValueExpression> var : vars.entrySet()) { 550 vm.setVariable(var.getKey(), var.getValue()); 551 } 552 553 if (selectedRows != null || selectedColumns != null) { 554 if (selectedRows != null) { 555 selectedRowsValue = (List<String>) selectedRows.getObject(ctx, List.class); 556 } else if (selectedColumns != null) { 557 List<String> selectedColumnsList = (List<String>) selectedColumns.getObject(ctx, 558 List.class); 559 // Handle empty selected columns list as null to 560 // display all columns. 561 if (selectedColumnsList != null && selectedColumnsList.isEmpty()) { 562 selectedColumnsList = null; 563 } 564 selectedRowsValue = selectedColumnsList; 565 } 566 } 567 if (selectAllByDefault != null) { 568 selectAllByDefaultValue = selectAllByDefault.getBoolean(ctx); 569 } 570 571 if (name != null) { 572 String layoutCategory = null; 573 if (category != null) { 574 layoutCategory = category.getValue(ctx); 575 } 576 577 String nameValue = name.getValue(ctx); 578 List<String> layoutNames = resolveLayoutNames(nameValue); 579 logId = layoutNames.toString(); 580 for (String layoutName : layoutNames) { 581 layoutInstance = layoutService.getLayout(ctx, layoutName, layoutCategory, modeValue, 582 valueName, selectedRowsValue, selectAllByDefaultValue); 583 if (layoutInstance == null) { 584 String errMsg = "Layout '" + layoutName + "' not found"; 585 applyErrorHandler(ctx, parent, helper, errMsg); 586 } else { 587 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, 588 templateValue, additionalProps, vars, resolveOnlyValue); 589 } 590 } 591 } 592 593 if (definition != null) { 594 LayoutDefinition layoutDef = (LayoutDefinition) definition.getObject(ctx, 595 LayoutDefinition.class); 596 597 if (layoutDef == null) { 598 String errMsg = "Layout definition resolved to null"; 599 applyErrorHandler(ctx, parent, helper, errMsg); 600 } else { 601 layoutInstance = layoutService.getLayout(ctx, layoutDef, modeValue, valueName, 602 selectedRowsValue, selectAllByDefaultValue); 603 applyCompatLayoutHandler(ctx, parent, helper, layoutService, layoutInstance, templateValue, 604 additionalProps, vars, resolveOnlyValue); 605 logId = layoutInstance.getId(); 606 } 607 } 608 } 609 610 } finally { 611 // layout resolved => cleanup variable mapper 612 ctx.setVariableMapper(orig); 613 } 614 615 } finally { 616 FaceletDebugTracer.trace(start, config.getTag(), logId); 617 } 618 } 619 620 protected void applyCompatLayoutHandler(FaceletContext ctx, UIComponent parent, FaceletHandlerHelper helper, 621 WebLayoutManager layoutService, Layout layoutInstance, String templateValue, 622 Map<String, Serializable> additionalProps, Map<String, ValueExpression> vars, boolean resolveOnly) 623 throws IOException, FacesException, ELException { 624 625 // set unique id on layout, unless layout is only resolved 626 if (!resolveOnly) { 627 layoutInstance.setId(FaceletHandlerHelper.generateLayoutId(ctx, layoutInstance.getName())); 628 } 629 630 // add additional properties put on tag 631 Map<String, Serializable> layoutProps = layoutInstance.getProperties(); 632 if (additionalProps != null && !additionalProps.isEmpty()) { 633 for (Map.Entry<String, Serializable> entry : additionalProps.entrySet()) { 634 // XXX: do not override with empty property values if already 635 // set on the layout properties 636 String key = entry.getKey(); 637 Serializable value = entry.getValue(); 638 if (layoutProps.containsKey(key) 639 && (value == null || ((value instanceof String) && StringUtils.isBlank((String) value)))) { 640 // do not override property on layout 641 if (log.isDebugEnabled()) { 642 log.debug(String.format( 643 "Do not override property '%s' with " + "empty value on layout named '%s'", key, 644 layoutInstance.getName())); 645 } 646 } else { 647 layoutInstance.setProperty(key, value); 648 } 649 } 650 } 651 652 if (StringUtils.isBlank(templateValue)) { 653 templateValue = layoutInstance.getTemplate(); 654 } 655 656 if (!resolveOnly) { 657 boolean scaffold = Boolean.parseBoolean(String.valueOf(layoutInstance.getProperty("scaffold"))); 658 if (scaffold) { 659 // generate ids on widgets 660 Map<String, Widget> widgetMap = layoutInstance.getWidgetMap(); 661 if (widgetMap != null) { 662 for (Widget widget : widgetMap.values()) { 663 if (widget != null && (widget.getId() == null)) { 664 WidgetTagHandler.generateWidgetId(ctx, helper, widget, false); 665 } 666 } 667 } 668 } 669 } 670 671 // expose layout instance to variable mapper to ensure good 672 // resolution of properties 673 ExpressionFactory eFactory = ctx.getExpressionFactory(); 674 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 675 ctx.getVariableMapper().setVariable(RenderVariables.layoutVariables.layout.name(), layoutVe); 676 677 // expose all variables through an alias tag handler 678 vars.putAll(getVariablesForLayoutRendering(ctx, layoutService, layoutInstance)); 679 680 List<String> blockedPatterns = new ArrayList<String>(); 681 blockedPatterns.add(RenderVariables.layoutVariables.layout.name()); 682 blockedPatterns.add(RenderVariables.layoutVariables.layoutProperty.name() + "_*"); 683 684 final String layoutTagConfigId = layoutInstance.getTagConfigId(); 685 686 if (resolveOnly) { 687 FaceletHandler handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, 688 nextHandler); 689 // apply 690 handler.apply(ctx, parent); 691 } else { 692 if (!StringUtils.isBlank(templateValue)) { 693 TagAttribute srcAttr = helper.createAttribute("template", templateValue); 694 TagConfig config = TagConfigFactory.createTagConfig(this.config, layoutTagConfigId, 695 FaceletHandlerHelper.getTagAttributes(srcAttr), nextHandler); 696 FaceletHandler includeHandler = new DecorateHandler(config); 697 FaceletHandler handler; 698 if (FaceletHandlerHelper.isDevModeEnabled(ctx)) { 699 // decorate handler with dev handler 700 FaceletHandler devHandler = getDevFaceletHandler(ctx, helper, config, layoutInstance); 701 FaceletHandler nextHandler; 702 if (devHandler == null) { 703 nextHandler = includeHandler; 704 } else { 705 nextHandler = new DevTagHandler(config, layoutInstance.getName(), includeHandler, devHandler); 706 } 707 handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, nextHandler); 708 } else { 709 handler = helper.getAliasFaceletHandler(layoutTagConfigId, vars, blockedPatterns, includeHandler); 710 } 711 // apply 712 handler.apply(ctx, parent); 713 } else { 714 String errMsg = "Missing template property for layout '" + layoutInstance.getName() + "'"; 715 applyErrorHandler(ctx, parent, helper, errMsg); 716 } 717 } 718 } 719 720 protected Map<String, ValueExpression> getVariablesForLayoutBuild(FaceletContext ctx, String modeValue) { 721 Map<String, ValueExpression> vars = new HashMap<String, ValueExpression>(); 722 ValueExpression valueExpr = value.getValueExpression(ctx, Object.class); 723 vars.put(RenderVariables.globalVariables.value.name(), valueExpr); 724 // vars.put(RenderVariables.globalVariables.document.name(), 725 // valueExpr); 726 vars.put(RenderVariables.globalVariables.layoutValue.name(), valueExpr); 727 ExpressionFactory eFactory = ctx.getExpressionFactory(); 728 ValueExpression modeVe = eFactory.createValueExpression(modeValue, String.class); 729 vars.put(RenderVariables.globalVariables.layoutMode.name(), modeVe); 730 // mode as alias to layoutMode 731 vars.put(RenderVariables.globalVariables.mode.name(), modeVe); 732 return vars; 733 } 734 735 /** 736 * Computes variables for rendering, making available the layout instance and its properties to the context. 737 */ 738 protected Map<String, ValueExpression> getVariablesForLayoutRendering(FaceletContext ctx, 739 WebLayoutManager layoutService, Layout layoutInstance) { 740 Map<String, ValueExpression> vars = new HashMap<String, ValueExpression>(); 741 ExpressionFactory eFactory = ctx.getExpressionFactory(); 742 743 // expose layout value 744 ValueExpression layoutVe = eFactory.createValueExpression(layoutInstance, Layout.class); 745 vars.put(RenderVariables.layoutVariables.layout.name(), layoutVe); 746 747 // expose layout properties too 748 for (Map.Entry<String, Serializable> prop : layoutInstance.getProperties().entrySet()) { 749 String key = prop.getKey(); 750 String name = RenderVariables.layoutVariables.layoutProperty.name() + "_" + key; 751 String value; 752 Serializable valueInstance = prop.getValue(); 753 if (!layoutService.referencePropertyAsExpression(key, valueInstance, null, null, null, null)) { 754 // FIXME: this will not be updated correctly using ajax 755 value = (String) valueInstance; 756 } else { 757 // create a reference so that it's a real expression and it's 758 // not kept (cached) in a component value on ajax refresh 759 value = "#{" + RenderVariables.layoutVariables.layout.name() + ".properties." + key + "}"; 760 } 761 vars.put(name, eFactory.createValueExpression(ctx, value, Object.class)); 762 } 763 764 return vars; 765 } 766 767 /** 768 * End of compatibility methods 769 */ 770 771}