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: WidgetTagHandler.java 30553 2008-02-24 15:51:31Z 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.ExpressionFactory;
034import javax.el.ValueExpression;
035import javax.el.VariableMapper;
036import javax.faces.FacesException;
037import javax.faces.component.UIComponent;
038import javax.faces.view.facelets.FaceletContext;
039import javax.faces.view.facelets.FaceletHandler;
040import javax.faces.view.facelets.MetaRuleset;
041import javax.faces.view.facelets.MetaTagHandler;
042import javax.faces.view.facelets.TagAttribute;
043import javax.faces.view.facelets.TagConfig;
044import javax.faces.view.facelets.TagException;
045
046import org.apache.commons.logging.Log;
047import org.apache.commons.logging.LogFactory;
048import org.jboss.el.ValueExpressionLiteral;
049import org.nuxeo.ecm.platform.forms.layout.api.Widget;
050import org.nuxeo.ecm.platform.forms.layout.api.WidgetDefinition;
051import org.nuxeo.ecm.platform.forms.layout.facelets.dev.DevTagHandler;
052import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager;
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 tag handler.
061 * <p>
062 * Applies {@link WidgetTypeHandler} found for given widget, in given mode and for given value.
063 *
064 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
065 */
066public class WidgetTagHandler extends MetaTagHandler {
067
068    private static final Log log = LogFactory.getLog(WidgetTagHandler.class);
069
070    protected final TagConfig config;
071
072    protected final TagAttribute widget;
073
074    /**
075     * @since 5.6
076     */
077    protected final TagAttribute name;
078
079    /**
080     * @since 5.6
081     */
082    protected final TagAttribute category;
083
084    /**
085     * @since 5.6
086     */
087    protected final TagAttribute definition;
088
089    /**
090     * @since 5.6
091     */
092    protected final TagAttribute mode;
093
094    /**
095     * @since 5.6
096     */
097    protected final TagAttribute layoutName;
098
099    /**
100     * @since 5.7
101     */
102    protected final TagAttribute resolveOnly;
103
104    protected final TagAttribute value;
105
106    protected final TagAttribute[] vars;
107
108    protected final String[] reservedVarsArray = { "id", "widget", "name", "category", "definition", "mode",
109            "layoutName", "value", "resolveOnly" };
110
111    public WidgetTagHandler(TagConfig config) {
112        super(config);
113        this.config = config;
114
115        widget = getAttribute("widget");
116        name = getAttribute("name");
117        definition = getAttribute("definition");
118        category = getAttribute("category");
119        mode = getAttribute("mode");
120        layoutName = getAttribute("layoutName");
121        resolveOnly = getAttribute("resolveOnly");
122
123        value = getAttribute("value");
124        vars = tag.getAttributes().getAll();
125
126        // additional checks
127        if (name == null && widget == null && definition == null) {
128            throw new TagException(this.tag, "At least one of attributes 'name', 'widget' "
129                    + "or 'definition' is required");
130        }
131        if (widget == null && (name != null || definition != null)) {
132            if (mode == null) {
133                throw new TagException(this.tag, "Attribute 'mode' is required when using attribute"
134                        + " 'name' or 'definition' so that the " + "widget instance " + "can be resolved");
135            }
136        }
137    }
138
139    /**
140     * Renders given widget resolving its {@link FaceletHandler} from {@link WebLayoutManager} configuration.
141     * <p>
142     * Variables exposed: {@link RenderVariables.globalVariables#value}, same variable suffixed with "_n" where n is the
143     * widget level, and {@link RenderVariables.globalVariables#document}.
144     */
145    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException {
146        // compute value name to set on widget instance in case it's changed
147        // from first computation
148        String valueName = null;
149        if (value != null) {
150            valueName = value.getValue();
151        }
152        if (ComponentTagUtils.isStrictValueReference(valueName)) {
153            valueName = ComponentTagUtils.getBareValueName(valueName);
154        }
155
156        // build handler
157        boolean widgetInstanceBuilt = false;
158        Widget widgetInstance = null;
159        if (widget != null) {
160            widgetInstance = (Widget) widget.getObject(ctx, Widget.class);
161            if (widgetInstance != null && valueName != null) {
162                widgetInstance.setValueName(valueName);
163            }
164        } else {
165            // resolve widget according to name and mode (and optional
166            // category)
167            WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
168
169            String modeValue = mode.getValue(ctx);
170            String layoutNameValue = null;
171            if (layoutName != null) {
172                layoutNameValue = layoutName.getValue(ctx);
173            }
174
175            if (name != null) {
176                String nameValue = name.getValue(ctx);
177                String catValue = null;
178                if (category != null) {
179                    catValue = category.getValue(ctx);
180                }
181                widgetInstance = layoutService.getWidget(ctx, nameValue, catValue, modeValue, valueName,
182                        layoutNameValue);
183                widgetInstanceBuilt = true;
184            } else if (definition != null) {
185                WidgetDefinition widgetDef = (WidgetDefinition) definition.getObject(ctx, WidgetDefinition.class);
186                if (widgetDef != null) {
187                    widgetInstance = layoutService.getWidget(ctx, widgetDef, modeValue, valueName, layoutNameValue);
188                    widgetInstanceBuilt = true;
189                }
190            }
191
192        }
193        if (widgetInstance != null) {
194            // add additional properties put on tag
195            String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_";
196            List<String> reservedVars = Arrays.asList(reservedVarsArray);
197            for (TagAttribute var : vars) {
198                String localName = var.getLocalName();
199                if (!reservedVars.contains(localName)) {
200                    if (localName != null && localName.startsWith(widgetPropertyMarker)) {
201                        localName = localName.substring(widgetPropertyMarker.length());
202                    }
203                    widgetInstance.setProperty(localName, var.getValue());
204                }
205            }
206            VariableMapper orig = ctx.getVariableMapper();
207            if (widgetInstanceBuilt) {
208                // expose widget variable to the context as layout row has not done it already, and set unique id on
209                // widget before exposing it to the context
210                FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
211                WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false);
212
213                VariableMapper vm = new VariableMapperWrapper(orig);
214                ctx.setVariableMapper(vm);
215                ExpressionFactory eFactory = ctx.getExpressionFactory();
216                ValueExpression widgetVe = eFactory.createValueExpression(widgetInstance, Widget.class);
217                vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
218                // expose widget controls too
219                for (Map.Entry<String, Serializable> ctrl : widgetInstance.getControls().entrySet()) {
220                    String key = ctrl.getKey();
221                    String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
222                    String value = "#{" + RenderVariables.widgetVariables.widget.name() + ".controls." + key + "}";
223                    vm.setVariable(name, eFactory.createValueExpression(ctx, value, Object.class));
224                }
225            }
226
227            try {
228                boolean resolveOnlyBool = false;
229                if (resolveOnly != null) {
230                    resolveOnlyBool = resolveOnly.getBoolean(ctx);
231                }
232
233                if (resolveOnlyBool) {
234                    nextHandler.apply(ctx, parent);
235                } else {
236                    applyWidgetHandler(ctx, parent, config, widgetInstance, value, true, nextHandler);
237                }
238            } finally {
239                ctx.setVariableMapper(orig);
240            }
241        }
242    }
243
244    public static void generateWidgetIdsRecursive(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget) {
245        generateWidgetId(ctx, helper, widget, true);
246    }
247
248    /**
249     * @since 7.2
250     */
251    public static void generateWidgetId(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
252            boolean recursive) {
253        if (widget == null) {
254            return;
255        }
256        widget.setId(FaceletHandlerHelper.generateWidgetId(ctx, widget.getName()));
257        if (recursive) {
258            Widget[] subWidgets = widget.getSubWidgets();
259            if (subWidgets != null) {
260                for (Widget subWidget : subWidgets) {
261                    generateWidgetIdsRecursive(ctx, helper, subWidget);
262                }
263            }
264        }
265    }
266
267    public static void applyWidgetHandler(FaceletContext ctx, UIComponent parent, TagConfig config, Widget widget,
268            TagAttribute value, boolean fillVariables, FaceletHandler nextHandler) throws IOException {
269        if (widget == null) {
270            return;
271        }
272
273        FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
274
275        TagConfig wtConfig = TagConfigFactory.createTagConfig(config, widget.getTagConfigId(), null, nextHandler);
276        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
277        WidgetTypeHandler handler = layoutService.getWidgetTypeHandler(wtConfig, widget);
278
279        if (handler == null) {
280            String widgetTypeName = widget.getType();
281            String widgetTypeCategory = widget.getTypeCategory();
282            String message = String.format("No widget handler found for type '%s' in category '%s'", widgetTypeName,
283                    widgetTypeCategory);
284            log.error(message);
285            FaceletHandler h = helper.getErrorComponentHandler(null, message);
286            h.apply(ctx, parent);
287            return;
288        }
289
290        FaceletHandler fh = handler;
291        if (FaceletHandlerHelper.isDevModeEnabled(ctx)) {
292            // decorate handler with dev handler
293            FaceletHandler devHandler = handler.getDevFaceletHandler(config, widget);
294            if (devHandler != null) {
295                // expose the widget variable to sub dev handler
296                String widgetTagConfigId = widget.getTagConfigId();
297                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
298                ExpressionFactory eFactory = ctx.getExpressionFactory();
299                ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
300                variables.put(RenderVariables.widgetVariables.widget.name(), widgetVe);
301                List<String> blockedPatterns = new ArrayList<String>();
302                blockedPatterns.add(RenderVariables.widgetVariables.widget.name() + "*");
303                FaceletHandler devAliasHandler = helper.getAliasFaceletHandler(widgetTagConfigId, variables,
304                        blockedPatterns, devHandler);
305                String refId = widget.getName();
306                fh = new DevTagHandler(config, refId, handler, devAliasHandler);
307            }
308        }
309
310        if (fillVariables) {
311            // expose widget variables
312            Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
313
314            ValueExpression valueExpr;
315            if (value == null) {
316                valueExpr = new ValueExpressionLiteral(null, Object.class);
317            } else {
318                valueExpr = value.getValueExpression(ctx, Object.class);
319            }
320
321            variables.put(RenderVariables.globalVariables.value.name(), valueExpr);
322            variables.put(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr);
323
324            FaceletHandler handlerWithVars = helper.getAliasFaceletHandler(widget.getTagConfigId(), variables, null, fh);
325            // apply
326            handlerWithVars.apply(ctx, parent);
327
328        } else {
329            // just apply
330            fh.apply(ctx, parent);
331        }
332    }
333
334    @Override
335    @SuppressWarnings("rawtypes")
336    protected MetaRuleset createMetaRuleset(Class type) {
337        return null;
338    }
339
340}