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