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