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.binding.BlockingVariableMapper;
054import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory;
055import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
056import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer;
057import org.nuxeo.runtime.api.Framework;
058
059import com.sun.faces.facelets.el.VariableMapperWrapper;
060
061/**
062 * Widget tag handler.
063 * <p>
064 * Applies {@link WidgetTypeHandler} found for given widget, in given mode and for given value.
065 *
066 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
067 */
068public class WidgetTagHandler extends MetaTagHandler {
069
070    private static final Log log = LogFactory.getLog(WidgetTagHandler.class);
071
072    protected final TagConfig config;
073
074    protected final TagAttribute widget;
075
076    /**
077     * @since 5.6
078     */
079    protected final TagAttribute name;
080
081    /**
082     * @since 5.6
083     */
084    protected final TagAttribute category;
085
086    /**
087     * @since 5.6
088     */
089    protected final TagAttribute definition;
090
091    /**
092     * @since 5.6
093     */
094    protected final TagAttribute mode;
095
096    /**
097     * @since 5.6
098     */
099    protected final TagAttribute layoutName;
100
101    /**
102     * @since 5.7
103     */
104    protected final TagAttribute resolveOnly;
105
106    protected final TagAttribute value;
107
108    protected final TagAttribute[] vars;
109
110    protected final String[] reservedVarsArray = { "id", "widget", "name", "category", "definition", "mode",
111            "layoutName", "value", "resolveOnly" };
112
113    public WidgetTagHandler(TagConfig config) {
114        super(config);
115        this.config = config;
116
117        widget = getAttribute("widget");
118        name = getAttribute("name");
119        definition = getAttribute("definition");
120        category = getAttribute("category");
121        mode = getAttribute("mode");
122        layoutName = getAttribute("layoutName");
123        resolveOnly = getAttribute("resolveOnly");
124
125        value = getAttribute("value");
126        vars = tag.getAttributes().getAll();
127
128        // additional checks
129        if (name == null && widget == null && definition == null) {
130            throw new TagException(this.tag,
131                    "At least one of attributes 'name', 'widget' " + "or 'definition' is required");
132        }
133        if (widget == null && (name != null || definition != null)) {
134            if (mode == null) {
135                throw new TagException(this.tag, "Attribute 'mode' is required when using attribute"
136                        + " 'name' or 'definition' so that the " + "widget instance " + "can be resolved");
137            }
138        }
139    }
140
141    /**
142     * Renders given widget resolving its {@link FaceletHandler} from {@link WebLayoutManager} configuration.
143     * <p>
144     * Variables exposed: {@link RenderVariables.globalVariables#value}, same variable suffixed with "_n" where n is the
145     * widget level, and {@link RenderVariables.globalVariables#document}.
146     */
147    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, ELException {
148        long start = FaceletDebugTracer.start();
149        Widget widgetInstance = null;
150
151        try {
152            // compute value name to set on widget instance in case it's changed
153            // from first computation
154            String valueName = null;
155            if (value != null) {
156                valueName = value.getValue();
157            }
158            if (ComponentTagUtils.isStrictValueReference(valueName)) {
159                valueName = ComponentTagUtils.getBareValueName(valueName);
160            }
161
162            // build handler
163            boolean widgetInstanceBuilt = false;
164            if (widget != null) {
165                widgetInstance = (Widget) widget.getObject(ctx, Widget.class);
166                if (widgetInstance != null && valueName != null) {
167                    widgetInstance.setValueName(valueName);
168                }
169            } else {
170                // resolve widget according to name and mode (and optional
171                // category)
172                WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
173
174                String modeValue = mode.getValue(ctx);
175                String layoutNameValue = null;
176                if (layoutName != null) {
177                    layoutNameValue = layoutName.getValue(ctx);
178                }
179
180                if (name != null) {
181                    String nameValue = name.getValue(ctx);
182                    String catValue = null;
183                    if (category != null) {
184                        catValue = category.getValue(ctx);
185                    }
186                    widgetInstance = layoutService.getWidget(ctx, nameValue, catValue, modeValue, valueName,
187                            layoutNameValue);
188                    widgetInstanceBuilt = true;
189                } else if (definition != null) {
190                    WidgetDefinition widgetDef = (WidgetDefinition) definition.getObject(ctx, WidgetDefinition.class);
191                    if (widgetDef != null) {
192                        widgetInstance = layoutService.getWidget(ctx, widgetDef, modeValue, valueName, layoutNameValue);
193                        widgetInstanceBuilt = true;
194                    }
195                }
196
197            }
198            if (widgetInstance != null) {
199                // add additional properties put on tag
200                String widgetPropertyMarker = RenderVariables.widgetVariables.widgetProperty.name() + "_";
201                List<String> reservedVars = Arrays.asList(reservedVarsArray);
202                for (TagAttribute var : vars) {
203                    String localName = var.getLocalName();
204                    if (!reservedVars.contains(localName)) {
205                        if (localName != null && localName.startsWith(widgetPropertyMarker)) {
206                            localName = localName.substring(widgetPropertyMarker.length());
207                        }
208                        widgetInstance.setProperty(localName, var.getValue());
209                    }
210                }
211
212                VariableMapper orig = ctx.getVariableMapper();
213                try {
214                    if (FaceletHandlerHelper.isAliasOptimEnabled()) {
215                        applyOptimized(ctx, orig, widgetInstance, widgetInstanceBuilt);
216                    } else {
217                        applyCompat(ctx, orig, widgetInstance, widgetInstanceBuilt);
218                    }
219
220                    boolean resolveOnlyBool = false;
221                    if (resolveOnly != null) {
222                        resolveOnlyBool = resolveOnly.getBoolean(ctx);
223                    }
224
225                    if (resolveOnlyBool) {
226                        nextHandler.apply(ctx, parent);
227                    } else {
228                        applyWidgetHandler(ctx, parent, config, widgetInstance, value, true, nextHandler);
229                    }
230                } finally {
231                    ctx.setVariableMapper(orig);
232                }
233            }
234        } finally {
235            FaceletDebugTracer.trace(start, config.getTag(), widgetInstance == null ? null : widgetInstance.getId());
236        }
237    }
238
239    public static void generateWidgetIdsRecursive(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget) {
240        generateWidgetId(ctx, helper, widget, true);
241    }
242
243    /**
244     * @since 7.2
245     */
246    public static void generateWidgetId(FaceletContext ctx, FaceletHandlerHelper helper, Widget widget,
247            boolean recursive) {
248        if (widget == null) {
249            return;
250        }
251        widget.setId(FaceletHandlerHelper.generateWidgetId(ctx, widget.getName()));
252        if (recursive) {
253            Widget[] subWidgets = widget.getSubWidgets();
254            if (subWidgets != null) {
255                for (Widget subWidget : subWidgets) {
256                    generateWidgetIdsRecursive(ctx, helper, subWidget);
257                }
258            }
259        }
260    }
261
262    protected void applyOptimized(FaceletContext ctx, VariableMapper orig, Widget widgetInstance,
263            boolean widgetInstanceBuilt) {
264        BlockingVariableMapper vm = new BlockingVariableMapper(orig);
265        ctx.setVariableMapper(vm);
266
267        if (widgetInstanceBuilt) {
268            // expose widget variable to the context as layout row has not done it already, and set unique id on
269            // widget before exposing it to the context
270            FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
271            WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false);
272            exposeWidgetVariables(ctx, vm, widgetInstance, null, false);
273        }
274
275    }
276
277    protected void applyCompat(FaceletContext ctx, VariableMapper orig, Widget widgetInstance,
278            boolean widgetInstanceBuilt) {
279        if (!widgetInstanceBuilt) {
280            return;
281        }
282        // expose widget variable to the context as layout row has not done it already, and set unique id on
283        // widget before exposing it to the context
284        FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
285        WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false);
286
287        VariableMapper vm = new VariableMapperWrapper(orig);
288        ctx.setVariableMapper(vm);
289        ExpressionFactory eFactory = ctx.getExpressionFactory();
290        ValueExpression widgetVe = eFactory.createValueExpression(widgetInstance, Widget.class);
291        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
292        // expose widget controls too
293        for (Map.Entry<String, Serializable> ctrl : widgetInstance.getControls().entrySet()) {
294            String key = ctrl.getKey();
295            String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
296            String value = "#{" + RenderVariables.widgetVariables.widget.name() + ".controls." + key + "}";
297            vm.setVariable(name, eFactory.createValueExpression(ctx, value, Object.class));
298        }
299    }
300
301    public static void applyWidgetHandler(FaceletContext ctx, UIComponent parent, TagConfig config, Widget widget,
302            TagAttribute value, boolean fillVariables, FaceletHandler nextHandler) throws IOException {
303        if (widget == null) {
304            return;
305        }
306
307        FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
308
309        TagConfig wtConfig = TagConfigFactory.createTagConfig(config, widget.getTagConfigId(), null, nextHandler);
310        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
311        WidgetTypeHandler handler = layoutService.getWidgetTypeHandler(wtConfig, widget);
312
313        if (handler == null) {
314            String widgetTypeName = widget.getType();
315            String widgetTypeCategory = widget.getTypeCategory();
316            String message = String.format("No widget handler found for type '%s' in category '%s'", widgetTypeName,
317                    widgetTypeCategory);
318            log.error(message);
319            FaceletHandler h = helper.getErrorComponentHandler(null, message);
320            h.apply(ctx, parent);
321            return;
322        }
323
324        FaceletHandler fh = handler;
325        if (FaceletHandlerHelper.isDevModeEnabled(ctx)) {
326            // decorate handler with dev handler
327            FaceletHandler devHandler = handler.getDevFaceletHandler(config, widget);
328            if (devHandler != null) {
329                // expose the widget variable to sub dev handler
330                String widgetTagConfigId = widget.getTagConfigId();
331                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
332                ExpressionFactory eFactory = ctx.getExpressionFactory();
333                ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
334                variables.put(RenderVariables.widgetVariables.widget.name(), widgetVe);
335                List<String> blockedPatterns = new ArrayList<String>();
336                blockedPatterns.add(RenderVariables.widgetVariables.widget.name() + "*");
337                FaceletHandler devAliasHandler = helper.getAliasFaceletHandler(widgetTagConfigId, variables,
338                        blockedPatterns, devHandler);
339                String refId = widget.getName();
340                fh = new DevTagHandler(config, refId, handler, devAliasHandler);
341            }
342        }
343
344        if (FaceletHandlerHelper.isAliasOptimEnabled()) {
345            if (fillVariables) {
346                // expose widget variables
347                VariableMapper cvm = ctx.getVariableMapper();
348                if (!(cvm instanceof BlockingVariableMapper)) {
349                    throw new IllegalArgumentException(
350                            "Current context variable mapper should be an instance of MetaVariableMapper");
351                }
352                BlockingVariableMapper vm = (BlockingVariableMapper) cvm;
353                ValueExpression valueExpr;
354                if (value == null) {
355                    valueExpr = new ValueExpressionLiteral(null, Object.class);
356                } else {
357                    valueExpr = value.getValueExpression(ctx, Object.class);
358                }
359
360                vm.setVariable(RenderVariables.globalVariables.value.name(), valueExpr);
361                vm.setVariable(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr);
362            }
363            fh.apply(ctx, parent);
364        } else {
365            if (fillVariables) {
366                // expose widget variables
367                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
368
369                ValueExpression valueExpr;
370                if (value == null) {
371                    valueExpr = new ValueExpressionLiteral(null, Object.class);
372                } else {
373                    valueExpr = value.getValueExpression(ctx, Object.class);
374                }
375
376                variables.put(RenderVariables.globalVariables.value.name(), valueExpr);
377                variables.put(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr);
378
379                FaceletHandler handlerWithVars = helper.getAliasFaceletHandler(widget.getTagConfigId(), variables, null,
380                        fh);
381                // apply
382                handlerWithVars.apply(ctx, parent);
383
384            } else {
385                // just apply
386                fh.apply(ctx, parent);
387            }
388        }
389
390    }
391
392    public static void exposeWidgetVariables(FaceletContext ctx, BlockingVariableMapper vm, Widget widget,
393            Integer widgetIndex, boolean exposeLevel) {
394        ExpressionFactory eFactory = ctx.getExpressionFactory();
395        ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
396        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
397        vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name());
398
399        ValueExpression widgetIndexVe = null;
400        if (widgetIndex != null) {
401            widgetIndexVe = eFactory.createValueExpression(widgetIndex, Integer.class);
402            vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name(), widgetIndexVe);
403        }
404
405        if (exposeLevel && !FaceletHandlerHelper.isAliasOptimEnabled()) {
406            Integer level = null;
407            if (widget != null) {
408                level = widget.getLevel();
409            }
410            vm.setVariable(RenderVariables.widgetVariables.widget.name() + "_" + level, widgetVe);
411            if (widgetIndexVe != null) {
412                vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name() + "_" + level, widgetIndexVe);
413            }
414            vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name() + "_*");
415            vm.addBlockedPattern(RenderVariables.widgetVariables.widgetIndex.name() + "*");
416        }
417
418        // expose widget controls too
419        if (widget != null) {
420            for (Map.Entry<String, Serializable> ctrl : widget.getControls().entrySet()) {
421                String key = ctrl.getKey();
422                String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
423                ValueExpression ve = eFactory.createValueExpression(ctrl.getValue(), Object.class);
424                vm.setVariable(name, ve);
425            }
426        }
427        vm.addBlockedPattern(RenderVariables.widgetVariables.widgetControl.name() + "_*");
428    }
429
430    @Override
431    @SuppressWarnings("rawtypes")
432    protected MetaRuleset createMetaRuleset(Class type) {
433        return null;
434    }
435
436}