blob: efa9db42a8793e2103c0e55d69c0a51b74037d9e [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 javax.faces.component;
import org.apache.myfaces.core.api.shared.ClassUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
import javax.el.ELException;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.Resource;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
import javax.faces.component.visit.VisitResult;
import javax.faces.context.FacesContext;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.event.ComponentSystemEventListener;
import javax.faces.event.FacesEvent;
import javax.faces.event.FacesListener;
import javax.faces.event.PostRestoreStateEvent;
import javax.faces.event.SystemEvent;
import javax.faces.event.SystemEventListener;
import javax.faces.event.SystemEventListenerHolder;
import javax.faces.render.Renderer;
import javax.faces.render.RendererWrapper;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFComponent;
import org.apache.myfaces.buildtools.maven2.plugin.builder.annotation.JSFWebConfigParam;
/**
*
* see Javadoc of <a href="http://java.sun.com/javaee/javaserverfaces/1.2/docs/api/index.html">J
* SF Specification</a>
*/
@JSFComponent(type = "javax.faces.Component", family = "javax.faces.Component",
desc = "abstract base component", configExcluded = true)
public abstract class UIComponent
implements PartialStateHolder, TransientStateHolder, SystemEventListenerHolder, ComponentSystemEventListener
{
// TODO: Reorder methods, this class is a mess
/**
* Constant used in component attribute map to retrieve the BeanInfo of a composite
* component.
*
* @see javax.faces.view.ViewDeclarationLanguage#getComponentMetadata(FacesContext, Resource)
* @see javax.faces.view.ViewDeclarationLanguage#retargetAttachedObjects(FacesContext, UIComponent, List)
* @see javax.faces.view.ViewDeclarationLanguage#retargetMethodExpressions(FacesContext, UIComponent)
* @see javax.faces.application.Application#createComponent(FacesContext, Resource)
*/
public static final String BEANINFO_KEY = "javax.faces.component.BEANINFO_KEY";
/**
* Constant used in BeanInfo descriptor as a key for retrieve an alternate component type
* for create the composite base component.
*
* @see javax.faces.application.Application#createComponent(FacesContext, Resource)
*/
public static final String COMPOSITE_COMPONENT_TYPE_KEY = "javax.faces.component.COMPOSITE_COMPONENT_TYPE";
/**
* Constant used to define the facet inside this component that store the component hierarchy
* generated by a composite component implementation, and then rendered. In other words,
* note that direct children of a component are not rendered, instead components inside
* this face are rendered.
*/
public static final String COMPOSITE_FACET_NAME = "javax.faces.component.COMPOSITE_FACET_NAME";
/**
* Constant used to store the current component that is being processed.
*
* @see #pushComponentToEL(FacesContext, UIComponent)
* @see #popComponentFromEL(FacesContext)
*/
public static final String CURRENT_COMPONENT = "javax.faces.component.CURRENT_COMPONENT";
/**
* Constant used to store the current composite component that is being processed.
*
* @see #pushComponentToEL(FacesContext, UIComponent)
* @see #popComponentFromEL(FacesContext)
*/
public static final String CURRENT_COMPOSITE_COMPONENT = "javax.faces.component.CURRENT_COMPOSITE_COMPONENT";
/**
* This constant has two usages. The first one is in component attribute map to identify the
* facet name under this component is child of its parent. The second one is on BeanInfo descriptor
* as a key for a Map&lt;String, PropertyDescriptor&gt; that contains metadata information defined
* by composite:facet tag and composite:implementation(because this one fills the facet referenced
* by COMPOSITE_FACET_NAME constant).
*/
public static final String FACETS_KEY = "javax.faces.component.FACETS_KEY";
/**
* Constant used in component attribute map to store the {@link javax.faces.view.Location} object
* where the definition of this component is.
*/
public static final String VIEW_LOCATION_KEY = "javax.faces.component.VIEW_LOCATION_KEY";
public static final String ATTRS_WITH_DECLARED_DEFAULT_VALUES
= "javax.faces.component.ATTR_NAMES_WITH_DEFAULT_VALUES";
/**
* Indicate if the facesContext attribute values under the keys javax.faces.component.CURRENT_COMPONENT and
* javax.faces.component.CURRENT_COMPOSITE_COMPONENT should be valid or not. By default, those keys are
* deprecated since 2.1
*/
@JSFWebConfigParam(since = "2.1.0", expectedValues = "true, false", defaultValue = "false")
public static final String HONOR_CURRENT_COMPONENT_ATTRIBUTES_PARAM_NAME
= "javax.faces.HONOR_CURRENT_COMPONENT_ATTRIBUTES";
/**
* The key under which the component stack is stored in the FacesContext.
* ATTENTION: this constant is duplicate in CompositeComponentExpressionUtils.
*/
private static final String _COMPONENT_STACK = "componentStack:" + UIComponent.class.getName();
private static final String _CURRENT_COMPOSITE_COMPONENT_KEY = "compositeComponent:" + UIComponent.class.getName();
Map<Class<? extends SystemEvent>, List<SystemEventListener>> _systemEventListenerClassMap;
/**
* Used to cache the map created using getResourceBundleMap() method, since this method could be called several
* times when rendering the composite component. This attribute may not be serialized, so transient is used (There
* are some very few special cases when UIComponent instances are serializable like t:schedule, so it is better if
* transient is used).
*/
private transient Map<String, String> _resourceBundleMap = null;
private boolean _inView = false;
private _DeltaStateHelper _stateHelper = null;
/**
* In JSF 2.0 bindings map was deprecated, and replaced with a map
* inside stateHelper. We need this one here because stateHelper needs
* to be implemented from here and internally it depends from this property.
*/
private boolean _initialStateMarked = false;
/** Value of the {@link UIComponent#HONOR_CURRENT_COMPONENT_ATTRIBUTES_PARAM_NAME} parameter */
private Boolean _honorCurrentComponentAttributes;
public UIComponent()
{
}
public abstract Map<String, Object> getAttributes();
/**
* @since 2.2
* @return
*/
public Map<String,Object> getPassThroughAttributes()
{
return getPassThroughAttributes(true);
}
/**
* @since 2.2
* @param create
* @return A {@code Map} instance, or {@code null}.
*/
public Map<String,Object> getPassThroughAttributes(boolean create)
{
return Collections.emptyMap();
}
/**
*
* {@inheritDoc}
*
* @since 2.0
*/
@Override
public boolean initialStateMarked()
{
return _initialStateMarked;
}
/**
* Invokes the <code>invokeContextCallback</code> method with the component, specified by <code>clientId</code>.
*
* @param context
* <code>FacesContext</code> for the current request
* @param clientId
* the id of the desired <code>UIComponent</code> clazz
* @param callback
* Implementation of the <code>ContextCallback</code> to be called
* @return has component been found ?
* @throws javax.faces.FacesException
*/
public boolean invokeOnComponent(FacesContext context, String clientId, ContextCallback callback)
throws FacesException
{
// java.lang.NullPointerException - if any of the arguments are null
if (context == null || clientId == null || callback == null)
{
throw new NullPointerException();
}
pushComponentToEL(context, this);
try
{
// searching for this component?
boolean found = clientId.equals(this.getClientId(context));
if (found)
{
try
{
callback.invokeContextCallback(context, this);
}
catch (Exception e)
{
throw new FacesException(e);
}
return found;
}
// Searching for this component's children/facets
// [perf] Use getFacetsAndChildren() is nicer but this one prevents
// create 1 iterator per component class that does not have
// facets attached, which is a common case.
if (this.getFacetCount() > 0)
{
for (Iterator<UIComponent> it = this.getFacets().values().iterator(); !found && it.hasNext(); )
{
found = it.next().invokeOnComponent(context, clientId, callback);
}
}
if (this.getChildCount() > 0)
{
for (int i = 0, childCount = getChildCount(); !found && (i < childCount); i++)
{
UIComponent child = getChildren().get(i);
found = child.invokeOnComponent(context, clientId, callback);
}
}
return found;
}
finally
{
//all components must call popComponentFromEl after visiting is finished
popComponentFromEL(context);
}
}
/**
*
* @param component
* @return true if the component is a composite component otherwise false is returned
*
*
* @throws NullPointerException if the component is null
* @since 2.0
*/
public static boolean isCompositeComponent(UIComponent component)
{
return component.getAttributes().containsKey(Resource.COMPONENT_RESOURCE_KEY);
}
/**
* Indicate if this component is inside a view,
* or in other words is contained by an UIViewRoot
* instance (which represents the view). If this component
* is a UIViewRoot instance, the components "always"
* is on the view.
*
* By default it is false but for UIViewRoot instances is
* true.
*
* @return
*
* @since 2.0
*/
public boolean isInView()
{
return _inView;
}
public abstract boolean isRendered();
@Override
public void markInitialState()
{
_initialStateMarked = true;
}
/**
*
* This method indicates if a component is visitable
* according to the hints passed by the VisitContext parameter!
*
* This method internally is used by visitTree and if it returns false
* it short circuits the visitTree execution.
*
*
*
* @param context
* @return
*
* @since 2.0
*/
protected boolean isVisitable(VisitContext context)
{
Collection<VisitHint> hints = context.getHints();
if (hints.contains(VisitHint.SKIP_TRANSIENT) && this.isTransient())
{
return false;
}
if (hints.contains(VisitHint.SKIP_UNRENDERED) && !this.isRendered())
{
return false;
}
//executable cannot be handled here because we do not have any method to determine
//whether a component is executable or not, this seems to be a hole in the spec!
//but we can resolve it on ppr context level, where it is needed!
//maybe in the long run we can move it down here, if it makes sense
return true;
}
public void setValueExpression(String name, ValueExpression expression)
{
if (name == null)
{
throw new NullPointerException("name");
}
if (name.equals("id"))
{
throw new IllegalArgumentException("Can't set a ValueExpression for the 'id' property.");
}
if (name.equals("parent"))
{
throw new IllegalArgumentException("Can't set a ValueExpression for the 'parent' property.");
}
if (expression == null)
{
getStateHelper().remove(PropertyKeys.bindings, name);
}
else
{
if (expression.isLiteralText())
{
try
{
Object value = expression.getValue(getFacesContext().getELContext());
getAttributes().put(name, value);
return;
}
catch (ELException e)
{
throw new FacesException(e);
}
}
getStateHelper().put(PropertyKeys.bindings, name, expression);
}
}
public String getClientId()
{
return getClientId(getFacesContext());
}
public abstract String getClientId(FacesContext context);
/**
* search for the nearest parent composite component, if no parent is found
* it has to return null!
*
* if the component itself is null we have to return null as well!
*
* @param component the component to start from
* @return the parent composite component if found otherwise null
*
* @since 2.0
*/
public static UIComponent getCompositeComponentParent(UIComponent component)
{
if (component == null)
{
return null;
}
UIComponent parent = component;
do
{
parent = parent.getParent();
if (parent != null && UIComponent.isCompositeComponent(parent))
{
return parent;
}
} while (parent != null);
return null;
}
/**
* @since 1.2
*/
public String getContainerClientId(FacesContext ctx)
{
if (ctx == null)
{
throw new NullPointerException("FacesContext ctx");
}
return getClientId(ctx);
}
/**
*
* @param context
* @return
*
* @since 2.0
*/
public static UIComponent getCurrentComponent(FacesContext context)
{
Boolean honorCurrentComponentAttributes = null;
if (context.getViewRoot() != null)
{
honorCurrentComponentAttributes = ((UIComponent)context.getViewRoot())._honorCurrentComponentAttributes;
if (honorCurrentComponentAttributes == null)
{
honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
}
else
{
honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
if (Boolean.TRUE.equals(honorCurrentComponentAttributes))
{
return (UIComponent) context.getAttributes().get(UIComponent.CURRENT_COMPONENT);
}
else
{
List<UIComponent> componentStack
= (List<UIComponent>) context.getAttributes().get(UIComponent._COMPONENT_STACK);
if (componentStack == null)
{
return null;
}
else
{
if (componentStack.size() > 0)
{
return componentStack.get(componentStack.size()-1);
}
else
{
return null;
}
}
}
}
/**
*
* @param context
* @return
*
* @since 2.0
*/
public static UIComponent getCurrentCompositeComponent(FacesContext context)
{
Boolean honorCurrentComponentAttributes = null;
if (context.getViewRoot() != null)
{
honorCurrentComponentAttributes = ((UIComponent)context.getViewRoot())._honorCurrentComponentAttributes;
if (honorCurrentComponentAttributes == null)
{
honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
}
else
{
honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
if (Boolean.TRUE.equals(honorCurrentComponentAttributes))
{
return (UIComponent) context.getAttributes().get(UIComponent.CURRENT_COMPOSITE_COMPONENT);
}
else
{
return (UIComponent) context.getAttributes().get(UIComponent._CURRENT_COMPOSITE_COMPONENT_KEY);
}
}
public abstract String getFamily();
public abstract String getId();
@Override
public List<SystemEventListener> getListenersForEventClass(Class<? extends SystemEvent> eventClass)
{
List<SystemEventListener> listeners;
if (_systemEventListenerClassMap == null)
{
listeners = Collections.emptyList();
}
else
{
listeners = _systemEventListenerClassMap.get(eventClass);
if (listeners == null)
{
listeners = Collections.emptyList();
}
else
{
listeners = Collections.unmodifiableList(listeners);
}
}
return listeners;
}
/**
*
* @return
*
* @since 2.0
*/
public UIComponent getNamingContainer()
{
// Starting with "this", return the closest component in the ancestry that is a NamingContainer
// or null if none can be found.
UIComponent component = this;
do
{
if (component instanceof NamingContainer)
{
return component;
}
component = component.getParent();
} while (component != null);
return null;
}
public abstract void setId(String id);
/**
* Define if the component is on the view or not.
* <p>
* This value is set in the following conditions:
* </p>
* <ul>
* <li>Component / Facet added: if the parent isInView = true,
* set it to true and all their children or facets,
* otherwise take no action</li>
* <li>Component / Facet removed: if the parent isInView = false,
* set it to false and all their children or facets,
* otherwise take no action</li>
* </ul>
* @param isInView
*
* @since 2.0
*/
public void setInView(boolean isInView)
{
_inView = isInView;
}
/**
* For JSF-framework internal use only. Don't call this method to add components to the component tree. Use
* <code>parent.getChildren().add(child)</code> instead.
*/
public abstract void setParent(UIComponent parent);
/**
* Returns the parent of the component. Children can be added to or removed from a component even if this method
* returns null for the child.
*/
public abstract UIComponent getParent();
public abstract void setRendered(boolean rendered);
public abstract String getRendererType();
public abstract void setRendererType(String rendererType);
public abstract boolean getRendersChildren();
public Map<String, String> getResourceBundleMap()
{
if (_resourceBundleMap == null)
{
FacesContext context = getFacesContext();
Locale locale = context.getViewRoot().getLocale();
ClassLoader loader = ClassUtils.getContextClassLoader();
try
{
ResourceBundle.Control bundleControl = (ResourceBundle.Control) context.getExternalContext()
.getApplicationMap().get("org.apache.myfaces.RESOURCE_BUNDLE_CONTROL");
// looks for a ResourceBundle with a base name equal to the fully qualified class
// name of the current UIComponent this and Locale equal to the Locale of the current UIViewRoot.
if (bundleControl == null)
{
_resourceBundleMap = new BundleMap(
ResourceBundle.getBundle(getClass().getName(), locale, loader));
}
else
{
_resourceBundleMap = new BundleMap(
ResourceBundle.getBundle(getClass().getName(), locale, loader, bundleControl));
}
}
catch (MissingResourceException e)
{
// If no such bundle is found, and the component is a composite component
if (this._isCompositeComponent())
{
// No need to check componentResource (the resource used to build the composite
// component instance) to null since it is already done on this._isCompositeComponent()
Resource componentResource = (Resource) getAttributes().get(Resource.COMPONENT_RESOURCE_KEY);
// Let resourceName be the resourceName of the Resource for this composite component,
// replacing the file extension with ".properties"
int extensionIndex = componentResource.getResourceName().lastIndexOf('.');
String resourceName = (extensionIndex < 0
? componentResource.getResourceName()
: componentResource.getResourceName().substring(0, extensionIndex)) + ".properties";
// Let libraryName be the libraryName of the the Resource for this composite component.
// Call ResourceHandler.createResource(java.lang.String,java.lang.String), passing the derived
// resourceName and
// libraryName.
Resource bundleResource = context.getApplication().getResourceHandler()
.createResource(resourceName, componentResource.getLibraryName());
if (bundleResource != null)
{
// If the resultant Resource exists and can be found, the InputStream for the resource
// is used to create a ResourceBundle. If either of the two previous steps for obtaining the
// ResourceBundle
// for this component is successful, the ResourceBundle is wrapped in a Map<String, String> and
// returned.
try
{
_resourceBundleMap
= new BundleMap(new PropertyResourceBundle(bundleResource.getInputStream()));
}
catch (IOException e1)
{
// Nothing happens, then resourceBundleMap is set as empty map
}
}
}
// Otherwise Collections.EMPTY_MAP is returned.
if (_resourceBundleMap == null)
{
_resourceBundleMap = Collections.emptyMap();
}
}
}
return _resourceBundleMap;
}
public ValueExpression getValueExpression(String name)
{
if (name == null)
{
throw new NullPointerException("name can not be null");
}
Map<String, Object> bindings = (Map<String, Object>) getStateHelper().get(PropertyKeys.bindings);
if (bindings != null)
{
return (ValueExpression) bindings.get(name);
}
return null;
}
public abstract List<UIComponent> getChildren();
public abstract int getChildCount();
public abstract UIComponent findComponent(String expr);
public abstract Map<String, UIComponent> getFacets();
public abstract UIComponent getFacet(String name);
public abstract Iterator<UIComponent> getFacetsAndChildren();
public abstract void broadcast(FacesEvent event) throws AbortProcessingException;
/**
* {@inheritDoc}
*
* @since 2.0
*/
@Override
public void clearInitialState()
{
_initialStateMarked = false;
}
public abstract void decode(FacesContext context);
public abstract void encodeBegin(FacesContext context) throws IOException;
public abstract void encodeChildren(FacesContext context) throws IOException;
public abstract void encodeEnd(FacesContext context) throws IOException;
public void encodeAll(FacesContext context) throws IOException
{
if (context == null)
{
throw new NullPointerException();
}
pushComponentToEL(context, this);
try
{
if (!isRendered())
{
return;
}
}
finally
{
popComponentFromEL(context);
}
this.encodeBegin(context);
// rendering children
if (this.getRendersChildren())
{
this.encodeChildren(context);
} // let children render itself
else
{
if (this.getChildCount() > 0)
{
for (int i = 0; i < this.getChildCount(); i++)
{
UIComponent comp = this.getChildren().get(i);
comp.encodeAll(context);
}
}
}
this.encodeEnd(context);
}
protected abstract void addFacesListener(FacesListener listener);
protected abstract FacesListener[] getFacesListeners(Class clazz);
protected abstract void removeFacesListener(FacesListener listener);
public abstract void queueEvent(FacesEvent event);
public abstract void processRestoreState(FacesContext context, Object state);
public abstract void processDecodes(FacesContext context);
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException
{
// The default implementation performs the following action. If the argument event is an instance of
// AfterRestoreStateEvent,
if (event instanceof PostRestoreStateEvent)
{
// call this.getValueExpression(java.lang.String) passing the literal string "binding"
ValueExpression expression = getValueExpression("binding");
// If the result is non-null, set the value of the ValueExpression to be this.
if (expression != null)
{
expression.setValue(getFacesContext().getELContext(), this);
}
//we issue a PostRestoreStateEvent, because the spec clearly states what UIComponent is allowed to do
//the main issue is that the spec does not say anything about a global dispatch on this level
//but a quick blackbox test against the ri revealed that the event clearly is dispatched
//at restore level for every component so we either issue it here or in UIViewRoot and/or the facelet
// and jsp restore state triggers, a central point is preferrble so we do it here
//TODO ask the EG the spec clearly contradicts blackbox RI behavior here
//getFacesContext().getApplication().publishEvent(getFacesContext(),
// PostRestoreStateEvent.class, UIComponent.class, this);
// JSF 2.2 vdl.createComponent() requires special handling to refresh
// dynamic parts when refreshing is done. The only way to do it is
// attaching a listener to PostRestoreStateEvent, so we need to do this
// invocation here.
// Do it inside UIComponent.processEvent() is better because in facelets
// UILeaf we can skip this part just overriding the method.
List<SystemEventListener> listeners = this.getListenersForEventClass(PostRestoreStateEvent.class);
if (!listeners.isEmpty())
{
for (int i = 0, size = listeners.size(); i < size; i++)
{
SystemEventListener listener = listeners.get(i);
if (listener.isListenerForSource(this))
{
// Check if the listener points again to the component, to
// avoid StackoverflowException
boolean shouldProcessEvent = true;
if (listener instanceof EventListenerWrapper &&
((EventListenerWrapper)listener).listenerCapability ==
EventListenerWrapper.LISTENER_TYPE_COMPONENT)
{
shouldProcessEvent = false;
}
if (shouldProcessEvent)
{
listener.processEvent(event);
}
}
}
}
}
}
public abstract void processValidators(FacesContext context);
public abstract void processUpdates(FacesContext context);
public abstract java.lang.Object processSaveState(FacesContext context);
public void subscribeToEvent(Class<? extends SystemEvent> eventClass,
ComponentSystemEventListener componentListener)
{
// The default implementation creates an inner SystemEventListener instance that wraps argument
// componentListener as the listener argument.
if (eventClass == null)
{
throw new NullPointerException("eventClass required");
}
if (componentListener == null)
{
throw new NullPointerException("componentListener required");
}
SystemEventListener listener = new EventListenerWrapper(this, componentListener);
// Make sure the map exists
if (_systemEventListenerClassMap == null)
{
_systemEventListenerClassMap = new HashMap<>(4, 1f);
}
List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
if (listeners == null)
{
listeners = new _DeltaList<>(3);
_systemEventListenerClassMap.put(eventClass, listeners);
}
// Deal with contains? Spec is silent
listeners.add(listener);
}
public void unsubscribeFromEvent(Class<? extends SystemEvent> eventClass,
ComponentSystemEventListener componentListener)
{
/*
* When doing the comparison to determine if an existing listener is equal to the argument componentListener
* (and thus must be removed), the equals() method on the existing listener must be invoked, passing the
* argument componentListener, rather than the other way around.
*
* -=Simon Lessard=- What is that supposed to mean? Are we supposed to keep
* an internal map of created listener wrappers?
* -= Leonardo Uribe=- Yes, it is supposed a wrapper should be used to hold listener references, to prevent
* serialize component instances on the state.
*/
if (eventClass == null)
{
throw new NullPointerException("eventClass required");
}
if (componentListener == null)
{
throw new NullPointerException("componentListener required");
}
if (_systemEventListenerClassMap != null)
{
List<SystemEventListener> listeners = _systemEventListenerClassMap.get(eventClass);
if (listeners != null && !listeners.isEmpty())
{
for (Iterator<SystemEventListener> it = listeners.iterator(); it.hasNext(); )
{
ComponentSystemEventListener listener
= ((EventListenerWrapper) it.next()).getComponentSystemEventListener();
if (listener != null && listener.equals(componentListener))
{
it.remove();
break;
}
}
}
}
}
/**
* The visit tree method, visit tree walks over a subtree and processes
* the callback object to perform some operation on the subtree
* <p>
* there are some details in the implementation which according to the spec have
* to be in place:
* a) before calling the callback and traversing into the subtree pushComponentToEL
* has to be called
* b) after the processing popComponentFromEL has to be performed to remove the component
* from the el
* </p>
* <p>
* The tree traversal optimizations are located in the visit context and can be replaced
* via the VisitContextFactory in the faces-config factory section
* </p>
*
* @param context the visit context which handles the processing details
* @param callback the callback to be performed
* @return false if the processing is not done true if we can shortcut
* the visiting because we are done with everything
*
* @since 2.0
*/
public boolean visitTree(VisitContext context, VisitCallback callback)
{
try
{
pushComponentToEL(context.getFacesContext(), this);
if (!isVisitable(context))
{
return false;
}
VisitResult res = context.invokeVisitCallback(this, callback);
switch (res)
{
//we are done nothing has to be processed anymore
case COMPLETE:
return true;
case REJECT:
return false;
//accept
default:
if (getFacetCount() > 0)
{
for (UIComponent facet : getFacets().values())
{
if (facet.visitTree(context, callback))
{
return true;
}
}
}
int childCount = getChildCount();
if (childCount > 0)
{
for (int i = 0; i < childCount; i++)
{
UIComponent child = getChildren().get(i);
if (child.visitTree(context, callback))
{
return true;
}
}
}
return false;
}
}
finally
{
//all components must call popComponentFromEl after visiting is finished
popComponentFromEL(context.getFacesContext());
}
}
protected abstract FacesContext getFacesContext();
protected abstract Renderer getRenderer(FacesContext context);
/**
* Note that id, clientId properties
* never change its value after the component is populated,
* so we don't need to store it on StateHelper or restore it when
* initialStateMarked == true
* (Note that rendererType is suspicious, in theory this field is
* initialized on constructor, but on 1.1 and 1.2 is saved and restored,
* so to keep backward behavior we put it on StateHelper )
*
* Also, facesListeners can't be wrapped on StateHelper because it
* needs to handle PartialStateHolder instances when it is saved and
* restored and this interface does not implement PartialStateHolder,
* so we can't propagate calls to markInitialState and clearInitialState,
* in other words, the List wrapped by StateHelper does not handle
* PartialStateHolder items.
*
* "bindings" map does not need to deal with PartialStateHolder instances,
* so we can use StateHelper feature (handle delta for this map or in
* other words track add/removal from bindings map as delta).
*/
enum PropertyKeys
{
rendered,
rendererType,
attributesMap,
bindings,
facesListeners,
passThroughAttributesMap
}
protected StateHelper getStateHelper()
{
return getStateHelper(true);
}
/**
* returns a delta state saving enabled state helper
* for the current component
* @param create if true a state helper is created if not already existing
* @return an implementation of the StateHelper interface or null if none exists and create is set to false
*/
protected StateHelper getStateHelper(boolean create)
{
if (_stateHelper != null)
{
return _stateHelper;
}
if (create)
{
_stateHelper = new _DeltaStateHelper(this);
}
return _stateHelper;
}
public TransientStateHelper getTransientStateHelper()
{
return getTransientStateHelper(true);
}
public TransientStateHelper getTransientStateHelper(boolean create)
{
if (_stateHelper != null)
{
return _stateHelper;
}
if (create)
{
_stateHelper = new _DeltaStateHelper(this);
}
return _stateHelper;
}
@Override
public void restoreTransientState(FacesContext context, Object state)
{
getTransientStateHelper().restoreTransientState(context, state);
}
@Override
public Object saveTransientState(FacesContext context)
{
return getTransientStateHelper().saveTransientState(context);
}
@SuppressWarnings("unchecked")
public void popComponentFromEL(FacesContext context)
{
Map<Object, Object> contextAttributes = context.getAttributes();
if (_honorCurrentComponentAttributes == null)
{
_honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
if (Boolean.TRUE.equals(_honorCurrentComponentAttributes))
{
// Pop the current UIComponent from the FacesContext attributes map so that the previous
// UIComponent, if any, becomes the current component.
List<UIComponent> componentStack
= (List<UIComponent>) contextAttributes.get(UIComponent._COMPONENT_STACK);
UIComponent oldCurrent = (UIComponent) contextAttributes.get(UIComponent.CURRENT_COMPONENT);
UIComponent newCurrent = null;
if (componentStack != null && !componentStack.isEmpty())
{
if (!this.equals(oldCurrent))
{
//Check on the componentStack if it can be found
int componentIndex = componentStack.lastIndexOf(this);
if (componentIndex >= 0)
{
for (int i = componentStack.size()-1; i >= componentIndex ; i--)
{
newCurrent = componentStack.remove(componentStack.size()-1);
}
}
else
{
//Component not found on the stack. Do not pop.
return;
}
}
else
{
newCurrent = componentStack.remove(componentStack.size()-1);
}
}
else
{
//Reset the current composite component
contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, null);
}
oldCurrent = (UIComponent) contextAttributes.put(UIComponent.CURRENT_COMPONENT, newCurrent);
if (oldCurrent != null && oldCurrent._isCompositeComponent() && newCurrent != null)
{
// Recalculate the current composite component
if (newCurrent._isCompositeComponent())
{
contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, newCurrent);
}
else
{
UIComponent previousCompositeComponent = null;
for (int i = componentStack.size() - 1; i >= 0; i--)
{
UIComponent component = componentStack.get(i);
if (component._isCompositeComponent())
{
previousCompositeComponent = component;
break;
}
}
contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, previousCompositeComponent);
}
}
}
else
{
// Pop the current UIComponent from the FacesContext attributes map so that the previous
// UIComponent, if any, becomes the current component.
List<UIComponent> componentStack
= (List<UIComponent>) contextAttributes.get(UIComponent._COMPONENT_STACK);
UIComponent oldCurrent = null;
if (componentStack != null && !componentStack.isEmpty())
{
int componentIndex = componentStack.lastIndexOf(this);
if (componentIndex >= 0)
{
for (int i = componentStack.size()-1; i >= componentIndex ; i--)
{
oldCurrent = componentStack.remove(componentStack.size()-1);
}
}
else
{
return;
}
}
if (oldCurrent != null && oldCurrent._isCompositeComponent())
{
// Recalculate the current composite component
UIComponent previousCompositeComponent = null;
for (int i = componentStack.size() - 1; i >= 0; i--)
{
UIComponent component = componentStack.get(i);
if (component._isCompositeComponent())
{
previousCompositeComponent = component;
break;
}
}
contextAttributes.put(UIComponent._CURRENT_COMPOSITE_COMPONENT_KEY, previousCompositeComponent);
}
}
}
@SuppressWarnings("unchecked")
public void pushComponentToEL(FacesContext context, UIComponent component)
{
if (component == null)
{
component = this;
}
Map<Object, Object> contextAttributes = context.getAttributes();
if (_honorCurrentComponentAttributes == null)
{
_honorCurrentComponentAttributes = _getHonorCurrentComponentAttributes(context);
}
if (Boolean.TRUE.equals(_honorCurrentComponentAttributes))
{
UIComponent currentComponent = (UIComponent) contextAttributes.get(UIComponent.CURRENT_COMPONENT);
if (currentComponent != null)
{
List<UIComponent> componentStack = (List<UIComponent>)
contextAttributes.get(UIComponent._COMPONENT_STACK);
if (componentStack == null)
{
componentStack = new ArrayList<>();
contextAttributes.put(UIComponent._COMPONENT_STACK, componentStack);
}
componentStack.add(currentComponent);
}
// Push the current UIComponent this to the FacesContext attribute map using the key CURRENT_COMPONENT
// saving the previous UIComponent associated with CURRENT_COMPONENT for a subsequent call to
// popComponentFromEL(javax.faces.context.FacesContext).
contextAttributes.put(UIComponent.CURRENT_COMPONENT, component);
if (component._isCompositeComponent())
{
contextAttributes.put(UIComponent.CURRENT_COMPOSITE_COMPONENT, component);
}
}
else
{
List<UIComponent> componentStack = (List<UIComponent>) contextAttributes.get(UIComponent._COMPONENT_STACK);
if (componentStack == null)
{
componentStack = new ArrayList<>();
contextAttributes.put(UIComponent._COMPONENT_STACK, componentStack);
}
componentStack.add(component);
if (component._isCompositeComponent())
{
contextAttributes.put(UIComponent._CURRENT_COMPOSITE_COMPONENT_KEY, component);
}
}
}
/**
* @since 1.2
*/
public int getFacetCount()
{
// not sure why the RI has this method in both
// UIComponent and UIComponentBase
Map<String, UIComponent> facets = getFacets();
return facets == null ? 0 : facets.size();
}
private boolean _isCompositeComponent()
{
//moved to the static method
return UIComponent.isCompositeComponent(this);
}
boolean isCachedFacesContext()
{
return false;
}
// Dummy method to prevent cast for UIComponentBase when caching
void setCachedFacesContext(FacesContext facesContext)
{
}
/**
* Gets value of "javax.faces.HONOR_CURRENT_COMPONENT_ATTRIBUTES" parameter cached in facesContext.attributes
* or resolves that param and caches its value in facesContext.attributes.
*
* @return canonical Boolean value for parameter "javax.faces.HONOR_CURRENT_COMPONENT_ATTRIBUTES"
*/
private static Boolean _getHonorCurrentComponentAttributes(FacesContext facesContext)
{
// performance note: we cache value in facesContext.attributes because
// 1) methods pushComponentToEL, popComponentFromEl, getCurrentComponent a getCurrentCompositeComponent
// can use that value
// 2) getExternalContext().getInitParameter has undetermined performance. In typical JSF app, there
// are one or two wrappers around external context; servletContext.getInitParameter has also unknown
// implementation and performance
Map<Object, Object> attributes = facesContext.getAttributes();
Boolean paramValue = (Boolean) attributes.get(HONOR_CURRENT_COMPONENT_ATTRIBUTES_PARAM_NAME);
if (paramValue == null)
{
String param
= facesContext.getExternalContext().getInitParameter(HONOR_CURRENT_COMPONENT_ATTRIBUTES_PARAM_NAME);
paramValue = (param != null && Boolean.parseBoolean(param));
attributes.put(HONOR_CURRENT_COMPONENT_ATTRIBUTES_PARAM_NAME, paramValue);
}
return paramValue;
}
private static class BundleMap implements Map<String, String>
{
private ResourceBundle _bundle;
private List<String> _values;
public BundleMap(ResourceBundle bundle)
{
_bundle = bundle;
}
// Optimized methods
@Override
public String get(Object key)
{
try
{
return (String) _bundle.getObject(key.toString());
}
catch (Exception e)
{
return "???" + key + "???";
}
}
@Override
public boolean isEmpty()
{
return !_bundle.getKeys().hasMoreElements();
}
@Override
public boolean containsKey(Object key)
{
try
{
return _bundle.getObject(key.toString()) != null;
}
catch (MissingResourceException e)
{
return false;
}
}
// Unoptimized methods
@Override
public Collection<String> values()
{
if (_values == null)
{
_values = new ArrayList<>();
for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
{
String v = _bundle.getString(enumer.nextElement());
_values.add(v);
}
}
return _values;
}
@Override
public int size()
{
return values().size();
}
@Override
public boolean containsValue(Object value)
{
return values().contains(value);
}
@Override
public Set<Map.Entry<String, String>> entrySet()
{
Set<Entry<String, String>> set = new HashSet<>();
for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
{
final String k = enumer.nextElement();
set.add(new Map.Entry<String, String>()
{
@Override
public String getKey()
{
return k;
}
@Override
public String getValue()
{
return (String) _bundle.getObject(k);
}
@Override
public String setValue(String value)
{
throw new UnsupportedOperationException();
}
});
}
return set;
}
@Override
public Set<String> keySet()
{
Set<String> set = new HashSet<>();
for (Enumeration<String> enumer = _bundle.getKeys(); enumer.hasMoreElements(); )
{
set.add(enumer.nextElement());
}
return set;
}
@Override
public String remove(Object key)
{
throw new UnsupportedOperationException();
}
@Override
public void putAll(Map<? extends String, ? extends String> t)
{
throw new UnsupportedOperationException();
}
@Override
public String put(String key, String value)
{
throw new UnsupportedOperationException();
}
@Override
public void clear()
{
throw new UnsupportedOperationException();
}
}
static class EventListenerWrapper implements SystemEventListener, PartialStateHolder
{
private Class<?> componentClass;
private ComponentSystemEventListener listener;
private boolean _initialStateMarked;
private int listenerCapability;
private transient UIComponent _component;
private static final int LISTENER_SAVE_STATE_HOLDER = 1;
private static final int LISTENER_SAVE_PARTIAL_STATE_HOLDER = 2;
private static final int LISTENER_TYPE_COMPONENT = 4;
private static final int LISTENER_TYPE_RENDERER = 8;
private static final int LISTENER_TYPE_OTHER = 16;
public EventListenerWrapper()
{
//need a no-arg constructor for state saving purposes
super();
}
/**
* Note we have two cases:
*
* 1. listener is an instance of UIComponent. In this case we cannot save and restore
* it because we need to point to the real component, but we can assume the instance
* is the same because UIComponent.subscribeToEvent says so. Also take into account
* this case is the reason why we need a wrapper for UIComponent.subscribeToEvent
* 2. listener is an instance of Renderer. In this case we can assume the same renderer
* used by the source component is the one used by the listener (ListenerFor).
* 3. listener is an instance of ComponentSystemEventListener but not from UIComponent.
* In this case, the instance could implement StateHolder, PartialStateHolder or do
* implement anything, so we have to deal with that case as usual.
*
* @param component
* @param listener
*/
public EventListenerWrapper(UIComponent component, ComponentSystemEventListener listener)
{
assert component != null;
assert listener != null;
this.componentClass = component.getClass();
this.listener = listener;
this._component = component;
initListenerCapability();
}
private void initListenerCapability()
{
this.listenerCapability = 0;
if (this.listener instanceof UIComponent)
{
this.listenerCapability = LISTENER_TYPE_COMPONENT;
}
else if (this.listener instanceof Renderer)
{
this.listenerCapability = LISTENER_TYPE_RENDERER;
}
else
{
if (this.listener instanceof PartialStateHolder)
{
this.listenerCapability = LISTENER_TYPE_OTHER | LISTENER_SAVE_PARTIAL_STATE_HOLDER;
}
else if (this.listener instanceof StateHolder)
{
this.listenerCapability = LISTENER_TYPE_OTHER | LISTENER_SAVE_STATE_HOLDER;
}
else
{
this.listenerCapability = LISTENER_TYPE_OTHER;
}
}
}
@Override
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
else if (o instanceof EventListenerWrapper)
{
EventListenerWrapper other = (EventListenerWrapper) o;
return componentClass.equals(other.componentClass) && listener.equals(other.listener);
}
else
{
return false;
}
}
@Override
public int hashCode()
{
return componentClass.hashCode() + listener.hashCode();
}
@Override
public boolean isListenerForSource(Object source)
{
// and its implementation of SystemEventListener.isListenerForSource(java.lang.Object) must return true
// if the instance class of this UIComponent is assignable from the argument to isListenerForSource.
return source.getClass().isAssignableFrom(componentClass);
}
public ComponentSystemEventListener getComponentSystemEventListener()
{
return listener;
}
@Override
public void processEvent(SystemEvent event)
{
// This inner class must call through to the argument componentListener in its implementation of
// SystemEventListener.processEvent(javax.faces.event.SystemEvent)
assert event instanceof ComponentSystemEvent;
listener.processEvent((ComponentSystemEvent) event);
}
@Override
public void clearInitialState()
{
if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
{
((PartialStateHolder) listener).clearInitialState();
}
_initialStateMarked = false;
}
@Override
public boolean initialStateMarked()
{
if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
{
return ((PartialStateHolder) listener).initialStateMarked();
}
return _initialStateMarked;
}
@Override
public void markInitialState()
{
if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
{
((PartialStateHolder) listener).markInitialState();
}
_initialStateMarked = true;
}
@Override
public boolean isTransient()
{
if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0 ||
(listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0)
{
return ((StateHolder) listener).isTransient();
}
return false;
}
@Override
public void restoreState(FacesContext context, Object state)
{
if (state == null)
{
return;
}
Object[] values = (Object[]) state;
componentClass = (Class) values[0];
if (values[1] instanceof _AttachedDeltaWrapper)
{
((StateHolder) listener).restoreState(context,
((_AttachedDeltaWrapper) values[1]).getWrappedStateObject());
}
else
{
//Full restore
listenerCapability = (Integer) values[2];
_component = UIComponent.getCurrentComponent(context);
if ((listenerCapability & LISTENER_TYPE_COMPONENT) != 0)
{
listener = _component;
}
else if ((listenerCapability & LISTENER_TYPE_RENDERER) != 0)
{
Renderer renderer = _component.getRenderer(context);
Integer i = (Integer) values[1];
if (i != null && i >= 0)
{
while (i > 0)
{
renderer = ((RendererWrapper) renderer).getWrapped();
i--;
}
}
listener = (ComponentSystemEventListener) renderer;
}
else
{
listener = (ComponentSystemEventListener)
UIComponentBase.restoreAttachedState(context, values[1]);
}
}
}
@Override
public Object saveState(FacesContext context)
{
if (!initialStateMarked())
{
Object[] state = new Object[3];
state[0] = componentClass;
//If this is not a component or a renderer, save it calling UIComponent.saveAttachedState
if (!((listenerCapability & LISTENER_TYPE_COMPONENT) != 0 ||
(listenerCapability & LISTENER_TYPE_RENDERER) != 0))
{
state[1] = UIComponentBase.saveAttachedState(context, listener);
}
else
{
if ( (listenerCapability & LISTENER_TYPE_RENDERER) != 0)
{
UIComponent componentRef = _component != null ? _component : getCurrentComponent(context);
Renderer renderer = componentRef.getRenderer(context);
int i = 0;
while (renderer != null && !renderer.getClass().equals(listener.getClass()))
{
if (renderer instanceof RendererWrapper)
{
renderer = ((RendererWrapper) renderer).getWrapped();
i++;
}
else
{
renderer = null;
i = -1;
}
}
if (i != -1)
{
// Store the number so we can get the right wrapper to invoke the method.
state[1] = i;
}
else
{
state[1] = null;
}
}
else
{
state[1] = null;
}
}
state[2] = (Integer) listenerCapability;
return state;
}
else
{
// If initialStateMarked() == true means two things:
// 1. PSS is being used
if ((listenerCapability & LISTENER_TYPE_COMPONENT) != 0)
{
return null;
}
else if ((listenerCapability & LISTENER_TYPE_RENDERER) != 0)
{
return null;
}
else
{
if ((listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0 ||
(listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0)
{
Object listenerSaved = ((StateHolder) listener).saveState(context);
if (listenerSaved == null)
{
return null;
}
return new Object[] { componentClass,
new _AttachedDeltaWrapper(listener.getClass(), listenerSaved) };
}
else
{
//This is not necessary, because the instance is considered serializable!
return null;
}
}
}
}
@Override
public void setTransient(boolean newTransientValue)
{
if ((listenerCapability & LISTENER_SAVE_PARTIAL_STATE_HOLDER) != 0 ||
(listenerCapability & LISTENER_SAVE_STATE_HOLDER) != 0)
{
((StateHolder) listener).setTransient(newTransientValue);
}
}
}
}