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