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