001/*
002 * (C) Copyright 2006-2007 Nuxeo SAS (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
016 *
017 * $Id: WebLayoutManagerImpl.java 28510 2008-01-06 10:21:44Z sfermigier $
018 */
019
020package org.nuxeo.ecm.platform.forms.layout.service;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.List;
028import java.util.Map;
029import java.util.Set;
030
031import javax.el.ExpressionFactory;
032import javax.el.ValueExpression;
033import javax.el.VariableMapper;
034import javax.faces.view.facelets.ComponentHandler;
035import javax.faces.view.facelets.FaceletContext;
036import javax.faces.view.facelets.FaceletHandler;
037import javax.faces.view.facelets.TagConfig;
038
039import org.apache.commons.lang.StringUtils;
040import org.apache.commons.logging.Log;
041import org.apache.commons.logging.LogFactory;
042import org.nuxeo.ecm.platform.forms.layout.api.BuiltinModes;
043import org.nuxeo.ecm.platform.forms.layout.api.BuiltinWidgetModes;
044import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
045import org.nuxeo.ecm.platform.forms.layout.api.Layout;
046import org.nuxeo.ecm.platform.forms.layout.api.LayoutDefinition;
047import org.nuxeo.ecm.platform.forms.layout.api.LayoutRow;
048import org.nuxeo.ecm.platform.forms.layout.api.LayoutRowDefinition;
049import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeConfiguration;
050import org.nuxeo.ecm.platform.forms.layout.api.LayoutTypeDefinition;
051import org.nuxeo.ecm.platform.forms.layout.api.Widget;
052import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
053import org.nuxeo.ecm.platform.forms.layout.api.WidgetReference;
054import org.nuxeo.ecm.platform.forms.layout.api.WidgetType;
055import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeConfiguration;
056import org.nuxeo.ecm.platform.forms.layout.api.WidgetTypeDefinition;
057import org.nuxeo.ecm.platform.forms.layout.api.converters.LayoutConversionContext;
058import org.nuxeo.ecm.platform.forms.layout.api.converters.LayoutDefinitionConverter;
059import org.nuxeo.ecm.platform.forms.layout.api.converters.WidgetDefinitionConverter;
060import org.nuxeo.ecm.platform.forms.layout.api.exceptions.LayoutException;
061import org.nuxeo.ecm.platform.forms.layout.api.exceptions.WidgetException;
062import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutImpl;
063import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowComparator;
064import org.nuxeo.ecm.platform.forms.layout.api.impl.LayoutRowImpl;
065import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
066import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetImpl;
067import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetReferenceImpl;
068import org.nuxeo.ecm.platform.forms.layout.core.service.AbstractLayoutManager;
069import org.nuxeo.ecm.platform.forms.layout.core.service.LayoutStoreImpl;
070import org.nuxeo.ecm.platform.forms.layout.descriptors.LayoutDescriptor;
071import org.nuxeo.ecm.platform.forms.layout.descriptors.LayoutTypeDescriptor;
072import org.nuxeo.ecm.platform.forms.layout.descriptors.WidgetDescriptor;
073import org.nuxeo.ecm.platform.forms.layout.descriptors.WidgetTypeDescriptor;
074import org.nuxeo.ecm.platform.forms.layout.facelets.FaceletHandlerHelper;
075import org.nuxeo.ecm.platform.forms.layout.facelets.RenderVariables;
076import org.nuxeo.ecm.platform.forms.layout.facelets.WidgetTypeHandler;
077import org.nuxeo.ecm.platform.forms.layout.facelets.dev.DevTagHandler;
078import org.nuxeo.ecm.platform.forms.layout.facelets.plugins.TemplateWidgetTypeHandler;
079import org.nuxeo.ecm.platform.forms.layout.functions.LayoutFunctions;
080import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
081import org.nuxeo.runtime.api.Framework;
082import org.nuxeo.runtime.model.ComponentInstance;
083import org.nuxeo.runtime.model.ComponentName;
084
085import com.sun.faces.facelets.el.VariableMapperWrapper;
086
087/**
088 * Layout service implementation.
089 *
090 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
091 */
092public class WebLayoutManagerImpl extends AbstractLayoutManager implements WebLayoutManager {
093
094    public static final ComponentName NAME = new ComponentName(WebLayoutManagerImpl.class.getName());
095
096    private static final Log log = LogFactory.getLog(WebLayoutManagerImpl.class);
097
098    private static final long serialVersionUID = 1L;
099
100    public static final String WIDGET_TYPES_EP_NAME = LayoutStoreImpl.WIDGET_TYPES_EP_NAME;
101
102    /**
103     * @since 6.0
104     */
105    public static final String LAYOUT_TYPES_EP_NAME = LayoutStoreImpl.LAYOUT_TYPES_EP_NAME;
106
107    public static final String WIDGETS_EP_NAME = LayoutStoreImpl.WIDGETS_EP_NAME;
108
109    public static final String LAYOUTS_EP_NAME = LayoutStoreImpl.LAYOUTS_EP_NAME;
110
111    public static final String PROPS_REF_EP_NAME = "disabledPropertyRefs";
112
113    protected DisabledPropertyRefRegistry disabledPropertyRefsReg;
114
115    // Runtime component API
116
117    public WebLayoutManagerImpl() {
118        super();
119        disabledPropertyRefsReg = new DisabledPropertyRefRegistry();
120    }
121
122    @Override
123    public void registerContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
124        if (extensionPoint.equals(WIDGET_TYPES_EP_NAME)) {
125            registerWidgetType(((WidgetTypeDescriptor) contribution).getWidgetTypeDefinition());
126        } else if (extensionPoint.equals(LAYOUT_TYPES_EP_NAME)) {
127            registerLayoutType(((LayoutTypeDescriptor) contribution).getLayoutTypeDefinition());
128        } else if (extensionPoint.equals(LAYOUTS_EP_NAME)) {
129            registerLayout(((LayoutDescriptor) contribution).getLayoutDefinition());
130        } else if (extensionPoint.equals(WIDGETS_EP_NAME)) {
131            registerWidget(((WidgetDescriptor) contribution).getWidgetDefinition());
132        } else if (extensionPoint.equals(PROPS_REF_EP_NAME)) {
133            registerDisabledPropertyRef(((DisabledPropertyRefDescriptor) contribution));
134        } else {
135            log.error(String.format("Unknown extension point '%s', can't register !", extensionPoint));
136        }
137    }
138
139    @Override
140    public void unregisterContribution(Object contribution, String extensionPoint, ComponentInstance contributor) {
141        if (extensionPoint.equals(WIDGET_TYPES_EP_NAME)) {
142            unregisterWidgetType(((WidgetTypeDescriptor) contribution).getWidgetTypeDefinition());
143        } else if (extensionPoint.equals(LAYOUT_TYPES_EP_NAME)) {
144            unregisterLayoutType(((LayoutTypeDescriptor) contribution).getLayoutTypeDefinition());
145        } else if (extensionPoint.equals(LAYOUTS_EP_NAME)) {
146            unregisterLayout(((LayoutDescriptor) contribution).getLayoutDefinition());
147        } else if (extensionPoint.equals(WIDGETS_EP_NAME)) {
148            unregisterWidget(((WidgetDescriptor) contribution).getWidgetDefinition());
149        } else if (extensionPoint.equals(PROPS_REF_EP_NAME)) {
150            unregisterDisabledPropertyRef(((DisabledPropertyRefDescriptor) contribution));
151        } else {
152            log.error(String.format("Unknown extension point '%s', can't unregister !", extensionPoint));
153        }
154    }
155
156    // specific API (depends on JSF impl)
157
158    @Override
159    public String getDefaultStoreCategory() {
160        return JSF_CATEGORY;
161    }
162
163    @Override
164    public WidgetTypeHandler getWidgetTypeHandler(String typeName) throws WidgetException {
165        return getWidgetTypeHandler(getDefaultStoreCategory(), typeName);
166    }
167
168    @Override
169    public WidgetTypeHandler getWidgetTypeHandler(String typeCategory, String typeName) throws WidgetException {
170        if (StringUtils.isBlank(typeCategory)) {
171            typeCategory = getDefaultStoreCategory();
172        }
173        WidgetType type = getLayoutStore().getWidgetType(typeCategory, typeName);
174        if (type == null) {
175            return null;
176        }
177        WidgetTypeHandler handler;
178        Class<?> klass = type.getWidgetTypeClass();
179        if (klass == null) {
180            // implicit handler is the "template" one
181            handler = new TemplateWidgetTypeHandler();
182        } else {
183            try {
184                // Thread context loader is not working in isolated EARs
185                handler = (WidgetTypeHandler) klass.newInstance();
186            } catch (ReflectiveOperationException e) {
187                log.error("Caught error when instanciating widget type handler", e);
188                return null;
189            }
190        }
191        // set properties
192        handler.setProperties(type.getProperties());
193        return handler;
194    }
195
196    /**
197     * Evaluates an EL expression in given context.
198     * <p>
199     * If the expression resolves to an EL expression, evaluate it again this is useful when retrieving the expression
200     * from a configuration file.
201     * <p>
202     * If given context is null, do no try to evaluate it and return the expression itself.
203     *
204     * @param context the facelet context.
205     * @param expression the string expression.
206     */
207    protected static Object evaluateExpression(FaceletContext context, String expression) {
208        if (expression == null) {
209            return null;
210        }
211        if (context == null) {
212            return expression;
213        }
214        Object value = ComponentTagUtils.resolveElExpression(context, expression);
215        if (value != null && value instanceof String) {
216            // evaluate a second time in case it's another EL expression
217            value = ComponentTagUtils.resolveElExpression(context, (String) value);
218        }
219        return value;
220    }
221
222    /**
223     * Evaluates an expression to a boolean value.
224     */
225    protected static Boolean getBooleanValue(FaceletContext context, String expression) {
226        Object value = evaluateExpression(context, expression);
227        if (value instanceof Boolean) {
228            return (Boolean) value;
229        } else if (value == null || value instanceof String) {
230            return Boolean.valueOf((String) value);
231        } else {
232            log.error(String.format("Could not get boolean value for '%s'", value));
233            return Boolean.FALSE;
234        }
235    }
236
237    /**
238     * Evaluates an expression to a string value.
239     */
240    protected static String getStringValue(FaceletContext context, String expression) {
241        Object value = evaluateExpression(context, expression);
242        if (value == null || value instanceof String) {
243            return (String) value;
244        } else {
245            log.error(String.format("Could not get string value for '%s'", value));
246            return null;
247        }
248    }
249
250    protected static String getModeFromLayoutMode(FaceletContext context, WidgetDefinition wDef, String layoutMode) {
251        String wMode = getStringValue(context, wDef.getMode(layoutMode));
252        if (wMode == null) {
253            wMode = BuiltinModes.getWidgetModeFromLayoutMode(layoutMode);
254        }
255        return wMode;
256    }
257
258    @Override
259    public Widget getWidget(FaceletContext ctx, String widgetName, String widgetCategory, String layoutMode,
260            String valueName, String layoutName) {
261        WidgetReference widgetRef = new WidgetReferenceImpl(widgetCategory, widgetName);
262        WidgetDefinition wDef = lookupWidget(widgetRef);
263        return getWidget(ctx, null, null, layoutName, null, wDef, widgetCategory, layoutMode, valueName, 0);
264    }
265
266    @Override
267    public Widget getWidget(FaceletContext ctx, WidgetDefinition wDef, String layoutMode, String valueName,
268            String layoutName) {
269        return getWidget(ctx, null, null, layoutName, null, wDef, getDefaultStoreCategory(), layoutMode, valueName, 0);
270    }
271
272    @Override
273    public Widget getWidget(FaceletContext ctx, LayoutConversionContext lctx, String conversionCat,
274            WidgetDefinition widgetDef, String layoutMode, String valueName, String layoutName) {
275        return getWidget(ctx, null, null, layoutName, null, widgetDef, getDefaultStoreCategory(), layoutMode,
276                valueName, 0);
277    }
278
279    /**
280     * Computes a widget from a definition for a mode in a given context.
281     * <p>
282     * If the widget is configured not to be rendered in the given mode, returns null.
283     * <p>
284     * Sub widgets are also computed recursively.
285     */
286    @SuppressWarnings("deprecation")
287    protected Widget getWidget(FaceletContext context, LayoutConversionContext lctx, String conversionCat,
288            String layoutName, LayoutDefinition layoutDef, WidgetDefinition widgetDefinition, String widgetCategory,
289            String layoutMode, String valueName, int level) {
290        if (widgetDefinition == null) {
291            return null;
292        }
293        WidgetDefinition wDef = widgetDefinition.clone();
294        if (lctx != null && !StringUtils.isBlank(conversionCat)) {
295            List<WidgetDefinitionConverter> lcs = getLayoutStore().getWidgetConverters(conversionCat);
296            for (WidgetDefinitionConverter wc : lcs) {
297                wDef = wc.getWidgetDefinition(wDef, lctx);
298            }
299        }
300        VariableMapper orig = null;
301        // avoid variable mapper changes if context is null for tests
302        if (context != null) {
303            // expose widget mode so that it can be used in a mode el
304            // expression
305            orig = context.getVariableMapper();
306            VariableMapper vm = new VariableMapperWrapper(orig);
307            context.setVariableMapper(vm);
308            ExpressionFactory eFactory = context.getExpressionFactory();
309            ValueExpression modeVe = eFactory.createValueExpression(layoutMode, String.class);
310            vm.setVariable(RenderVariables.globalVariables.mode.name(), modeVe);
311        }
312        String wMode = getModeFromLayoutMode(context, wDef, layoutMode);
313        if (context != null) {
314            context.setVariableMapper(orig);
315        }
316
317        if (BuiltinWidgetModes.HIDDEN.equals(wMode)) {
318            return null;
319        }
320        List<Widget> subWidgets = new ArrayList<Widget>();
321        WidgetDefinition[] swDefs = wDef.getSubWidgetDefinitions();
322        if (swDefs != null) {
323            for (WidgetDefinition swDef : swDefs) {
324                Widget subWidget = getWidget(context, lctx, conversionCat, layoutName, layoutDef, swDef,
325                        widgetCategory, wMode, valueName, level + 1);
326                if (subWidget != null) {
327                    subWidgets.add(subWidget);
328                }
329            }
330        }
331
332        WidgetReference[] swRefs = wDef.getSubWidgetReferences();
333        if (swRefs != null) {
334            for (WidgetReference swRef : swRefs) {
335                String cat = swRef.getCategory();
336                if (StringUtils.isBlank(cat)) {
337                    cat = widgetCategory;
338                }
339                WidgetDefinition swDef = lookupWidget(layoutDef, new WidgetReferenceImpl(cat, swRef.getName()));
340                if (swDef == null) {
341                    log.error(String.format("Widget '%s' not found in layout %s", swRef.getName(), layoutName));
342                } else {
343                    Widget subWidget = getWidget(context, lctx, conversionCat, layoutName, layoutDef, swDef, cat,
344                            wMode, valueName, level + 1);
345                    if (subWidget != null) {
346                        subWidgets.add(subWidget);
347                    }
348                }
349            }
350        }
351
352        boolean required = getBooleanValue(context, wDef.getRequired(layoutMode, wMode)).booleanValue();
353
354        String wType = wDef.getType();
355        String wTypeCat = wDef.getTypeCategory();
356        // fill default property and control values from the widget definition
357        Map<String, Serializable> props = new HashMap<String, Serializable>();
358        Map<String, Serializable> controls = new HashMap<String, Serializable>();
359        String actualWTypeCat = getStoreCategory(wTypeCat);
360        WidgetTypeDefinition def = getLayoutStore().getWidgetTypeDefinition(actualWTypeCat, wType);
361
362        WidgetTypeConfiguration conf = def != null ? def.getConfiguration() : null;
363        if (conf != null) {
364            Map<String, Serializable> defaultProps = conf.getDefaultPropertyValues(wMode);
365            if (defaultProps != null && !defaultProps.isEmpty()) {
366                props.putAll(defaultProps);
367            }
368            Map<String, Serializable> defaultControls = conf.getDefaultControlValues(wMode);
369            if (defaultControls != null && !defaultControls.isEmpty()) {
370                controls.putAll(defaultControls);
371            }
372        }
373
374        props.putAll(wDef.getProperties(layoutMode, wMode));
375        controls.putAll(wDef.getControls(layoutMode, wMode));
376
377        WidgetImpl widget = new WidgetImpl(layoutName, wDef.getName(), wMode, wType, valueName,
378                wDef.getFieldDefinitions(), wDef.getLabel(layoutMode), wDef.getHelpLabel(layoutMode),
379                wDef.isTranslated(), wDef.isHandlingLabels(), props, required, subWidgets.toArray(new Widget[0]),
380                level, wDef.getSelectOptions(), LayoutFunctions.computeWidgetDefinitionId(wDef),
381                wDef.getRenderingInfos(layoutMode));
382        widget.setControls(controls);
383        widget.setTypeCategory(actualWTypeCat);
384        if (Framework.isDevModeSet()) {
385            widget.setDefinition(wDef);
386        }
387        return widget;
388    }
389
390    @Override
391    public Layout getLayout(FaceletContext ctx, String layoutName, String mode, String valueName)
392            throws LayoutException {
393        return getLayout(ctx, layoutName, mode, valueName, null, false);
394    }
395
396    @Override
397    public Layout getLayout(FaceletContext ctx, String layoutName, String mode, String valueName,
398            List<String> selectedRows, boolean selectAllRowsByDefault) {
399        return getLayout(ctx, layoutName, null, mode, valueName, selectedRows, selectAllRowsByDefault);
400    }
401
402    @Override
403    public Layout getLayout(FaceletContext ctx, String layoutName, String layoutCategory, String mode,
404            String valueName, List<String> selectedRows, boolean selectAllRowsByDefault) {
405        if (StringUtils.isBlank(layoutCategory)) {
406            layoutCategory = getDefaultStoreCategory();
407        }
408        LayoutDefinition layoutDef = getLayoutStore().getLayoutDefinition(layoutCategory, layoutName);
409        if (layoutDef == null) {
410            log.debug(String.format("Layout '%s' not found for category '%s'", layoutName, layoutCategory));
411            return null;
412        }
413        return getLayout(ctx, layoutDef, mode, valueName, selectedRows, selectAllRowsByDefault);
414    }
415
416    @Override
417    public Layout getLayout(FaceletContext ctx, LayoutDefinition layoutDef, String mode, String valueName,
418            List<String> selectedRows, boolean selectAllRowsByDefault) {
419        return getLayout(ctx, null, null, layoutDef, mode, valueName, selectedRows, selectAllRowsByDefault);
420    }
421
422    @Override
423    public Layout getLayout(FaceletContext ctx, LayoutConversionContext lctx, String conversionCat,
424            LayoutDefinition layoutDefinition, String mode, String valueName, List<String> selectedRows,
425            boolean selectAllRowsByDefault) {
426        if (layoutDefinition == null) {
427            log.debug("Layout definition is null");
428            return null;
429        }
430        if (ctx == null) {
431            log.warn("Layout creation computed in a null facelet context: expressions "
432                    + "found in the layout definition will not be evaluated");
433        }
434        LayoutDefinition lDef = layoutDefinition.clone();
435        if (lctx != null && !StringUtils.isBlank(conversionCat)) {
436            List<LayoutDefinitionConverter> lcs = getLayoutStore().getLayoutConverters(conversionCat);
437            for (LayoutDefinitionConverter lc : lcs) {
438                lDef = lc.getLayoutDefinition(lDef, lctx);
439            }
440        }
441        String layoutName = lDef.getName();
442        LayoutRowDefinition[] rowsDef = lDef.getRows();
443        List<LayoutRow> rows = new ArrayList<LayoutRow>();
444        Set<String> foundRowNames = new HashSet<String>();
445        int rowIndex = -1;
446        for (LayoutRowDefinition rowDef : rowsDef) {
447            rowIndex++;
448            String rowName = rowDef.getName();
449            if (rowName == null) {
450                rowName = rowDef.getDefaultName(rowIndex);
451                if (selectedRows != null) {
452                    log.debug(String.format("Generating default name '%s' in "
453                            + "layout '%s' for row or column at index %s", rowName, layoutName,
454                            Integer.valueOf(rowIndex)));
455                }
456            }
457            boolean emptyRow = true;
458            if (selectedRows != null && !selectedRows.contains(rowName) && !rowDef.isAlwaysSelected()) {
459                continue;
460            }
461            if (selectedRows == null && !selectAllRowsByDefault && !rowDef.isSelectedByDefault()
462                    && !rowDef.isAlwaysSelected()) {
463                continue;
464            }
465            List<Widget> widgets = new ArrayList<Widget>();
466            for (WidgetReference widgetRef : rowDef.getWidgetReferences()) {
467                String widgetName = widgetRef.getName();
468                if (widgetName == null || widgetName.length() == 0) {
469                    // no widget at this place
470                    widgets.add(null);
471                    continue;
472                }
473                String cat = widgetRef.getCategory();
474                if (StringUtils.isBlank(cat)) {
475                    cat = getDefaultStoreCategory();
476                }
477                WidgetDefinition wDef = lookupWidget(lDef, new WidgetReferenceImpl(cat, widgetName));
478                if (wDef == null) {
479                    log.error(String.format("Widget '%s' not found in layout %s", widgetName, layoutName));
480                    widgets.add(null);
481                    continue;
482                }
483                Widget widget = getWidget(ctx, lctx, conversionCat, layoutName, lDef, wDef, cat, mode, valueName, 0);
484                if (widget != null) {
485                    emptyRow = false;
486                }
487                widgets.add(widget);
488            }
489            if (!emptyRow) {
490                rows.add(new LayoutRowImpl(rowName, rowDef.isSelectedByDefault(), rowDef.isAlwaysSelected(), widgets,
491                        rowDef.getProperties(mode), LayoutFunctions.computeLayoutRowDefinitionId(rowDef)));
492            }
493            foundRowNames.add(rowName);
494        }
495        if (selectedRows != null) {
496            Collections.sort(rows, new LayoutRowComparator(selectedRows));
497            for (String selectedRow : selectedRows) {
498                if (!foundRowNames.contains(selectedRow)) {
499                    log.debug(String.format("Selected row or column named '%s' " + "was not found in layout '%s'",
500                            selectedRow, layoutName));
501                }
502            }
503        }
504
505        String layoutTypeCategory = lDef.getTypeCategory();
506        String actualLayoutTypeCategory = getStoreCategory(layoutTypeCategory);
507        LayoutTypeDefinition layoutTypeDef = null;
508        String layoutType = lDef.getType();
509        if (!StringUtils.isBlank(layoutType)) {
510            // retrieve type for templates and props mapping
511            layoutTypeDef = getLayoutStore().getLayoutTypeDefinition(actualLayoutTypeCategory, layoutType);
512            if (layoutTypeDef == null) {
513                log.debug(String.format("Layout type '%s' not found for category '%s'", layoutType, layoutTypeCategory));
514            }
515        }
516
517        String template = lDef.getTemplate(mode);
518        Map<String, Serializable> props = new HashMap<>();
519        if (layoutTypeDef != null) {
520            if (StringUtils.isEmpty(template)) {
521                template = layoutTypeDef.getTemplate(mode);
522            }
523            LayoutTypeConfiguration conf = layoutTypeDef.getConfiguration();
524            if (conf != null) {
525                Map<String, Serializable> typeProps = conf.getDefaultPropertyValues(mode);
526                if (typeProps != null) {
527                    props.putAll(typeProps);
528                }
529            }
530        }
531        Map<String, Serializable> lprops = lDef.getProperties(mode);
532        if (lprops != null) {
533            props.putAll(lprops);
534        }
535        LayoutImpl layout = new LayoutImpl(lDef.getName(), mode, template, rows, lDef.getColumns(), props,
536                LayoutFunctions.computeLayoutDefinitionId(lDef));
537        layout.setValueName(valueName);
538        layout.setType(layoutType);
539        layout.setTypeCategory(actualLayoutTypeCategory);
540        if (Framework.isDevModeSet()) {
541            layout.setDefinition(lDef);
542            // resolve template in "dev" mode, avoiding default lookup on "any"
543            // mode
544            Map<String, String> templates = lDef.getTemplates();
545            String devTemplate = templates != null ? templates.get(BuiltinModes.DEV) : null;
546            if (layoutTypeDef != null && StringUtils.isEmpty(devTemplate)) {
547                Map<String, String> typeTemplates = layoutTypeDef.getTemplates();
548                devTemplate = typeTemplates != null ? typeTemplates.get(BuiltinModes.DEV) : null;
549            }
550            layout.setDevTemplate(devTemplate);
551        }
552        return layout;
553    }
554
555    @Override
556    public FaceletHandler getFaceletHandler(FaceletContext ctx, TagConfig config, Widget widget) {
557        return getFaceletHandler(ctx, config, widget, null);
558    }
559
560    @Override
561    public FaceletHandler getFaceletHandler(FaceletContext ctx, TagConfig config, Widget widget,
562            FaceletHandler nextHandler) {
563        String widgetTypeName = widget.getType();
564        String widgetTypeCategory = widget.getTypeCategory();
565        WidgetTypeHandler handler = getWidgetTypeHandler(widgetTypeCategory, widgetTypeName);
566        if (handler == null) {
567            FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, config);
568            String message = String.format("No widget handler found for type '%s' in category '%s'", widgetTypeName,
569                    widgetTypeCategory);
570            log.error(message);
571            ComponentHandler output = helper.getErrorComponentHandler(null, message);
572            return output;
573        } else {
574            FaceletHandler[] subHandlers = null;
575            List<FaceletHandler> subHandlersList = new ArrayList<FaceletHandler>();
576            if (nextHandler != null) {
577                subHandlersList.add(nextHandler);
578            }
579            if (!subHandlersList.isEmpty()) {
580                subHandlers = subHandlersList.toArray(new FaceletHandler[0]);
581            }
582            FaceletHandler widgetHandler = handler.getFaceletHandler(ctx, config, widget, subHandlers);
583
584            if (FaceletHandlerHelper.isDevModeEnabled(ctx)) {
585                // decorate handler with dev handler
586                FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, config);
587                FaceletHandler devHandler = handler.getDevFaceletHandler(ctx, config, widget);
588                if (devHandler == null) {
589                    return widgetHandler;
590                }
591                // expose the widget variable to sub dev handler
592                String widgetTagConfigId = widget.getTagConfigId();
593                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
594                ExpressionFactory eFactory = ctx.getExpressionFactory();
595                ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
596                variables.put(RenderVariables.widgetVariables.widget.name(), widgetVe);
597                List<String> blockedPatterns = new ArrayList<String>();
598                blockedPatterns.add(RenderVariables.widgetVariables.widget.name() + "*");
599                FaceletHandler devAliasHandler = helper.getAliasTagHandler(widgetTagConfigId, variables,
600                        blockedPatterns, devHandler);
601                String refId = widget.getName();
602                FaceletHandler widgetDevHandler = new DevTagHandler(config, refId, widgetHandler, devAliasHandler);
603                return widgetDevHandler;
604            }
605            return widgetHandler;
606        }
607    }
608
609    @Override
610    public Widget createWidget(FaceletContext ctx, String type, String mode, String valueName,
611            Map<String, Serializable> properties, Widget[] subWidgets) {
612        return createWidget(ctx, type, mode, valueName, null, null, null, null, properties, subWidgets);
613    }
614
615    @Override
616    public Widget createWidget(FaceletContext ctx, String type, String mode, String valueName,
617            List<FieldDefinition> fieldDefinitions, String label, String helpLabel, Boolean translated,
618            Map<String, Serializable> properties, Widget[] subWidgets) {
619        return createWidget(
620                ctx,
621                createWidgetDefinition(ctx, type, null, mode, valueName, fieldDefinitions, null, label, helpLabel,
622                        translated, properties, subWidgets), mode, valueName, subWidgets);
623    }
624
625    @Override
626    public Widget createWidget(FaceletContext ctx, WidgetDefinition wDef, String mode, String valueName,
627            Widget[] subWidgets) {
628
629        String wType = wDef.getType();
630        String wTypeCat = wDef.getTypeCategory();
631        // fill default property and control values from the widget definition
632        Map<String, Serializable> props = new HashMap<String, Serializable>();
633        Map<String, Serializable> controls = new HashMap<String, Serializable>();
634        String actualWTypeCat = getStoreCategory(wTypeCat);
635        WidgetTypeDefinition def = getLayoutStore().getWidgetTypeDefinition(actualWTypeCat, wType);
636
637        boolean required = false;
638        WidgetTypeConfiguration conf = def != null ? def.getConfiguration() : null;
639        if (conf != null) {
640            Map<String, Serializable> defaultProps = conf.getDefaultPropertyValues(mode);
641            if (defaultProps != null && !defaultProps.isEmpty()) {
642                props.putAll(defaultProps);
643            }
644            Map<String, Serializable> defaultControls = conf.getDefaultControlValues(mode);
645            if (defaultControls != null && !defaultControls.isEmpty()) {
646                controls.putAll(defaultControls);
647            }
648        }
649        Map<String, Serializable> modeProps = wDef.getProperties(mode, mode);
650        if (modeProps != null) {
651            props.putAll(modeProps);
652            Serializable requiredProp = props.get(WidgetDefinition.REQUIRED_PROPERTY_NAME);
653            if (requiredProp != null) {
654                if (requiredProp instanceof Boolean) {
655                    required = ((Boolean) requiredProp).booleanValue();
656                } else if (requiredProp instanceof String) {
657                    required = getBooleanValue(ctx, (String) requiredProp).booleanValue();
658                } else {
659                    log.error(String.format("Invalid property '%s' on widget: '%s'",
660                            WidgetDefinition.REQUIRED_PROPERTY_NAME, requiredProp));
661                }
662            }
663        }
664        Map<String, Serializable> modeControls = wDef.getControls(mode, mode);
665        if (modeControls != null) {
666            controls.putAll(modeControls);
667        }
668        WidgetImpl widget = new WidgetImpl("layout", wDef.getName(), mode, wType, valueName,
669                wDef.getFieldDefinitions(), wDef.getLabel(mode), wDef.getHelpLabel(mode), wDef.isTranslated(), props,
670                required, subWidgets, 0, null, LayoutFunctions.computeWidgetDefinitionId(wDef));
671        widget.setControls(controls);
672        widget.setTypeCategory(actualWTypeCat);
673        widget.setDynamic(wDef.isDynamic());
674        widget.setGlobal(wDef.isGlobal());
675        if (Framework.isDevModeSet()) {
676            widget.setDefinition(wDef);
677        }
678        return widget;
679    }
680
681    protected WidgetDefinition createWidgetDefinition(FaceletContext ctx, String type, String category, String mode,
682            String valueName, List<FieldDefinition> fieldDefinitions, String widgetName, String label,
683            String helpLabel, Boolean translated, Map<String, Serializable> properties, Widget[] subWidgets) {
684        String wName = widgetName;
685        if (StringUtils.isBlank(widgetName)) {
686            wName = type;
687        }
688        WidgetDefinitionImpl wDef = new WidgetDefinitionImpl(wName, type, label, helpLabel,
689                Boolean.TRUE.equals(translated), null, fieldDefinitions, properties, null);
690        wDef.setDynamic(true);
691        return wDef;
692    }
693
694    /**
695     * @since 5.6
696     */
697    protected void registerDisabledPropertyRef(DisabledPropertyRefDescriptor desc) {
698        disabledPropertyRefsReg.addContribution(desc);
699        log.info(String.format("Registered disabled property reference descriptor: %s", desc.toString()));
700    }
701
702    /**
703     * @since 5.6
704     */
705    protected void unregisterDisabledPropertyRef(DisabledPropertyRefDescriptor desc) {
706        disabledPropertyRefsReg.removeContribution(desc);
707        log.info(String.format("Removed disabled property reference descriptor: %s", desc.toString()));
708    }
709
710    @Override
711    public boolean referencePropertyAsExpression(String name, Serializable value, String widgetType, String widgetMode,
712            String template) {
713        return referencePropertyAsExpression(name, value, widgetType, null, widgetMode, template);
714    }
715
716    @Override
717    public boolean referencePropertyAsExpression(String name, Serializable value, String widgetType,
718            String widgetTypeCategory, String widgetMode, String template) {
719        if ((value instanceof String) && (ComponentTagUtils.isValueReference((String) value))) {
720            return false;
721        }
722        String cat = widgetTypeCategory;
723        if (widgetTypeCategory == null) {
724            cat = WebLayoutManager.JSF_CATEGORY;
725        }
726        for (DisabledPropertyRefDescriptor desc : disabledPropertyRefsReg.getDisabledPropertyRefs()) {
727            if (Boolean.TRUE.equals(desc.getEnabled()) && desc.matches(name, widgetType, cat, widgetMode, template)) {
728                return false;
729            }
730        }
731        return true;
732    }
733
734}