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}