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: WidgetTypeTagHandler.java 26053 2007-10-16 01:45:43Z atchertchian $
018 */
019
020package org.nuxeo.ecm.platform.forms.layout.facelets;
021
022import java.io.IOException;
023import java.io.Serializable;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029
030import javax.el.ELException;
031import javax.el.ValueExpression;
032import javax.el.VariableMapper;
033import javax.faces.component.UIComponent;
034import javax.faces.view.facelets.ComponentConfig;
035import javax.faces.view.facelets.FaceletContext;
036import javax.faces.view.facelets.FaceletHandler;
037import javax.faces.view.facelets.TagAttribute;
038import javax.faces.view.facelets.TagConfig;
039import javax.faces.view.facelets.TagHandler;
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.FieldDefinition;
045import org.nuxeo.ecm.platform.forms.layout.api.Widget;
046import org.nuxeo.ecm.platform.forms.layout.api.impl.FieldDefinitionImpl;
047import org.nuxeo.ecm.platform.forms.layout.api.impl.WidgetDefinitionImpl;
048import org.nuxeo.ecm.platform.forms.layout.facelets.plugins.TemplateWidgetTypeHandler;
049import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager;
050import org.nuxeo.ecm.platform.ui.web.tag.handler.SetTagHandler;
051import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory;
052import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
053import org.nuxeo.runtime.api.Framework;
054
055import com.sun.faces.facelets.el.VariableMapperWrapper;
056
057/**
058 * Widget type tag handler.
059 * <p>
060 * Applies a {@link WidgetTypeHandler} resolved from a widget created for given type name and mode, and uses other tag
061 * attributes to fill the widget properties.
062 * <p>
063 * Does not handle sub widgets.
064 *
065 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
066 */
067public class WidgetTypeTagHandler extends TagHandler {
068
069    private static final Log log = LogFactory.getLog(WidgetTypeTagHandler.class);
070
071    protected final TagConfig config;
072
073    protected final TagAttribute name;
074
075    protected final TagAttribute category;
076
077    protected final TagAttribute mode;
078
079    protected final TagAttribute value;
080
081    protected final TagAttribute field;
082
083    protected final TagAttribute fields;
084
085    protected final TagAttribute label;
086
087    protected final TagAttribute helpLabel;
088
089    protected final TagAttribute translated;
090
091    protected final TagAttribute properties;
092
093    /**
094     * @since 5.7
095     */
096    protected final TagAttribute widgetName;
097
098    /**
099     * Convenient attribute to remove the "template" property from widget properties (and avoid stack overflow errors
100     * when using another widget type in a widget template, for compatibility code for instance).
101     *
102     * @since 5.6
103     */
104    protected final TagAttribute ignoreTemplateProperty;
105
106    /**
107     * @since 5.6
108     */
109    protected final TagAttribute subWidgets;
110
111    protected final TagAttribute resolveOnly;
112
113    protected final TagAttribute[] vars;
114
115    protected final String[] reservedVarsArray = { "id", "name", "category", "mode", "value", "type", "field",
116            "fields", "widgetName", "label", "helpLabel", "translated", "properties", "ignoreTemplateProperty",
117            "subWidgets", "resolveOnly" };
118
119    public WidgetTypeTagHandler(TagConfig config) {
120        super(config);
121        this.config = config;
122        name = getRequiredAttribute("name");
123        category = getAttribute("category");
124        mode = getRequiredAttribute("mode");
125        value = getAttribute("value");
126        field = getAttribute("field");
127        fields = getAttribute("fields");
128        widgetName = getAttribute("widgetName");
129        label = getAttribute("label");
130        helpLabel = getAttribute("helpLabel");
131        translated = getAttribute("translated");
132        properties = getAttribute("properties");
133        ignoreTemplateProperty = getAttribute("ignoreTemplateProperty");
134        subWidgets = getAttribute("subWidgets");
135        resolveOnly = getAttribute("resolveOnly");
136        vars = tag.getAttributes().getAll();
137    }
138
139    @Override
140    @SuppressWarnings({ "unchecked", "rawtypes" })
141    public final void apply(FaceletContext ctx, UIComponent parent) throws IOException, ELException {
142        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
143
144        // compute field definitions
145        List<FieldDefinition> fieldsValue = new ArrayList<FieldDefinition>();
146        if (field != null) {
147            Object fieldValue = field.getObject(ctx, Object.class);
148            if (fieldValue instanceof FieldDefinition) {
149                fieldsValue.add((FieldDefinition) fieldValue);
150            } else if (fieldValue instanceof String) {
151                fieldsValue.add(new FieldDefinitionImpl(null, (String) fieldValue));
152            } else {
153                fieldsValue.add(new FieldDefinitionImpl(null, field.getValue()));
154            }
155        }
156        if (fields != null) {
157            List resolvedfields = (List) fields.getObject(ctx, List.class);
158            for (Object item : resolvedfields) {
159                if (item instanceof FieldDefinition) {
160                    fieldsValue.add((FieldDefinition) item);
161                } else if (item instanceof String) {
162                    fieldsValue.add(new FieldDefinitionImpl(null, (String) item));
163                } else {
164                    log.error("Invalid field item => discard: " + item);
165                }
166            }
167        }
168
169        // build handler
170        List<String> reservedVars = Arrays.asList(reservedVarsArray);
171        Map<String, Serializable> widgetProps = new HashMap<String, Serializable>();
172        if (properties != null) {
173            Map<String, Serializable> propertiesValue = (Map<String, Serializable>) properties.getObject(ctx, Map.class);
174            if (propertiesValue != null) {
175                widgetProps.putAll(propertiesValue);
176            }
177        }
178
179        // do not propagate value the value attribute to the widget
180        // properties if field definitions should be taken into account
181        // instead
182        String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_";
183        boolean includeValueInProps = fieldsValue.isEmpty();
184        for (TagAttribute var : vars) {
185            String localName = var.getLocalName();
186            if ((!reservedVars.contains(localName)) || ("value".equals(localName) && includeValueInProps)) {
187                String varName = localName;
188                if (localName != null && localName.startsWith(widgetPropertyMarker)) {
189                    varName = localName.substring(widgetPropertyMarker.length());
190                }
191                widgetProps.put(varName, var.getValue());
192            }
193        }
194
195        boolean ignoreTemplatePropValue = false;
196        if (ignoreTemplateProperty != null) {
197            ignoreTemplatePropValue = ignoreTemplateProperty.getBoolean(ctx);
198        }
199        if (ignoreTemplatePropValue) {
200            widgetProps.remove(TemplateWidgetTypeHandler.TEMPLATE_PROPERTY_NAME);
201        }
202
203        String typeValue = name.getValue(ctx);
204        String categoryValue = null;
205        if (category != null) {
206            categoryValue = category.getValue(ctx);
207        }
208        String modeValue = mode.getValue(ctx);
209        String valueName = null;
210        if (value != null) {
211            valueName = value.getValue();
212            if (ComponentTagUtils.isStrictValueReference(valueName)) {
213                valueName = ComponentTagUtils.getBareValueName(valueName);
214            }
215        }
216        String widgetNameValue = null;
217        if (widgetName != null) {
218            widgetNameValue = widgetName.getValue(ctx);
219        }
220        String labelValue = null;
221        if (label != null) {
222            labelValue = label.getValue(ctx);
223        }
224        String helpLabelValue = null;
225        if (helpLabel != null) {
226            helpLabelValue = helpLabel.getValue(ctx);
227        }
228        Boolean translatedValue = Boolean.FALSE;
229        if (translated != null) {
230            translatedValue = Boolean.valueOf(translated.getBoolean(ctx));
231        }
232
233        Widget[] subWidgetsValue = null;
234        if (subWidgets != null) {
235            subWidgetsValue = (Widget[]) subWidgets.getObject(ctx, Widget[].class);
236        }
237
238        // avoid double markers
239        if (widgetNameValue != null && widgetNameValue.startsWith(FaceletHandlerHelper.WIDGET_ID_PREFIX)) {
240            widgetNameValue = widgetNameValue.substring(FaceletHandlerHelper.WIDGET_ID_PREFIX.length());
241        }
242        if (StringUtils.isBlank(widgetNameValue)) {
243            widgetNameValue = typeValue;
244        }
245        WidgetDefinitionImpl wDef = new WidgetDefinitionImpl(widgetNameValue, typeValue, labelValue, helpLabelValue,
246                translatedValue.booleanValue(), null, fieldsValue, widgetProps, null);
247        wDef.setTypeCategory(categoryValue);
248        wDef.setDynamic(true);
249        Widget widget = layoutService.createWidget(ctx, wDef, modeValue, valueName, subWidgetsValue);
250
251        // expose widget variable
252        VariableMapper orig = ctx.getVariableMapper();
253        VariableMapper vm = new VariableMapperWrapper(orig);
254        ctx.setVariableMapper(vm);
255        ValueExpression widgetVe = ctx.getExpressionFactory().createValueExpression(widget, Widget.class);
256        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
257        vm.setVariable(
258                String.format("%s_%s", RenderVariables.widgetVariables.widget.name(),
259                        Integer.valueOf(widget.getLevel())), widgetVe);
260        // TODO NXP-13280: expose widget controls too when they can be
261        // retrieved from tag attributes
262        try {
263            // set unique id on widget before exposing it to the context
264            FaceletHandlerHelper helper = new FaceletHandlerHelper(ctx, config);
265            WidgetTagHandler.generateWidgetId(helper, widget, false);
266
267            boolean resolveOnlyBool = false;
268            if (resolveOnly != null) {
269                resolveOnlyBool = resolveOnly.getBoolean(ctx);
270            }
271            if (resolveOnlyBool) {
272                // NXP-12882: wrap handler in an nxu:set tag to avoid duplicate
273                // id issue when widget definition changes, as component ids
274                // can be cached and not generated-again on ajax re-render,
275                // this is a quick fix that can be optimized, as the widget
276                // variable is already exposed in the current variable mapper.
277                // Update after NXP-15050: this does not seem to be necessary
278                // anymore, could not reproduce the corresponding bug, to
279                // remove after complementary tests.
280                String setTagConfigId = widget.getTagConfigId();
281                ComponentConfig aliasConfig = TagConfigFactory.createAliasTagConfig(this.config, setTagConfigId,
282                        RenderVariables.widgetVariables.widget.name(), "#{widget}", "true", "true", nextHandler);
283                FaceletHandler handler = new SetTagHandler(aliasConfig);
284                handler.apply(ctx, parent);
285            } else {
286                WidgetTagHandler.applyWidgetHandler(ctx, parent, config, widget, value, true, nextHandler);
287            }
288        } finally {
289            ctx.setVariableMapper(orig);
290        }
291    }
292
293}