blob: 94137cc6d744086a285f9e133d06681008385a03 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.myfaces.view.facelets.impl;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ELResolver;
import javax.el.ExpressionFactory;
import javax.el.FunctionMapper;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.FacesException;
import javax.faces.application.Resource;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.view.facelets.FaceletContext;
import javax.faces.view.facelets.FaceletException;
import org.apache.myfaces.view.facelets.AbstractFacelet;
import org.apache.myfaces.view.facelets.AbstractFaceletContext;
import org.apache.myfaces.view.facelets.ELExpressionCacheMode;
import org.apache.myfaces.view.facelets.FaceletCompositionContext;
import org.apache.myfaces.view.facelets.PageContext;
import org.apache.myfaces.view.facelets.TemplateClient;
import org.apache.myfaces.view.facelets.TemplateContext;
import org.apache.myfaces.view.facelets.TemplateManager;
import org.apache.myfaces.view.facelets.el.DefaultVariableMapper;
import org.apache.myfaces.view.facelets.el.VariableMapperBase;
import org.apache.myfaces.view.facelets.tag.jsf.core.AjaxHandler;
/**
* Default FaceletContext implementation.
*
* A single FaceletContext is used for all Facelets involved in an invocation of
* {@link org.apache.myfaces.view.facelets.Facelet#apply(FacesContext, UIComponent)
* Facelet#apply(FacesContext, UIComponent)}. This
* means that included Facelets are treated the same as the JSP include directive.
*
* @author Jacob Hookom
* @version $Id$
*/
final class DefaultFaceletContext extends AbstractFaceletContext
{
private final FacesContext _faces;
private final ELContext _ctx;
private final AbstractFacelet _facelet;
private final List<AbstractFacelet> _faceletHierarchy;
private VariableMapper _varMapper;
private final DefaultVariableMapper _defaultVarMapper;
private VariableMapperBase _varMapperBase;
private FunctionMapper _fnMapper;
private String _prefix;
private StringBuilder _uniqueIdBuilder;
private final FaceletCompositionContext _mctx;
private LinkedList<AjaxHandler> _ajaxHandlerStack;
private final List<TemplateContext> _isolatedTemplateContext;
private int _currentTemplateContext;
private ELExpressionCacheMode _elExpressionCacheMode;
private boolean _isCacheELExpressions;
private final List<PageContext> _isolatedPageContext;
public DefaultFaceletContext(DefaultFaceletContext ctx, AbstractFacelet facelet, boolean ccWrap)
{
_ctx = ctx._ctx;
_faces = ctx._faces;
_fnMapper = ctx._fnMapper;
_varMapper = ctx._varMapper;
_defaultVarMapper = ctx._defaultVarMapper;
_varMapperBase = ctx._varMapperBase;
_faceletHierarchy = new ArrayList<>(ctx._faceletHierarchy.size() + 1);
_faceletHierarchy.addAll(ctx._faceletHierarchy);
_faceletHierarchy.add(facelet);
_facelet = facelet;
_mctx = ctx._mctx;
if (ccWrap)
{
// Each time a composite component is being applied, a new
// ajax stack should be created, and f:ajax tags outside the
// composite component should be ignored.
_ajaxHandlerStack = null;
}
else
{
// It is a template include, the current ajax stack should be
// preserved.
_ajaxHandlerStack = ctx._ajaxHandlerStack;
}
_isolatedTemplateContext = ctx._isolatedTemplateContext;
_currentTemplateContext = ctx._currentTemplateContext;
_isolatedPageContext = ctx._isolatedPageContext;
_elExpressionCacheMode = ctx._elExpressionCacheMode;
_isCacheELExpressions = ctx._isCacheELExpressions;
}
public DefaultFaceletContext(FacesContext faces, AbstractFacelet facelet, FaceletCompositionContext mctx)
{
_ctx = faces.getELContext();
_faces = faces;
_fnMapper = _ctx.getFunctionMapper();
_varMapper = _ctx.getVariableMapper();
if (_varMapper == null)
{
_defaultVarMapper = new DefaultVariableMapper();
_varMapper = _defaultVarMapper;
_varMapperBase = _defaultVarMapper;
}
else
{
_defaultVarMapper = new DefaultVariableMapper(_varMapper);
_varMapper = _defaultVarMapper;
_varMapperBase = _defaultVarMapper;
}
_faceletHierarchy = new ArrayList<>(1);
_faceletHierarchy.add(facelet);
_facelet = facelet;
_mctx = mctx;
_isolatedTemplateContext = new ArrayList<>(1);
_isolatedTemplateContext.add(new TemplateContextImpl());
_currentTemplateContext = 0;
_defaultVarMapper.setTemplateContext(_isolatedTemplateContext.get(_currentTemplateContext));
_isolatedPageContext = new ArrayList<>(8);
_elExpressionCacheMode = mctx.getELExpressionCacheMode();
_isCacheELExpressions = !ELExpressionCacheMode.noCache.equals(_elExpressionCacheMode);
}
/**
* {@inheritDoc}
*/
@Override
public FacesContext getFacesContext()
{
return _faces;
}
/**
* {@inheritDoc}
*/
@Override
public ExpressionFactory getExpressionFactory()
{
return _facelet.getExpressionFactory();
}
/**
* {@inheritDoc}
*/
@Override
public void setVariableMapper(VariableMapper varMapper)
{
_varMapper = varMapper;
_varMapperBase = (_varMapper instanceof VariableMapperBase) ? (VariableMapperBase) varMapper : null;
}
/**
* {@inheritDoc}
*/
@Override
public void setFunctionMapper(FunctionMapper fnMapper)
{
_fnMapper = fnMapper;
}
/**
* {@inheritDoc}
*/
@Override
public void includeFacelet(UIComponent parent, String relativePath) throws IOException
{
_facelet.include(this, parent, relativePath);
}
/**
* {@inheritDoc}
*/
@Override
public FunctionMapper getFunctionMapper()
{
return _fnMapper;
}
/**
* {@inheritDoc}
*/
@Override
public VariableMapper getVariableMapper()
{
return _varMapper;
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public Object getContext(Class key)
{
return _ctx.getContext(key);
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public void putContext(Class key, Object contextObject)
{
_ctx.putContext(key, contextObject);
}
private void initPrefix()
{
if (_prefix == null)
{
_uniqueIdBuilder = new StringBuilder(_faceletHierarchy.size() * 30);
for (int i = 0; i < _faceletHierarchy.size(); i++)
{
AbstractFacelet facelet = _faceletHierarchy.get(i);
_uniqueIdBuilder.append(facelet.getFaceletId());
}
// Integer prefixInt = new Integer(builder.toString().hashCode());
// -= Leonardo Uribe =- if the previous formula is used, it is possible that
// negative values are introduced. The presence of '-' char causes problems
// with htmlunit 2.4 or lower, so in order to prevent it it is better to use
// only positive values instead.
// Take into account CompilationManager.nextTagId() uses Math.abs too.
_prefix = Integer.toString(Math.abs(_uniqueIdBuilder.toString().hashCode()));
}
}
/**
* {@inheritDoc}
*/
@Override
public String generateUniqueId(String base)
{
initPrefix();
_uniqueIdBuilder.setLength(0);
// getFaceletCompositionContext().generateUniqueId() is the one who ensures
// the final id will be unique, but prefix and base ensure it will be unique
// per facelet because prefix is calculated from faceletHierarchy and base is
// related to the tagId, which depends on the location.
//_uniqueIdBuilder.append(getFaceletCompositionContext().generateUniqueId());
String uniqueIdFromIterator = getFaceletCompositionContext().getUniqueIdFromIterator();
if (uniqueIdFromIterator == null)
{
getFaceletCompositionContext().generateUniqueId(_uniqueIdBuilder);
// Since two different facelets are used to build the metadata, it is necessary
// to trim the "base" part from the returned unique id, to ensure the components will be
// refreshed properly. Note the "base" part is the one that allows to ensure
// uniqueness between two different facelets with the same <f:metadata>, but since by
// spec view metadata sections cannot live on template client facelets, this case is
// just not possible.
// MYFACES-3709 It was also noticed that in some cases, the prefix should also
// be excluded from the id. The prefix is included if the metadata section is
// applied inside an included section (by ui:define and ui:insert for example).
if (!getFaceletCompositionContext().isInMetadataSection())
{
_uniqueIdBuilder.append('_');
_uniqueIdBuilder.append(_prefix);
_uniqueIdBuilder.append('_');
_uniqueIdBuilder.append(base);
}
uniqueIdFromIterator = _uniqueIdBuilder.toString();
getFaceletCompositionContext().addUniqueId(uniqueIdFromIterator);
return uniqueIdFromIterator;
}
else
{
getFaceletCompositionContext().incrementUniqueId();
return uniqueIdFromIterator;
}
}
@Override
public String generateUniqueFaceletTagId(String count, String base)
{
initPrefix();
_uniqueIdBuilder.setLength(0);
_uniqueIdBuilder.append(count);
_uniqueIdBuilder.append('_');
_uniqueIdBuilder.append(_prefix);
_uniqueIdBuilder.append('_');
_uniqueIdBuilder.append(base);
return _uniqueIdBuilder.toString();
}
/**
* {@inheritDoc}
*/
@Override
public Object getAttribute(String name)
{
if (_varMapper != null)
{
ValueExpression ve = _varMapper.resolveVariable(name);
if (ve != null)
{
return ve.getValue(this);
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void setAttribute(String name, Object value)
{
if (_varMapper != null)
{
if (value == null)
{
_varMapper.setVariable(name, null);
}
else
{
_varMapper.setVariable(name, _facelet.getExpressionFactory()
.createValueExpression(value, Object.class));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void includeFacelet(UIComponent parent, URL absolutePath)
throws IOException, FacesException, ELException
{
_facelet.include(this, parent, absolutePath);
}
/**
* {@inheritDoc}
*/
@Override
public ELResolver getELResolver()
{
return _ctx.getELResolver();
}
//Begin methods from AbstractFaceletContext
@Override
public TemplateManager popClient(TemplateClient client)
{
return _isolatedTemplateContext.get(_currentTemplateContext).popClient(this);
}
@Override
public void pushClient(final TemplateClient client)
{
_isolatedTemplateContext.get(_currentTemplateContext).pushClient(this, this._facelet, client);
}
@Override
public TemplateManager popExtendedClient(TemplateClient client)
{
return _isolatedTemplateContext.get(_currentTemplateContext).popExtendedClient(this);
}
@Override
public void extendClient(final TemplateClient client)
{
_isolatedTemplateContext.get(_currentTemplateContext).extendClient(this, this._facelet, client);
}
@Override
public boolean includeDefinition(UIComponent parent, String name)
throws IOException, FaceletException, FacesException, ELException
{
return _isolatedTemplateContext.get(_currentTemplateContext).includeDefinition(
this, this._facelet, parent, name);
}
@Override
public void pushCompositeComponentClient(final TemplateClient client)
{
TemplateContext itc = new TemplateContextImpl();
itc.setCompositeComponentClient(
new CompositeComponentTemplateManager(this._facelet, client, getPageContext()));
_isolatedTemplateContext.add(itc);
_currentTemplateContext++;
_defaultVarMapper.setTemplateContext(itc);
}
@Override
public void popCompositeComponentClient()
{
if (_currentTemplateContext > 0)
{
_isolatedTemplateContext.remove(_currentTemplateContext);
_currentTemplateContext--;
_defaultVarMapper.setTemplateContext(_isolatedTemplateContext.get(_currentTemplateContext));
}
}
@Override
public void pushTemplateContext(TemplateContext client)
{
_isolatedTemplateContext.add(client);
_currentTemplateContext++;
_defaultVarMapper.setTemplateContext(client);
}
@Override
public TemplateContext popTemplateContext()
{
if (_currentTemplateContext > 0)
{
TemplateContext itc = _isolatedTemplateContext.get(_currentTemplateContext);
_isolatedTemplateContext.remove(_currentTemplateContext);
_currentTemplateContext--;
_defaultVarMapper.setTemplateContext(_isolatedTemplateContext.get(_currentTemplateContext));
return itc;
}
return null;
}
@Override
public TemplateContext getTemplateContext()
{
return _isolatedTemplateContext.get(_currentTemplateContext);
}
@Override
public boolean includeCompositeComponentDefinition(UIComponent parent, String name)
throws IOException, FaceletException, FacesException, ELException
{
TemplateClient ccClient = _isolatedTemplateContext.get(_currentTemplateContext).getCompositeComponentClient();
if (ccClient != null)
{
return ccClient.apply(this, parent, name);
}
return false;
}
private final static class CompositeComponentTemplateManager extends TemplateManager implements TemplateClient
{
private final AbstractFacelet _owner;
protected final TemplateClient _target;
private final Set<String> _names = new HashSet<>();
private final PageContext _pageContext;
public CompositeComponentTemplateManager(AbstractFacelet owner, TemplateClient target, PageContext pageContext)
{
this._owner = owner;
this._target = target;
this._pageContext = pageContext;
}
@Override
public boolean apply(FaceletContext ctx, UIComponent parent, String name)
throws IOException, FacesException, FaceletException, ELException
{
String testName = (name != null) ? name : "facelets._NULL_DEF_";
if (this._names.contains(testName))
{
return false;
}
else
{
this._names.add(testName);
boolean found = false;
AbstractFaceletContext actx = new DefaultFaceletContext(
(DefaultFaceletContext) ctx, this._owner, false);
ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, actx);
try
{
actx.pushPageContext(this._pageContext);
found = this._target.apply(actx, parent, name);
}
finally
{
actx.popPageContext();
}
ctx.getFacesContext().getAttributes().put(FaceletContext.FACELET_CONTEXT_KEY, ctx);
this._names.remove(testName);
return found;
}
}
@Override
public boolean equals(Object o)
{
return this._owner == o || this._target == o;
}
@Override
public int hashCode()
{
int result = _owner != null ? _owner.hashCode() : 0;
result = 31 * result + (_target != null ? _target.hashCode() : 0);
return result;
}
}
@Override
public void pushPageContext(PageContext client)
{
_isolatedPageContext.add(client);
_defaultVarMapper.setPageContext(client);
}
@Override
public PageContext popPageContext()
{
if (!_isolatedPageContext.isEmpty())
{
int currentPageContext = _isolatedPageContext.size()-1;
PageContext itc = _isolatedPageContext.get(currentPageContext);
_isolatedPageContext.remove(currentPageContext);
if (!_isolatedPageContext.isEmpty())
{
_defaultVarMapper.setPageContext(getPageContext());
}
else
{
_defaultVarMapper.setPageContext(null);
}
return itc;
}
return null;
}
@Override
public PageContext getPageContext()
{
return _isolatedPageContext.get(_isolatedPageContext.size()-1);
}
//End methods from AbstractFaceletContext
/**
* {@inheritDoc}
*/
@Override
public boolean isPropertyResolved()
{
return _ctx.isPropertyResolved();
}
/**
* {@inheritDoc}
*/
@Override
public void setPropertyResolved(boolean resolved)
{
_ctx.setPropertyResolved(resolved);
}
@Override
public void applyCompositeComponent(UIComponent parent, Resource resource)
throws IOException, FaceletException, FacesException, ELException
{
_facelet.applyCompositeComponent(this, parent, resource);
}
@Override
public Iterator<AjaxHandler> getAjaxHandlers()
{
if (_ajaxHandlerStack != null && !_ajaxHandlerStack.isEmpty())
{
return _ajaxHandlerStack.iterator();
}
return null;
}
@Override
public void popAjaxHandlerToStack()
{
if (_ajaxHandlerStack != null && !_ajaxHandlerStack.isEmpty())
{
_ajaxHandlerStack.removeFirst();
}
}
@Override
public void pushAjaxHandlerToStack(AjaxHandler parent)
{
if (_ajaxHandlerStack == null)
{
_ajaxHandlerStack = new LinkedList<>();
}
_ajaxHandlerStack.addFirst(parent);
}
@Override
public boolean isBuildingCompositeComponentMetadata()
{
return _facelet.isBuildingCompositeComponentMetadata();
}
@Override
public FaceletCompositionContext getFaceletCompositionContext()
{
return _mctx;
}
@Override
public boolean isAnyFaceletsVariableResolved()
{
if (_varMapperBase != null)
{
return _varMapperBase.isAnyFaceletsVariableResolved();
}
return true;
}
@Override
public boolean isAllowCacheELExpressions()
{
return _isCacheELExpressions && getTemplateContext().isAllowCacheELExpressions()
&& getPageContext().isAllowCacheELExpressions();
}
@Override
public void beforeConstructELExpression()
{
if (_varMapperBase != null)
{
_varMapperBase.beforeConstructELExpression();
}
}
@Override
public void afterConstructELExpression()
{
if (_varMapperBase != null)
{
_varMapperBase.afterConstructELExpression();
}
}
@Override
public ELExpressionCacheMode getELExpressionCacheMode()
{
return _elExpressionCacheMode;
}
}