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