/*
* (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.tag.handler;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.view.facelets.ComponentConfig;
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.lang.StringUtils;
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.binding.MetaValueExpression;
import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasTagHandler;
import org.nuxeo.ecm.platform.ui.web.binding.alias.AliasVariableMapper;
import org.nuxeo.ecm.platform.ui.web.util.ComponentTagUtils;
import org.nuxeo.ecm.platform.ui.web.util.FaceletDebugTracer;
/**
* Tag handler that exposes a variable to the variable map. Behaviour is close to the c:set tag handler except:
* <ul>
* <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>Since 5.4, variables are made available in the request context after the JSF component tree build thanks to a
* backing component.</li>
* </ul>
*
* @author <a href="mailto:at@nuxeo.com">Anahide Tchertchian</a>
* @since 5.3.1
*/
public class SetTagHandler extends AliasTagHandler {
private static final Log log = LogFactory.getLog(SetTagHandler.class);
protected final TagAttribute var;
protected final TagAttribute value;
/**
* @since 5.5
*/
protected final TagAttribute resolveTwice;
/**
* @since 5.6
*/
protected final TagAttribute blockPatterns;
/**
* @since 5.9.2
*/
protected final TagAttribute blockMerge;
/**
* Determines in which context expression will be evaluated when expression is not cached and resolved twice (build
* time by default, render time if local).
*
* @since 7.10
*/
protected final TagAttribute local;
/**
* Force using of {@link AliasVariableMapper} logics, exposing a reference of to a value that might change between
* "restore view" and "render response" phase, to make sure it's not cached by components and resolved again at
* "render response" phase.
*
* @since 8.2
*/
protected final TagAttribute useAlias;
public SetTagHandler(ComponentConfig config) {
super(config, null);
var = getRequiredAttribute("var");
value = getAttribute("value");
resolveTwice = getAttribute("resolveTwice");
blockPatterns = getAttribute("blockPatterns");
blockMerge = getAttribute("blockMerge");
local = getAttribute("local");
useAlias = getAttribute("useAlias");
}
@Override
public void apply(FaceletContext ctx, UIComponent parent)
throws IOException, FacesException, FaceletException, ELException {
long start = FaceletDebugTracer.start();
String varStr = null;
try {
// make sure our parent is not null
if (parent == null) {
throw new TagException(tag, "Parent UIComponent was null");
}
boolean useAliasBool = false;
if (useAlias != null) {
useAliasBool = useAlias.getBoolean(ctx);
}
if (!useAliasBool && isOptimizedAgain()) {
varStr = var.getValue(ctx);
VariableMapper orig = ctx.getVariableMapper();
boolean done = false;
if (orig instanceof BlockingVariableMapper) {
BlockingVariableMapper vm = (BlockingVariableMapper) orig;
if (isAcceptingMerge(ctx, vm, varStr)) {
FaceletHandler next = applyOptimized(ctx, parent, vm, varStr);
next.apply(ctx, parent);
done = true;
}
}
if (!done) {
try {
BlockingVariableMapper vm = new BlockingVariableMapper(orig);
ctx.setVariableMapper(vm);
FaceletHandler next = applyOptimized(ctx, parent, vm, varStr);
next.apply(ctx, parent);
} finally {
ctx.setVariableMapper(orig);
}
}
} else {
applyAlias(ctx, parent);
}
} finally {
FaceletDebugTracer.trace(start, getTag(), var.getValue());
}
}
public FaceletHandler getNextHandler() {
return nextHandler;
}
public boolean isAcceptingMerge(FaceletContext ctx, BlockingVariableMapper vm, String var) {
// avoid overriding variable already in the mapper
if (vm.hasVariable(var)) {
return false;
}
return isAcceptingMerge(ctx);
}
public boolean isAcceptingMerge(FaceletContext ctx) {
if (useAlias != null && useAlias.getBoolean(ctx)) {
return false;
}
if (blockMerge != null && blockMerge.getBoolean(ctx)) {
return false;
}
if (blockPatterns != null) {
String blocked = blockPatterns.getValue(ctx);
if (!StringUtils.isEmpty(blocked)) {
return false;
}
}
return true;
}
public FaceletHandler applyOptimized(FaceletContext ctx, UIComponent parent, BlockingVariableMapper vm)
throws IOException {
String varStr = var.getValue(ctx);
return applyOptimized(ctx, parent, vm, varStr);
}
public FaceletHandler applyOptimized(FaceletContext ctx, UIComponent parent, BlockingVariableMapper vm,
String varStr) throws IOException {
// handle variable expression
boolean cacheValue = false;
if (cache != null) {
cacheValue = cache.getBoolean(ctx);
}
boolean resolveTwiceBool = false;
if (resolveTwice != null) {
resolveTwiceBool = resolveTwice.getBoolean(ctx);
}
ValueExpression ve;
if (cacheValue) {
// resolve value and put it as is in variable mapper
Object res = value.getObject(ctx);
if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) {
ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class);
res = ve.getValue(ctx);
}
ve = ctx.getExpressionFactory().createValueExpression(res, Object.class);
} else {
ve = value.getValueExpression(ctx, Object.class);
if (resolveTwiceBool) {
boolean localBool = false;
if (local != null) {
localBool = local.getBoolean(ctx);
}
if (localBool) {
ve = new MetaValueExpression(ve);
} else {
ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), vm);
}
}
}
vm.setVariable(varStr, ve);
if (blockPatterns != null) {
String blockedValue = blockPatterns.getValue(ctx);
if (!StringUtils.isEmpty(blockedValue)) {
// split on "," character
vm.setBlockedPatterns(resolveBlockPatterns(blockedValue));
}
}
FaceletHandler nextHandler = this.nextHandler;
if (nextHandler instanceof SetTagHandler) {
// try merging with next handler
SetTagHandler next = (SetTagHandler) nextHandler;
if (next.isAcceptingMerge(ctx)) {
nextHandler = next.applyOptimized(ctx, parent, vm);
}
}
return nextHandler;
}
public void applyAlias(FaceletContext ctx, UIComponent parent) throws IOException {
FaceletHandler nextHandler = this.nextHandler;
VariableMapper orig = ctx.getVariableMapper();
AliasVariableMapper target = new AliasVariableMapper();
// generate id before applying (and before generating next handler, in
// case of merge of variables, as parent aliases will be exposed to
// request then).
target.setId(ctx.generateUniqueId(tagId));
VariableMapper vm = target.getVariableMapperForBuild(orig);
ctx.setVariableMapper(vm);
try {
nextHandler = getAliasVariableMapper(ctx, target);
} finally {
ctx.setVariableMapper(orig);
}
applyAliasHandler(ctx, parent, target, nextHandler);
}
public FaceletHandler getAliasVariableMapper(FaceletContext ctx, AliasVariableMapper target) {
String varStr = var.getValue(ctx);
// avoid overriding variable already in the mapper
if (target.hasVariables(varStr)) {
return nextHandler;
}
// handle variable expression
boolean cacheValue = false;
if (cache != null) {
cacheValue = cache.getBoolean(ctx);
}
boolean resolveTwiceBool = false;
if (resolveTwice != null) {
resolveTwiceBool = resolveTwice.getBoolean(ctx);
}
ValueExpression ve;
if (cacheValue) {
// resolve value and put it as is in variable mapper
Object res = value.getObject(ctx);
if (resolveTwiceBool && res instanceof String && ComponentTagUtils.isValueReference((String) res)) {
ve = ctx.getExpressionFactory().createValueExpression(ctx, (String) res, Object.class);
res = ve.getValue(ctx);
}
ve = ctx.getExpressionFactory().createValueExpression(res, Object.class);
} else {
ve = value.getValueExpression(ctx, Object.class);
if (resolveTwiceBool) {
boolean localBool = false;
if (local != null) {
localBool = local.getBoolean(ctx);
}
if (localBool) {
ve = new MetaValueExpression(ve);
} else {
ve = new MetaValueExpression(ve, ctx.getFunctionMapper(), ctx.getVariableMapper());
}
}
}
target.setVariable(varStr, ve);
if (blockPatterns != null) {
String blockedValue = blockPatterns.getValue(ctx);
if (!StringUtils.isEmpty(blockedValue)) {
// split on "," character
target.setBlockedPatterns(resolveBlockPatterns(blockedValue));
}
}
FaceletHandler nextHandler = this.nextHandler;
if (nextHandler instanceof SetTagHandler) {
// try merging with next handler
SetTagHandler next = (SetTagHandler) nextHandler;
if (next.isAcceptingMerge(ctx)) {
// make sure referenced vars will be resolved in this context
ctx.getVariableMapper().setVariable(varStr, ve);
try {
AliasVariableMapper.exposeAliasesToRequest(ctx.getFacesContext(), target);
nextHandler = next.getAliasVariableMapper(ctx, target);
} finally {
AliasVariableMapper.removeAliasesExposedToRequest(ctx.getFacesContext(), target.getId());
}
}
}
return nextHandler;
}
protected List<String> resolveBlockPatterns(String value) {
List<String> res = new ArrayList<String>();
if (value != null) {
String[] split = StringUtils.split(value, ',');
if (split != null) {
for (String item : split) {
res.add(item.trim());
}
}
}
return res;
}
}