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        // expose widget variable to the context as layout row has not done it already, and set unique id on
280        // widget before exposing it to the context
281        FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
282        WidgetTagHandler.generateWidgetId(ctx, helper, widgetInstance, false);
283
284        VariableMapper vm = new VariableMapperWrapper(orig);
285        ctx.setVariableMapper(vm);
286        ExpressionFactory eFactory = ctx.getExpressionFactory();
287        ValueExpression widgetVe = eFactory.createValueExpression(widgetInstance, Widget.class);
288        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
289        // expose widget controls too
290        for (Map.Entry<String, Serializable> ctrl : widgetInstance.getControls().entrySet()) {
291            String key = ctrl.getKey();
292            String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
293            String value = "#{" + RenderVariables.widgetVariables.widget.name() + ".controls." + key + "}";
294            vm.setVariable(name, eFactory.createValueExpression(ctx, value, Object.class));
295        }
296    }
297
298    public static void applyWidgetHandler(FaceletContext ctx, UIComponent parent, TagConfig config, Widget widget,
299            TagAttribute value, boolean fillVariables, FaceletHandler nextHandler) throws IOException {
300        if (widget == null) {
301            return;
302        }
303
304        FaceletHandlerHelper helper = new FaceletHandlerHelper(config);
305
306        TagConfig wtConfig = TagConfigFactory.createTagConfig(config, widget.getTagConfigId(), null, nextHandler);
307        WebLayoutManager layoutService = Framework.getService(WebLayoutManager.class);
308        WidgetTypeHandler handler = layoutService.getWidgetTypeHandler(wtConfig, widget);
309
310        if (handler == null) {
311            String widgetTypeName = widget.getType();
312            String widgetTypeCategory = widget.getTypeCategory();
313            String message = String.format("No widget handler found for type '%s' in category '%s'", widgetTypeName,
314                    widgetTypeCategory);
315            log.error(message);
316            FaceletHandler h = helper.getErrorComponentHandler(null, message);
317            h.apply(ctx, parent);
318            return;
319        }
320
321        FaceletHandler fh = handler;
322        if (FaceletHandlerHelper.isDevModeEnabled(ctx)) {
323            // decorate handler with dev handler
324            FaceletHandler devHandler = handler.getDevFaceletHandler(config, widget);
325            if (devHandler != null) {
326                // expose the widget variable to sub dev handler
327                String widgetTagConfigId = widget.getTagConfigId();
328                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
329                ExpressionFactory eFactory = ctx.getExpressionFactory();
330                ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
331                variables.put(RenderVariables.widgetVariables.widget.name(), widgetVe);
332                List<String> blockedPatterns = new ArrayList<String>();
333                blockedPatterns.add(RenderVariables.widgetVariables.widget.name() + "*");
334                FaceletHandler devAliasHandler = helper.getAliasFaceletHandler(widgetTagConfigId, variables,
335                        blockedPatterns, devHandler);
336                String refId = widget.getName();
337                fh = new DevTagHandler(config, refId, handler, devAliasHandler);
338            }
339        }
340
341        if (FaceletHandlerHelper.isAliasOptimEnabled()) {
342            if (fillVariables) {
343                // expose widget variables
344                VariableMapper cvm = ctx.getVariableMapper();
345                if (!(cvm instanceof BlockingVariableMapper)) {
346                    throw new IllegalArgumentException(
347                            "Current context variable mapper should be an instance of MetaVariableMapper");
348                }
349                BlockingVariableMapper vm = (BlockingVariableMapper) cvm;
350                ValueExpression valueExpr;
351                if (value == null) {
352                    valueExpr = new ValueExpressionLiteral(null, Object.class);
353                } else {
354                    valueExpr = value.getValueExpression(ctx, Object.class);
355                }
356
357                vm.setVariable(RenderVariables.globalVariables.value.name(), valueExpr);
358                vm.setVariable(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr);
359            }
360            fh.apply(ctx, parent);
361        } else {
362            if (fillVariables) {
363                // expose widget variables
364                Map<String, ValueExpression> variables = new HashMap<String, ValueExpression>();
365
366                ValueExpression valueExpr;
367                if (value == null) {
368                    valueExpr = new ValueExpressionLiteral(null, Object.class);
369                } else {
370                    valueExpr = value.getValueExpression(ctx, Object.class);
371                }
372
373                variables.put(RenderVariables.globalVariables.value.name(), valueExpr);
374                variables.put(RenderVariables.globalVariables.value.name() + "_" + widget.getLevel(), valueExpr);
375
376                FaceletHandler handlerWithVars = helper.getAliasFaceletHandler(widget.getTagConfigId(), variables, null,
377                        fh);
378                // apply
379                handlerWithVars.apply(ctx, parent);
380
381            } else {
382                // just apply
383                fh.apply(ctx, parent);
384            }
385        }
386
387    }
388
389    public static void exposeWidgetVariables(FaceletContext ctx, BlockingVariableMapper vm, Widget widget,
390            Integer widgetIndex, boolean exposeLevel) {
391        ExpressionFactory eFactory = ctx.getExpressionFactory();
392        ValueExpression widgetVe = eFactory.createValueExpression(widget, Widget.class);
393        vm.setVariable(RenderVariables.widgetVariables.widget.name(), widgetVe);
394        vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name());
395
396        ValueExpression widgetIndexVe = null;
397        if (widgetIndex != null) {
398            widgetIndexVe = eFactory.createValueExpression(widgetIndex, Integer.class);
399            vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name(), widgetIndexVe);
400        }
401
402        if (exposeLevel && !FaceletHandlerHelper.isAliasOptimEnabled()) {
403            Integer level = null;
404            if (widget != null) {
405                level = widget.getLevel();
406            }
407            vm.setVariable(RenderVariables.widgetVariables.widget.name() + "_" + level, widgetVe);
408            if (widgetIndexVe != null) {
409                vm.setVariable(RenderVariables.widgetVariables.widgetIndex.name() + "_" + level, widgetIndexVe);
410            }
411            vm.addBlockedPattern(RenderVariables.widgetVariables.widget.name() + "_*");
412            vm.addBlockedPattern(RenderVariables.widgetVariables.widgetIndex.name() + "*");
413        }
414
415        // expose widget controls too
416        if (widget != null) {
417            for (Map.Entry<String, Serializable> ctrl : widget.getControls().entrySet()) {
418                String key = ctrl.getKey();
419                String name = RenderVariables.widgetVariables.widgetControl.name() + "_" + key;
420                ValueExpression ve = eFactory.createValueExpression(ctrl.getValue(), Object.class);
421                vm.setVariable(name, ve);
422            }
423        }
424        vm.addBlockedPattern(RenderVariables.widgetVariables.widgetControl.name() + "_*");
425    }
426
427    @Override
428    @SuppressWarnings("rawtypes")
429    protected MetaRuleset createMetaRuleset(Class type) {
430        return null;
431    }
432
433}