/* * (C) Copyright 2010 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Nuxeo - initial API and implementation * * $Id: $ */ package org.nuxeo.ecm.platform.ui.web.binding.alias; import java.io.IOException; import java.util.List; import java.util.Map; import javax.el.ELException; import javax.el.ExpressionFactory; import javax.el.ValueExpression; import javax.el.VariableMapper; import javax.faces.FacesException; import javax.faces.component.UIComponent; import javax.faces.component.UIViewRoot; import javax.faces.context.FacesContext; import javax.faces.view.facelets.ComponentConfig; import javax.faces.view.facelets.ComponentHandler; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.FaceletException; import javax.faces.view.facelets.FaceletHandler; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.nuxeo.ecm.platform.ui.web.binding.BlockingVariableMapper; import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.services.config.ConfigurationService; import com.sun.faces.facelets.tag.jsf.ComponentSupport; /** * Tag handler that exposes variables to the variable map. Behaviour is close to the c:set tag handler except: * <ul> * <li>It handles several variables</li> * <li>It allows caching a variable using cache parameter: variable will be resolved the first time is is called and * will be put in the context after</li> * <li>The resolved variable is removed from context when tag is closed to avoid filling the context with it</li> * <li>Variables are made available in the request context after the JSF component tree build thanks to a backing * component.</li> * </ul> * <p> * The backing component value expressions are changed even if the component was found to ensure a good resolution even * when re-rendering the tag using ajax. * * @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a> * @since 5.4 */ public class AliasTagHandler extends ComponentHandler { private static final Log log = LogFactory.getLog(AliasTagHandler.class); /** * @since 6.0 */ public static String ANCHOR_ENABLED_VARIABLE = "nuxeoAliasAnchorEnabled"; protected final TagAttribute cache; protected final TagAttribute id; protected final Map<String, ValueExpression> variables; protected final List<String> blockedPatterns; /** * @since 6.0 */ protected final TagAttribute anchor; public AliasTagHandler(ComponentConfig config, Map<String, ValueExpression> variables) { this(config, variables, null); } /** * @since 5.6 */ public AliasTagHandler(ComponentConfig config, Map<String, ValueExpression> variables, List<String> blockedPatterns) { super(config); id = getAttribute("id"); cache = getAttribute("cache"); anchor = getAttribute("anchor"); this.variables = variables; this.blockedPatterns = blockedPatterns; } @Override public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException, ELException { long start = FaceletDebugTracer.start(); try { // make sure our parent is not null if (parent == null) { throw new TagException(tag, "Parent UIComponent was null"); } // handle variable expression boolean cacheValue = false; if (cache != null) { cacheValue = cache.getBoolean(ctx); } if (isOptimizedAgain()) { applyOptimized(ctx, parent, cacheValue); } else { applyAlias(ctx, parent, cacheValue); } } finally { FaceletDebugTracer.trace(start, getTag(), "alias"); } } protected boolean isOptimizedAgain() { ConfigurationService cs = Framework.getService(ConfigurationService.class); return !cs.isBooleanPropertyTrue("nuxeo.jsf.removeAliasOptimsReloaded"); } protected void applyOptimized(FaceletContext ctx, UIComponent parent, boolean cache) throws IOException, FacesException, FaceletException, ELException { VariableMapper orig = ctx.getVariableMapper(); try { BlockingVariableMapper vm = new BlockingVariableMapper(orig); vm.setBlockedPatterns(blockedPatterns); if (variables != null) { for (Map.Entry<String, ValueExpression> var : variables.entrySet()) { if (cache) { // resolve value and put it as is in variables Object res = var.getValue().getValue(ctx); ValueExpression ve = ctx.getExpressionFactory().createValueExpression(res, Object.class); vm.setVariable(var.getKey(), ve); } else { vm.setVariable(var.getKey(), var.getValue()); } } } ctx.setVariableMapper(vm); nextHandler.apply(ctx, parent); } finally { ctx.setVariableMapper(orig); } } protected void applyAlias(FaceletContext ctx, UIComponent parent, boolean cacheValue) throws IOException, FacesException, FaceletException, ELException { AliasVariableMapper target = new AliasVariableMapper(); target.setBlockedPatterns(blockedPatterns); if (variables != null) { for (Map.Entry<String, ValueExpression> var : variables.entrySet()) { if (cacheValue) { // resolve value and put it as is in variables Object res = var.getValue().getValue(ctx); target.setVariable(var.getKey(), ctx.getExpressionFactory().createValueExpression(res, Object.class)); } else { target.setVariable(var.getKey(), var.getValue()); } } } // generate id before applying target.setId(ctx.generateUniqueId(tagId)); apply(ctx, parent, target, nextHandler); } protected void apply(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, FaceletHandler nextHandler) throws IOException, FacesException, FaceletException, ELException { ConfigurationService configurationService = Framework.getService(ConfigurationService.class); if (configurationService.isBooleanPropertyTrue("nuxeo.jsf.removeAliasOptims")) { applyCompat(ctx, parent, alias, nextHandler); return; } applyAliasHandler(ctx, parent, alias, nextHandler); } protected boolean isAnchored(FaceletContext ctx) { if (cache != null && cache.getBoolean(ctx)) { return false; } ExpressionFactory eFactory = ctx.getExpressionFactory(); ValueExpression ve = eFactory.createValueExpression(ctx, "#{" + ANCHOR_ENABLED_VARIABLE + "}", Boolean.class); if (Boolean.TRUE.equals(ve.getValue(ctx))) { return true; } if (anchor != null) { return anchor.getBoolean(ctx); } return false; } protected void applyAliasHandler(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, FaceletHandler nextHandler) throws IOException, FacesException, FaceletException, ELException { // resolve the "anchor" attribute to decide whether variable should be // anchored in the tree as a UIAliasHolder boolean createComponent = isAnchored(ctx); if (createComponent) { // start by removing component from tree if it is already there, to // make sure it's recreated next String id = ctx.generateUniqueId(getTagId()); UIComponent c = ComponentSupport.findChildByTagId(parent, id); if (c != null && c.getParent() != parent) { c.getParent().getChildren().remove(c); } } String id = alias.getId(); VariableMapper orig = ctx.getVariableMapper(); VariableMapper vm = alias.getVariableMapperForBuild(orig); ctx.setVariableMapper(vm); FacesContext facesContext = ctx.getFacesContext(); try { AliasVariableMapper.exposeAliasesToRequest(facesContext, alias); if (createComponent) { super.apply(ctx, parent); } else { nextHandler.apply(ctx, parent); } } finally { AliasVariableMapper.removeAliasesExposedToRequest(facesContext, id); ctx.setVariableMapper(orig); } } /** * Compatibility application of facelet handler, used to preserve behaviour while optimizing and improving variables * exposure and resolution. */ protected void applyCompat(FaceletContext ctx, UIComponent parent, AliasVariableMapper alias, FaceletHandler nextHandler) throws IOException, FacesException, FaceletException, ELException { String id = alias.getId(); VariableMapper orig = ctx.getVariableMapper(); VariableMapper vm = alias.getVariableMapperForBuild(orig); ctx.setVariableMapper(vm); // create component UIComponent c = ComponentSupport.findChildByTagId(parent, id); boolean componentFound = false; if (c != null) { componentFound = true; // mark all children for cleaning ComponentSupport.markForDeletion(c); } else { c = new UIAliasHolder(); // mark it owned by a facelet instance c.getAttributes().put(ComponentSupport.MARK_CREATED, id); // assign our unique id if (this.id != null) { c.setId(this.id.getValue(ctx)); } else { UIViewRoot root = ComponentSupport.getViewRoot(ctx, parent); if (root != null) { String uid = root.createUniqueId(); c.setId(uid); } } } // update value held by component ((UIAliasHolder) c).setAlias(alias); FacesContext facesContext = ctx.getFacesContext(); try { AliasVariableMapper.exposeAliasesToRequest(facesContext, alias); // first allow c to get populated nextHandler.apply(ctx, c); } finally { AliasVariableMapper.removeAliasesExposedToRequest(facesContext, id); ctx.setVariableMapper(orig); } // finish cleaning up orphaned children if (componentFound) { ComponentSupport.finalizeForDeletion(c); } // add to the tree afterwards // this allows children to determine if it's // been part of the tree or not yet parent.getChildren().add(c); } }