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