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: FaceletHandlerHelper.java 30553 2008-02-24 15:51:31Z atchertchian $
018 */
019
020package org.nuxeo.ecm.platform.forms.layout.facelets;
021
022import java.io.Serializable;
023import java.util.ArrayList;
024import java.util.Arrays;
025import java.util.Collections;
026import java.util.HashMap;
027import java.util.List;
028import java.util.Map;
029import java.util.regex.Matcher;
030import java.util.regex.Pattern;
031
032import javax.el.ExpressionFactory;
033import javax.el.ValueExpression;
034import javax.faces.component.html.HtmlMessage;
035import javax.faces.component.html.HtmlOutputText;
036import javax.faces.context.FacesContext;
037import javax.faces.convert.Converter;
038import javax.faces.validator.Validator;
039import javax.faces.view.facelets.ComponentConfig;
040import javax.faces.view.facelets.ComponentHandler;
041import javax.faces.view.facelets.ConverterConfig;
042import javax.faces.view.facelets.ConverterHandler;
043import javax.faces.view.facelets.FaceletContext;
044import javax.faces.view.facelets.FaceletHandler;
045import javax.faces.view.facelets.TagAttribute;
046import javax.faces.view.facelets.TagAttributes;
047import javax.faces.view.facelets.TagConfig;
048import javax.faces.view.facelets.ValidatorConfig;
049import javax.faces.view.facelets.ValidatorHandler;
050
051import org.apache.commons.logging.Log;
052import org.apache.commons.logging.LogFactory;
053import org.nuxeo.ecm.platform.forms.layout.actions.NuxeoLayoutManagerBean;
054import org.nuxeo.ecm.platform.forms.layout.api.FieldDefinition;
055import org.nuxeo.ecm.platform.forms.layout.api.Widget;
056import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOption;
057import org.nuxeo.ecm.platform.forms.layout.api.WidgetSelectOptions;
058import org.nuxeo.ecm.platform.forms.layout.service.WebLayoutManager;
059import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasTagHandler;
060import org.nuxeo.ecm.platform.ui.web.tag.fn.Functions;
061import org.nuxeo.ecm.platform.ui.web.tag.handler.GenericHtmlComponentHandler;
062import org.nuxeo.ecm.platform.ui.web.tag.handler.SetTagHandler;
063import org.nuxeo.ecm.platform.ui.web.tag.handler.TagConfigFactory;
064import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
065import org.nuxeo.runtime.api.Framework;
066
067import com.sun.faces.facelets.tag.TagAttributeImpl;
068import com.sun.faces.facelets.tag.TagAttributesImpl;
069
070/**
071 * Helpers for layout/widget handlers.
072 * <p>
073 * Helps generating custom tag handlers and custom tag attributes.
074 *
075 * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
076 */
077public final class FaceletHandlerHelper {
078
079    private static final Log log = LogFactory.getLog(FaceletHandlerHelper.class);
080
081    public static final String LAYOUT_ID_PREFIX = "nxl_";
082
083    public static final String WIDGET_ID_PREFIX = "nxw_";
084
085    public static final String MESSAGE_ID_SUFFIX = "_message";
086
087    /**
088     * @since 6.0
089     */
090    public static final String DEV_CONTAINER_ID_SUFFIX = "_dev_container";
091
092    /**
093     * @since 6.0
094     */
095    public static final String DEV_REGION_ID_SUFFIX = "_dev_region";
096
097    /**
098     * @since 6.0
099     */
100    public static String DEV_MODE_DISABLED_VARIABLE = "nuxeoLayoutDevModeDisabled";
101
102    private static final Pattern UNIQUE_ID_STRIP_PATTERN = Pattern.compile("(.*)(_[0-9]+)");
103
104    /**
105     * @since 5.7
106     */
107    public static final String DIR_PROPERTY = "dir";
108
109    /**
110     * @since 5.7
111     */
112    public static final String DIR_AUTO = "auto";
113
114    final FaceletContext context;
115
116    final TagConfig tagConfig;
117
118    public FaceletHandlerHelper(FaceletContext context, TagConfig tagConfig) {
119        this.context = context;
120        this.tagConfig = tagConfig;
121    }
122
123    /**
124     * Returns a id unique within the facelet context.
125     */
126    public String generateUniqueId() {
127        String id;
128        TagAttribute idAttr = tagConfig.getTag().getAttributes().get("id");
129        if (idAttr != null) {
130            id = idAttr.getValue(context);
131        } else {
132            id = context.getFacesContext().getViewRoot().createUniqueId();
133        }
134        return generateUniqueId(id);
135    }
136
137    /**
138     * Returns a id unique within the facelet context using given id as base.
139     */
140    public String generateUniqueId(String base) {
141        FacesContext faces = context.getFacesContext();
142        NuxeoLayoutIdManagerBean bean = lookupIdBean(faces);
143        return bean.generateUniqueId(base);
144    }
145
146    protected static NuxeoLayoutIdManagerBean lookupIdBean(FacesContext ctx) {
147        String expr = "#{" + NuxeoLayoutIdManagerBean.NAME + "}";
148        NuxeoLayoutIdManagerBean bean = (NuxeoLayoutIdManagerBean) ctx.getApplication().evaluateExpressionGet(ctx,
149                expr, Object.class);
150        if (bean == null) {
151            throw new RuntimeException("Managed bean not found: " + expr);
152        }
153        return bean;
154    }
155
156    /**
157     * Strips given base of any ending counter that would conflict with potential already generated unique ids
158     *
159     * @since 5.7
160     */
161    protected static String stripUniqueIdBase(String base) {
162        if (base != null) {
163            Matcher m = UNIQUE_ID_STRIP_PATTERN.matcher(base);
164            if (m.matches()) {
165                base = m.group(1);
166                return stripUniqueIdBase(base);
167            }
168        }
169        return base;
170    }
171
172    /**
173     * Generates a unique id from counters persisted in given map
174     *
175     * @since 5.7
176     * @deprecated since 7.2, see {@link NuxeoLayoutIdManagerBean}
177     */
178    @Deprecated
179    public static String generateUniqueId(String base, Map<String, Integer> counters) {
180        // strip base of any remnant counter name
181        base = stripUniqueIdBase(base);
182        // increment in map
183        Integer cnt = counters.get(base);
184        if (cnt == null) {
185            counters.put(base, new Integer(0));
186            return base;
187        } else {
188            int i = cnt.intValue() + 1;
189            counters.put(base, new Integer(i));
190            return base + "_" + i;
191        }
192    }
193
194    /**
195     * @throws IllegalArgumentException if the given string is null or empty.
196     */
197    protected static String generateValidIdString(String base) {
198        if (base == null) {
199            throw new IllegalArgumentException(base);
200        }
201        int n = base.length();
202        if (n < 1) {
203            throw new IllegalArgumentException(base);
204        }
205        return Functions.jsfTagIdEscape(base);
206    }
207
208    public String generateWidgetId(String widgetName) {
209        return generateUniqueId(WIDGET_ID_PREFIX + widgetName);
210    }
211
212    public String generateLayoutId(String layoutName) {
213        return generateUniqueId(LAYOUT_ID_PREFIX + layoutName);
214    }
215
216    public String generateMessageId(String widgetName) {
217        return generateUniqueId(WIDGET_ID_PREFIX + widgetName + MESSAGE_ID_SUFFIX);
218    }
219
220    /**
221     * @since 6.0
222     */
223    public String generateDevRegionId(String widgetName) {
224        return generateUniqueId(WIDGET_ID_PREFIX + widgetName + DEV_REGION_ID_SUFFIX);
225    }
226
227    /**
228     * @since 6.0
229     */
230    public String generateDevContainerId(String widgetName) {
231        return generateUniqueId(WIDGET_ID_PREFIX + widgetName + DEV_CONTAINER_ID_SUFFIX);
232    }
233
234    /**
235     * Creates a unique id and returns corresponding attribute, using given string id as base.
236     */
237    public TagAttribute createIdAttribute(String base) {
238        String value = generateUniqueId(base);
239        return new TagAttributeImpl(tagConfig.getTag().getLocation(), "", "id", "id", value);
240    }
241
242    /**
243     * Creates an attribute with given name and value.
244     * <p>
245     * The attribute namespace is assumed to be empty.
246     */
247    public TagAttribute createAttribute(String name, String value) {
248        if (value == null || value instanceof String) {
249            return new TagAttributeImpl(tagConfig.getTag().getLocation(), "", name, name, value);
250        }
251        return null;
252    }
253
254    /**
255     * Returns true if a reference tag attribute should be created for given property value.
256     * <p>
257     * Reference tag attributes are using a non-literal EL expression so that this property value is not kept (cached)
258     * in the component on ajax refresh.
259     * <p>
260     * Of course property values already representing an expression cannot be mapped as is because they would need to be
261     * resolved twice.
262     * <p>
263     * Converters and validators cannot be referenced either because components expect corresponding value expressions
264     * to resolve to a {@link Converter} or {@link Validator} instance (instead of the converter of validator id).
265     */
266    public boolean shouldCreateReferenceAttribute(String key, Serializable value) {
267        // FIXME: NXP-7004: make this configurable per widget type and mode or
268        // JSF component
269        if ((value instanceof String)
270                && (ComponentTagUtils.isValueReference((String) value) || "converter".equals(key)
271                        || "validator".equals(key)
272                        // size is mistaken for the properties map size because
273                        // of jboss el resolvers
274                        || "size".equals(key)
275                        // richfaces calendar does not resolve EL expressions
276                        // correctly
277                        || "showApplyButton".equals(key) || "defaultTime".equals(key))) {
278            return false;
279        }
280        return true;
281    }
282
283    public static TagAttributes getTagAttributes(TagAttribute... attributes) {
284        if (attributes == null || attributes.length == 0) {
285            return new TagAttributesImpl(new TagAttribute[0]);
286        }
287        return new TagAttributesImpl(attributes);
288    }
289
290    public static TagAttributes getTagAttributes(List<TagAttribute> attributes) {
291        return getTagAttributes(attributes.toArray(new TagAttribute[0]));
292    }
293
294    public static TagAttributes addTagAttribute(TagAttributes orig, TagAttribute newAttr) {
295        if (orig == null) {
296            return new TagAttributesImpl(new TagAttribute[] { newAttr });
297        }
298        List<TagAttribute> allAttrs = new ArrayList<TagAttribute>(Arrays.asList(orig.getAll()));
299        allAttrs.add(newAttr);
300        return getTagAttributes(allAttrs);
301    }
302
303    /**
304     * Copies tag attributes with given names from the tag config, using given id as base for the id attribute.
305     */
306    public TagAttributes copyTagAttributes(String id, String... names) {
307        List<TagAttribute> list = new ArrayList<TagAttribute>();
308        list.add(createIdAttribute(id));
309        for (String name : names) {
310            if ("id".equals(name)) {
311                // ignore
312                continue;
313            }
314            TagAttribute attr = tagConfig.getTag().getAttributes().get(name);
315            if (attr != null) {
316                list.add(attr);
317            }
318        }
319        TagAttribute[] attrs = list.toArray(new TagAttribute[list.size()]);
320        return new TagAttributesImpl(attrs);
321    }
322
323    /**
324     * Creates tag attributes using given widget properties and field definitions.
325     * <p>
326     * Assumes the "value" attribute has to be computed from the first field definition, using the "value" expression
327     * (see widget type tag handler exposed values).
328     */
329    public TagAttributes getTagAttributes(String id, Widget widget) {
330        // add id and value computed from fields
331        TagAttributes widgetAttrs = getTagAttributes(widget);
332        return addTagAttribute(widgetAttrs, createAttribute("id", id));
333    }
334
335    public TagAttributes getTagAttributes(Widget widget) {
336        return getTagAttributes(widget, null, true);
337    }
338
339    /**
340     * @since 5.5
341     */
342    public TagAttributes getTagAttributes(Widget widget, List<String> excludedProperties,
343            boolean bindFirstFieldDefinition) {
344        return getTagAttributes(widget, excludedProperties, bindFirstFieldDefinition, false);
345    }
346
347    /**
348     * Return tag attributes for this widget, including value mapping from field definitions and properties
349     *
350     * @since 5.6
351     * @param widget the widget to generate tag attributes for
352     * @param excludedProperties the properties to exclude from tag attributes
353     * @param bindFirstFieldDefinition if true, the first field definition will be bound to the tag attribute named
354     *            "value"
355     * @param defaultToValue if true, and there are no field definitions, tag attribute named "value" will be mapped to
356     *            the current widget value name (e.g the layout value in most cases, or the parent widget value if
357     *            widget is a sub widget)
358     */
359    public TagAttributes getTagAttributes(Widget widget, List<String> excludedProperties,
360            boolean bindFirstFieldDefinition, boolean defaultToValue) {
361        List<TagAttribute> attrs = new ArrayList<TagAttribute>();
362        if (bindFirstFieldDefinition) {
363            FieldDefinition field = null;
364            FieldDefinition[] fields = widget.getFieldDefinitions();
365            if (fields != null && fields.length > 0) {
366                field = fields[0];
367            }
368            if (field != null || defaultToValue) {
369                // bind value to first field definition or current value name
370                TagAttribute valueAttr = createAttribute("value",
371                        ValueExpressionHelper.createExpressionString(widget.getValueName(), field));
372                attrs.add(valueAttr);
373            }
374        }
375        // fill with widget properties
376        List<TagAttribute> propertyAttrs = getTagAttributes(widget.getProperties(), excludedProperties, true,
377                widget.getType(), widget.getTypeCategory(), widget.getMode());
378        if (propertyAttrs != null) {
379            attrs.addAll(propertyAttrs);
380        }
381        return getTagAttributes(attrs);
382    }
383
384    /**
385     * @since 5.5, signature changed on 5.6 to include parameters widgetType and widgetMode.
386     */
387    public List<TagAttribute> getTagAttributes(Map<String, Serializable> properties, List<String> excludedProperties,
388            boolean useReferenceProperties, String widgetType, String widgetTypeCategory, String widgetMode) {
389        WebLayoutManager service = Framework.getService(WebLayoutManager.class);
390        List<TagAttribute> attrs = new ArrayList<TagAttribute>();
391        if (properties != null) {
392            for (Map.Entry<String, Serializable> prop : properties.entrySet()) {
393                TagAttribute attr;
394                String key = prop.getKey();
395                if (excludedProperties != null && excludedProperties.contains(key)) {
396                    continue;
397                }
398                Serializable valueInstance = prop.getValue();
399                if (!useReferenceProperties
400                        || !service.referencePropertyAsExpression(key, valueInstance, widgetType, widgetTypeCategory,
401                                widgetMode, null)) {
402                    if (valueInstance == null || valueInstance instanceof String) {
403                        // FIXME: this will not be updated correctly using ajax
404                        attr = createAttribute(key, (String) valueInstance);
405                    } else {
406                        attr = createAttribute(key, valueInstance.toString());
407                    }
408                } else {
409                    // create a reference so that it's a real expression
410                    // and it's not kept (cached) in a component value on
411                    // ajax refresh
412                    attr = createAttribute(key,
413                            String.format("#{%s.properties.%s}", RenderVariables.widgetVariables.widget.name(), key));
414                }
415                attrs.add(attr);
416            }
417        }
418        return attrs;
419    }
420
421    /**
422     * @since 6.0
423     */
424    public TagAttributes getTagAttributes(WidgetSelectOption selectOption, Map<String, Serializable> additionalProps) {
425        Map<String, Serializable> props = getSelectOptionProperties(selectOption);
426        if (additionalProps != null) {
427            props.putAll(additionalProps);
428        }
429        List<TagAttribute> attrs = getTagAttributes(props, null, false, null, null, null);
430        if (attrs == null) {
431            attrs = Collections.emptyList();
432        }
433        return getTagAttributes(attrs);
434    }
435
436    public TagAttributes getTagAttributes(WidgetSelectOption selectOption) {
437        return getTagAttributes(selectOption, null);
438    }
439
440    public Map<String, Serializable> getSelectOptionProperties(WidgetSelectOption selectOption) {
441        Map<String, Serializable> map = new HashMap<String, Serializable>();
442        if (selectOption != null) {
443            Serializable value = selectOption.getValue();
444            if (value != null) {
445                map.put("value", value);
446            }
447            String var = selectOption.getVar();
448            if (var != null) {
449                map.put("var", var);
450            }
451            String itemLabel = selectOption.getItemLabel();
452            if (itemLabel != null) {
453                map.put("itemLabel", itemLabel);
454            }
455            String itemValue = selectOption.getItemValue();
456            if (itemValue != null) {
457                map.put("itemValue", itemValue);
458            }
459            Serializable itemDisabled = selectOption.getItemDisabled();
460            if (itemDisabled != null) {
461                map.put("itemDisabled", itemDisabled);
462            }
463            Serializable itemRendered = selectOption.getItemRendered();
464            if (itemRendered != null) {
465                map.put("itemRendered", itemRendered);
466            }
467            if (selectOption instanceof WidgetSelectOptions) {
468                WidgetSelectOptions selectOptions = (WidgetSelectOptions) selectOption;
469                Boolean caseSensitive = selectOptions.getCaseSensitive();
470                if (caseSensitive != null) {
471                    map.put("caseSensitive", caseSensitive);
472                }
473                String ordering = selectOptions.getOrdering();
474                if (ordering != null) {
475                    map.put("ordering", ordering);
476                }
477            }
478        }
479        return map;
480    }
481
482    /**
483     * @deprecated since 5.4.2, use
484     *             {@link FaceletHandlerHelper#getHtmlComponentHandler(String, TagAttributes, FaceletHandler, String, String)}
485     *             instead.
486     */
487    @Deprecated
488    public ComponentHandler getHtmlComponentHandler(TagAttributes attributes, FaceletHandler nextHandler,
489            String componentType, String rendererType) {
490        return getHtmlComponentHandler(null, attributes, nextHandler, componentType, rendererType);
491    }
492
493    /**
494     * Returns an html component handler for this configuration.
495     * <p>
496     * Next handler cannot be null, use {@link org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler} if no next
497     * handler is needed.
498     */
499    public ComponentHandler getHtmlComponentHandler(String tagConfigId, TagAttributes attributes,
500            FaceletHandler nextHandler, String componentType, String rendererType) {
501        ComponentConfig config = TagConfigFactory.createComponentConfig(tagConfig, tagConfigId, attributes,
502                nextHandler, componentType, rendererType);
503        return new GenericHtmlComponentHandler(config);
504    }
505
506    /**
507     * @deprecated since 5.4.2, use {@link FaceletHandlerHelper#getErrorComponentHandler(String, String)} instead.
508     */
509    @Deprecated
510    public ComponentHandler getErrorComponentHandler(String errorMessage) {
511        return getErrorComponentHandler(null, errorMessage);
512    }
513
514    /**
515     * Component handler that displays an error on interface
516     */
517    public ComponentHandler getErrorComponentHandler(String tagConfigId, String errorMessage) {
518        FaceletHandler leaf = new org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler();
519        TagAttribute valueAttr = createAttribute("value", "<span style=\"color:red;font-weight:bold;\">ERROR: "
520                + errorMessage + "</span><br />");
521        TagAttribute escapeAttr = createAttribute("escape", "false");
522        ComponentHandler output = getHtmlComponentHandler(tagConfigId,
523                FaceletHandlerHelper.getTagAttributes(valueAttr, escapeAttr), leaf, HtmlOutputText.COMPONENT_TYPE, null);
524        return output;
525    }
526
527    /**
528     * @deprecated since 5.4.2, use
529     *             {@link FaceletHandlerHelper#getConvertHandler(String, TagAttributes, FaceletHandler, String)}
530     *             instead.
531     */
532    @Deprecated
533    public ConverterHandler getConvertHandler(TagAttributes attributes, FaceletHandler nextHandler, String converterId) {
534        return getConvertHandler(null, attributes, nextHandler, converterId);
535    }
536
537    /**
538     * Returns a convert handler for this configuration.
539     * <p>
540     * Next handler cannot be null, use {@link org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler} if no next
541     * handler is needed.
542     */
543    public ConverterHandler getConvertHandler(String tagConfigId, TagAttributes attributes, FaceletHandler nextHandler,
544            String converterId) {
545        ConverterConfig config = TagConfigFactory.createConverterConfig(tagConfig, tagConfigId, attributes,
546                nextHandler, converterId);
547        return new ConverterHandler(config);
548    }
549
550    /**
551     * @deprecated since 5.4.2, use
552     *             {@link FaceletHandlerHelper#getValidateHandler(String, TagAttributes, FaceletHandler, String)}
553     *             instead.
554     */
555    @Deprecated
556    public ValidatorHandler getValidateHandler(TagAttributes attributes, FaceletHandler nextHandler, String validatorId) {
557        return getValidateHandler(null, attributes, nextHandler, validatorId);
558    }
559
560    /**
561     * Returns a validate handler for this configuration.
562     * <p>
563     * Next handler cannot be null, use {@link org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler} if no next
564     * handler is needed.
565     */
566    public ValidatorHandler getValidateHandler(String tagConfigId, TagAttributes attributes,
567            FaceletHandler nextHandler, String validatorId) {
568        ValidatorConfig config = TagConfigFactory.createValidatorConfig(tagConfig, tagConfigId, attributes,
569                nextHandler, validatorId);
570        return new ValidatorHandler(config);
571    }
572
573    /**
574     * @deprecated since 5.4.2, use
575     *             {@link FaceletHandlerHelper#getMessageComponentHandler(String, String, String, String)} instead.
576     */
577    @Deprecated
578    public ComponentHandler getMessageComponentHandler(String id, String forId, String styleClass) {
579        return getMessageComponentHandler(null, id, forId, styleClass);
580    }
581
582    /**
583     * Returns a message component handler with given attributes.
584     * <p>
585     * Uses component type "javax.faces.HtmlMessage" and renderer type "javax.faces.Message".
586     */
587    public ComponentHandler getMessageComponentHandler(String tagConfigId, String id, String forId, String styleClass) {
588        TagAttribute forAttr = createAttribute("for", forId);
589        TagAttribute idAttr = createAttribute("id", id);
590        if (styleClass == null) {
591            // default style class
592            styleClass = "errorMessage";
593        }
594        TagAttribute styleAttr = createAttribute("styleClass", styleClass);
595        TagAttributes attributes = getTagAttributes(forAttr, idAttr, styleAttr);
596        ComponentConfig config = TagConfigFactory.createComponentConfig(tagConfig, tagConfigId, attributes,
597                new org.nuxeo.ecm.platform.ui.web.tag.handler.LeafFaceletHandler(), HtmlMessage.COMPONENT_TYPE, null);
598        return new ComponentHandler(config);
599    }
600
601    /**
602     * @since 5.6
603     */
604    public FaceletHandler getAliasTagHandler(String tagConfigId, Map<String, ValueExpression> variables,
605            List<String> blockedPatterns, FaceletHandler nextHandler) {
606        FaceletHandler currentHandler = nextHandler;
607        if (variables != null) {
608            // XXX also set id? cache? anchor?
609            ComponentConfig config = TagConfigFactory.createAliasTagConfig(tagConfig, tagConfigId, getTagAttributes(),
610                    nextHandler);
611            currentHandler = new AliasTagHandler(config, variables, blockedPatterns);
612        }
613        return currentHandler;
614    }
615
616    /**
617     * @since 6.0
618     */
619    public static boolean isDevModeEnabled(FaceletContext ctx) {
620        // avoid stack overflow when using layout tags within the dev
621        // handler
622        if (Framework.isDevModeSet()) {
623            NuxeoLayoutManagerBean bean = lookupBean(ctx.getFacesContext());
624            if (bean.isDevModeSet()) {
625                ExpressionFactory eFactory = ctx.getExpressionFactory();
626                ValueExpression disableDevAttr = eFactory.createValueExpression(ctx,
627                        String.format("#{%s}", DEV_MODE_DISABLED_VARIABLE), Boolean.class);
628                if (!Boolean.TRUE.equals(disableDevAttr.getValue(ctx))) {
629                    return true;
630                }
631            }
632        }
633        return false;
634    }
635
636    protected static NuxeoLayoutManagerBean lookupBean(FacesContext ctx) {
637        String expr = "#{" + NuxeoLayoutManagerBean.NAME + "}";
638        NuxeoLayoutManagerBean bean = (NuxeoLayoutManagerBean) ctx.getApplication().evaluateExpressionGet(ctx, expr,
639                Object.class);
640        if (bean == null) {
641            log.error("Managed bean not found: " + expr);
642            return null;
643        }
644        return bean;
645    }
646
647    /**
648     * @since 6.0
649     */
650    public FaceletHandler getDisableDevModeTagHandler(String tagConfigId, FaceletHandler nextHandler) {
651        ComponentConfig config = TagConfigFactory.createAliasTagConfig(tagConfig, tagConfigId,
652                DEV_MODE_DISABLED_VARIABLE, "true", "true", "false", nextHandler);
653        return new SetTagHandler(config);
654    }
655}