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