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.lang3.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.binding.BlockingVariableMapper;
053import org.nuxeo.ecm.platform.ui.web.tag.handler.SetTagHandler;
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", "fields",
118            "widgetName", "label", "helpLabel", "translated", "properties", "ignoreTemplateProperty", "subWidgets",
119            "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,
176                    Map.class);
177            if (propertiesValue != null) {
178                widgetProps.putAll(propertiesValue);
179            }
180        }
181
182        // do not propagate value the value attribute to the widget
183        // properties if field definitions should be taken into account
184        // instead
185        String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_";
186        boolean includeValueInProps = fieldsValue.isEmpty();
187        for (TagAttribute var : vars) {
188            String localName = var.getLocalName();
189            if ((!reservedVars.contains(localName)) || ("value".equals(localName) && includeValueInProps)) {
190                String varName = localName;
191                if (localName != null && localName.startsWith(widgetPropertyMarker)) {
192                    varName = localName.substring(widgetPropertyMarker.length());
193                }
194                widgetProps.put(varName, var.getValue());
195            }
196        }
197
198        boolean ignoreTemplatePropValue = false;
199        if (ignoreTemplateProperty != null) {
200            ignoreTemplatePropValue = ignoreTemplateProperty.getBoolean(ctx);
201        }
202        if (ignoreTemplatePropValue) {
203            widgetProps.remove(TemplateWidgetTypeHandler.TEMPLATE_PROPERTY_NAME);
204        }
205
206        String typeValue = name.getValue(ctx);
207        String categoryValue = null;
208        if (category != null) {
209            categoryValue = category.getValue(ctx);
210        }
211        String modeValue = mode.getValue(ctx);
212        String valueName = null;
213        if (value != null) {
214            valueName = value.getValue();
215            if (ComponentTagUtils.isStrictValueReference(valueName)) {
216                valueName = ComponentTagUtils.getBareValueName(valueName);
217            }
218        }
219        String widgetNameValue = null;
220        if (widgetName != null) {
221            widgetNameValue = widgetName.getValue(ctx);
222        }
223        String labelValue = null;
224        if (label != null) {
225            labelValue = label.getValue(ctx);
226        }
227        String helpLabelValue = null;
228        if (helpLabel != null) {
229            helpLabelValue = helpLabel.getValue(ctx);
230        }
231        Boolean translatedValue = Boolean.FALSE;
232        if (translated != null) {
233            translatedValue = Boolean.valueOf(translated.getBoolean(ctx));
234        }
235
236        Widget[] subWidgetsValue = null;
237        if (subWidgets != null) {
238            subWidgetsValue = (Widget[]) subWidgets.getObject(ctx, Widget[].class);
239        }
240
241        // avoid double markers
242        if (widgetNameValue != null && widgetNameValue.startsWith(FaceletHandlerHelper.WIDGET_ID_PREFIX)) {
243            widgetNameValue = widgetNameValue.substring(FaceletHandlerHelper.WIDGET_ID_PREFIX.length());
244        }
245        if (StringUtils.isBlank(widgetNameValue)) {
246            widgetNameValue = typeValue;
247        }
248        WidgetDefinitionImpl wDef = new WidgetDefinitionImpl(widgetNameValue, typeValue, labelValue, helpLabelValue,
249                translatedValue.booleanValue(), null, fieldsValue, widgetProps, null);
250        wDef.setTypeCategory(categoryValue);
251        wDef.setDynamic(true);
252        Widget widget = layoutService.createWidget(ctx, wDef, modeValue, valueName, subWidgetsValue);
253
254        if (FaceletHandlerHelper.isAliasOptimEnabled()) {
255            applyOptimized(ctx, parent, widget);
256        } else {
257            applyCompat(ctx, parent, widget);
258        }
259    }
260
261    protected void applyOptimized(FaceletContext ctx, UIComponent parent, Widget widget)
262            throws IOException, ELException {
263        // expose widget variable
264        VariableMapper orig = ctx.getVariableMapper();
265        try {
266            BlockingVariableMapper vm = new BlockingVariableMapper(orig);
267            ctx.setVariableMapper(vm);
268
269            // set unique id on widget before exposing it to the context
270            FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
271            WidgetTagHandler.generateWidgetId(ctx, helper, widget, false);
272
273            // TODO NXP-13280: retrieve widget controls from tag attributes before exposure
274            WidgetTagHandler.exposeWidgetVariables(ctx, vm, widget, null, true);
275
276            boolean resolveOnlyBool = false;
277            if (resolveOnly != null) {
278                resolveOnlyBool = resolveOnly.getBoolean(ctx);
279            }
280            if (resolveOnlyBool) {
281                nextHandler.apply(ctx, parent);
282            } else {
283                WidgetTagHandler.applyWidgetHandler(ctx, parent, config, widget, value, true, nextHandler);
284            }
285        } finally {
286            ctx.setVariableMapper(orig);
287        }
288    }
289
290    protected void applyCompat(FaceletContext ctx, UIComponent parent, Widget widget) throws IOException, ELException {
291        // expose widget variable
292        VariableMapper orig = ctx.getVariableMapper();
293        VariableMapper vm = new VariableMapperWrapper(orig);
294        ctx.setVariableMapper(vm);
295        ValueExpression widgetVe = ctx.getExpressionFactory().createValueExpression(widget, Widget.class);
296        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
297        vm.setVariable(RenderVariables.widgetVariables.widget.name() + "_" + widget.getLevel(), widgetVe);
298        // TODO NXP-13280: expose widget controls too when they can be
299        // retrieved from tag attributes
300        try {
301            // set unique id on widget before exposing it to the context
302            FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
303            WidgetTagHandler.generateWidgetId(ctx, helper, widget, false);
304
305            boolean resolveOnlyBool = false;
306            if (resolveOnly != null) {
307                resolveOnlyBool = resolveOnly.getBoolean(ctx);
308            }
309            if (resolveOnlyBool) {
310                // NXP-12882: wrap handler in an nxu:set tag to avoid duplicate
311                // id issue when widget definition changes, as component ids
312                // can be cached and not generated-again on ajax re-render,
313                // this is a quick fix that can be optimized, as the widget
314                // variable is already exposed in the current variable mapper.
315                // Update after NXP-15050: this does not seem to be necessary
316                // anymore, could not reproduce the corresponding bug, to
317                // remove after complementary tests.
318                String setTagConfigId = widget.getTagConfigId();
319                ComponentConfig aliasConfig = org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory.createAliasTagConfig(
320                        this.config, setTagConfigId, RenderVariables.widgetVariables.widget.name(), "#{widget}", "true",
321                        "true", nextHandler);
322                FaceletHandler handler = new SetTagHandler(aliasConfig);
323                handler.apply(ctx, parent);
324            } else {
325                WidgetTagHandler.applyWidgetHandler(ctx, parent, config, widget, value, true, nextHandler);
326            }
327        } finally {
328            ctx.setVariableMapper(orig);
329        }
330    }
331
332}