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: TemplateWidgetTypeHandler.java 28244 2007-12-18 19:44:57Z atchertchian $
020 */
021
022package org.nuxeo.ecm.platform.forms.layout.facelets.plugins;
023
024import java.io.IOException;
025import java.io.Serializable;
026import java.util.ArrayList;
027import java.util.HashMap;
028import java.util.List;
029import java.util.Map;
030
031import javax.el.ExpressionFactory;
032import javax.el.ValueExpression;
033import javax.faces.component.UIComponent;
034import javax.faces.view.facelets.FaceletContext;
035import javax.faces.view.facelets.TagAttribute;
036import javax.faces.view.facelets.TagAttributes;
037import javax.faces.view.facelets.TagConfig;
038import javax.faces.view.facelets.TagHandler;
039
040import org.apache.commons.logging.Log;
041import org.apache.commons.logging.LogFactory;
042import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
043import org.nuxeo.ecm.platform.forms.layout.api.Widget;
044import org.nuxeo.ecm.platform.forms.layout.api.exceptions.WidgetException;
045import org.nuxeo.ecm.platform.forms.layout.facelets.FaceletHandlerHelper;
046import org.nuxeo.ecm.platform.forms.layout.facelets.RenderVariables;
047import org.nuxeo.ecm.platform.forms.layout.facelets.ValueExpressionHelper;
048import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager;
049import org.nuxeo.ecm.platform.ui.web.binding.MapValueExpression;
050import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory;
051import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer;
052import org.nuxeo.runtime.api.Framework;
053
054import com.sun.faces.facelets.tag.ui.DecorateHandler;
055
056/**
057 * Template widget type.
058 *
059 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
060 */
061public class TemplateWidgetTypeHandler extends AbstractWidgetTypeHandler {
062
063    private static final Log log = LogFactory.getLog(TemplateWidgetTypeHandler.class);
064
065    public static final String TEMPLATE_PROPERTY_NAME = "template";
066
067    /**
068     * Property that can be put on the widget type definition to decide whether the widget type should bind to parent
069     * value when no field is set
070     *
071     * @since 5.6
072     */
073    public static final String BIND_VALUE_IF_NO_FIELD_PROPERTY_NAME = "bindValueIfNoField";
074
075    public TemplateWidgetTypeHandler(TagConfig config) {
076        super(config);
077    }
078
079    @Override
080    public void apply(FaceletContext ctx, UIComponent parent, Widget widget) throws WidgetException, IOException {
081        long start = FaceletDebugTracer.start();
082
083        try {
084            String template = getTemplateValue(widget);
085            if (template == null) {
086                log.error("Missing template property for widget " + widget.getName() + " in layout "
087                        + widget.getLayoutName());
088                return;
089            }
090            FaceletHandlerHelper helper = new FaceletHandlerHelper(tagConfig);
091            TagAttribute templateAttr = getTemplateAttribute(helper);
092            if (templateAttr == null) {
093                templateAttr = helper.createAttribute(TEMPLATE_PROPERTY_NAME, template);
094            }
095            TagAttributes attributes = FaceletHandlerHelper.getTagAttributes(templateAttr);
096            String widgetTagConfigId = widget.getTagConfigId();
097            TagConfig config = TagConfigFactory.createTagConfig(tagConfig, widgetTagConfigId, attributes, nextHandler);
098
099            Map<String, ValueExpression> variables = getVariablesForRendering(ctx, helper, widget, widgetTagConfigId,
100                    template);
101
102            List<String> blockedPatterns = new ArrayList<String>();
103            blockedPatterns.add(RenderVariables.widgetVariables.field.name() + "*");
104            blockedPatterns.add(RenderVariables.widgetVariables.fieldOrValue.name());
105            blockedPatterns.add(RenderVariables.widgetVariables.widgetProperty.name() + "_*");
106            blockedPatterns.add(RenderVariables.widgetVariables.widgetProperties.name());
107            blockedPatterns.add(RenderVariables.widgetVariables.widgetControl.name() + "_*");
108
109            DecorateHandler includeHandler = new DecorateHandler(config);
110            TagHandler handler = helper.getAliasTagHandler(widgetTagConfigId, variables, blockedPatterns,
111                    includeHandler);
112            handler.apply(ctx, parent);
113        } finally {
114            FaceletDebugTracer.trace(start, tagConfig.getTag(), widget.getId(), -1);
115        }
116    }
117
118    /**
119     * Computes variables for rendering, making available the field values in templates using the format "field_0",
120     * "field_1", etc. and also the widget properties using the format "widgetProperty_thePropertyName".
121     */
122    protected Map<String, ValueExpression> getVariablesForRendering(FaceletContext ctx, FaceletHandlerHelper helper,
123            Widget widget, String widgetTagConfigId, String template) {
124        Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
125        ExpressionFactory eFactory = ctx.getExpressionFactory();
126
127        FieldDefinition[] fieldDefs = widget.getFieldDefinitions();
128        // expose field variables
129        FieldDefinition firstField = null;
130        if (fieldDefs != null && fieldDefs.length > 0) {
131            for (int i = 0; i < fieldDefs.length; i++) {
132                if (i == 0) {
133                    addFieldVariable(variables, ctx, widget, fieldDefs[i], null);
134                    firstField = fieldDefs[i];
135                }
136                addFieldVariable(variables, ctx, widget, fieldDefs[i], Integer.valueOf(i));
137            }
138        } else if (getBindValueIfNoFieldValue(widget)) {
139            // expose value as first parameter
140            addFieldVariable(variables, ctx, widget, null, null);
141            addFieldVariable(variables, ctx, widget, null, Integer.valueOf(0));
142        }
143
144        // add binding "fieldOrValue" available since 5.6, in case template
145        // widget is always supposed to bind value when no field is defined
146        String computedValue = ValueExpressionHelper.createExpressionString(widget.getValueName(), firstField);
147        variables.put(RenderVariables.widgetVariables.fieldOrValue.name(),
148                eFactory.createValueExpression(ctx, computedValue, Object.class));
149
150        // expose widget properties too
151        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
152        Map<String, ValueExpression> mappedExpressions = new HashMap<String, ValueExpression>();
153        for (Map.Entry<String, Serializable> prop : widget.getProperties().entrySet()) {
154            String key = prop.getKey();
155            String name = RenderVariables.widgetVariables.widgetProperty.name() + "_" + key;
156            String value;
157            Serializable valueInstance = prop.getValue();
158            if (!layoutService.referencePropertyAsExpression(key, valueInstance, widget.getType(),
159                    widget.getTypeCategory(), widget.getMode(), template)) {
160                // FIXME: this will not be updated correctly using ajax
161                value = (String) valueInstance;
162            } else {
163                // create a reference so that it's a real expression and it's
164                // not kept (cached) in a component value on ajax refresh
165                value = "#{" + RenderVariables.widgetVariables.widget.name() + ".properties." + key + "}";
166            }
167            ValueExpression ve = eFactory.createValueExpression(ctx, value, Object.class);
168            variables.put(name, ve);
169            mappedExpressions.put(key, ve);
170        }
171        variables.put(RenderVariables.widgetVariables.widgetProperties.name(),
172                new MapValueExpression(mappedExpressions));
173        // expose widget controls too
174        for (Map.Entry<String, Serializable> ctrl : widget.getControls().entrySet()) {
175            String key = ctrl.getKey();
176            String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
177            String value = "#{" + RenderVariables.widgetVariables.widget.name() + ".controls." + key + "}";
178            variables.put(name, eFactory.createValueExpression(ctx, value, Object.class));
179        }
180        return variables;
181    }
182
183    protected void addFieldVariable(Map<String, ValueExpression> variables, FaceletContext ctx, Widget widget,
184            FieldDefinition fieldDef, Integer index) {
185        String computedName;
186        if (index == null) {
187            computedName = RenderVariables.widgetVariables.field.name();
188        } else {
189            computedName = RenderVariables.widgetVariables.field.name() + "_" + index;
190        }
191        String computedValue = ValueExpressionHelper.createExpressionString(widget.getValueName(), fieldDef);
192
193        ExpressionFactory eFactory = ctx.getExpressionFactory();
194        variables.put(computedName, eFactory.createValueExpression(ctx, computedValue, Object.class));
195    }
196
197    /**
198     * Returns the "template" property value, looking up on the widget definition definition first, and on the widget
199     * type definition if not found.
200     */
201    protected String getTemplateValue(Widget widget) {
202        return lookupProperty(TEMPLATE_PROPERTY_NAME, widget);
203    }
204
205    /**
206     * Helper method to retrieve a property value, looking up on the widget definition first, and on the widget type
207     * definition if not found.
208     *
209     * @since 7.2
210     */
211    protected String lookupProperty(String name, Widget widget) {
212        // lookup in the widget configuration
213        String val = (String) widget.getProperty(name);
214        if (val == null) {
215            // lookup in the widget type configuration
216            val = getProperty(name);
217        }
218        return val;
219    }
220
221    /**
222     * Returns the "bindValueIfNoField" property value, looking up on the widget type definition first, and on the
223     * widget definition if not found.
224     *
225     * @since 5.6
226     * @param widget
227     * @return
228     */
229    protected boolean getBindValueIfNoFieldValue(Widget widget) {
230        Object value = getProperty(BIND_VALUE_IF_NO_FIELD_PROPERTY_NAME);
231        if (value == null) {
232            value = widget.getProperty(BIND_VALUE_IF_NO_FIELD_PROPERTY_NAME);
233        }
234        if (value == null) {
235            return false;
236        }
237        if (value instanceof Boolean) {
238            return Boolean.TRUE.equals(value);
239        }
240        return Boolean.TRUE.equals(Boolean.valueOf(value.toString()));
241
242    }
243
244    /**
245     * Returns the template attribute.
246     */
247    protected TagAttribute getTemplateAttribute(FaceletHandlerHelper helper) {
248        // do not return anything as it will be computed from the widget
249        // properties anyway.
250        return null;
251    }
252
253}