jakarta.faces.component.UIViewRoot Maven / Gradle / Ivy
Show all versions of jakarta.faces Show documentation
/*
* Copyright (c) 1997, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package jakarta.faces.component;
import static java.util.Collections.unmodifiableList;
import static java.util.logging.Level.SEVERE;
import static java.util.logging.Level.WARNING;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Logger;
import jakarta.el.MethodExpression;
import jakarta.faces.FacesException;
import jakarta.faces.FactoryFinder;
import jakarta.faces.application.ProjectStage;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.component.behavior.ClientBehaviorContext;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.PartialViewContext;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.ComponentSystemEvent;
import jakarta.faces.event.ExceptionQueuedEvent;
import jakarta.faces.event.ExceptionQueuedEventContext;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.PhaseEvent;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PhaseListener;
import jakarta.faces.event.PostConstructViewMapEvent;
import jakarta.faces.event.PostRestoreStateEvent;
import jakarta.faces.event.PreDestroyViewMapEvent;
import jakarta.faces.event.SystemEvent;
import jakarta.faces.event.SystemEventListener;
import jakarta.faces.lifecycle.Lifecycle;
import jakarta.faces.lifecycle.LifecycleFactory;
import jakarta.faces.render.ResponseStateManager;
import jakarta.faces.view.ViewDeclarationLanguage;
import jakarta.faces.view.ViewMetadata;
import jakarta.faces.webapp.FacesServlet;
/**
*
* UIViewRoot is the UIComponent that represents the root of
* the UIComponent tree. This component renders markup as the response to Ajax requests. It also serves as the root of
* the component tree, and as a place to hang per-view {@link PhaseListener}s.
*
*
*
* For each of the following lifecycle phase methods:
*
*
*
*
* -
*
* {@link #processDecodes}
*
*
*
* -
*
* {@link #processValidators}
*
*
*
* -
*
* {@link #processUpdates}
*
*
*
* -
*
* {@link #processApplication}
*
*
*
* -
*
* RenderResponse, via {@link #encodeBegin} and {@link #encodeEnd}
*
*
*
*
*
*
* Take the following action regarding PhaseListener
s.
*
*
*
*
*
* Initialize a state flag to false
.
*
*
*
* If {@link #getBeforePhaseListener} returns non-null
, invoke the listener, passing in the correct
* corresponding {@link PhaseId} for this phase.
*
*
*
* Upon return from the listener, call {@link FacesContext#getResponseComplete} and
* {@link FacesContext#getRenderResponse}. If either return true
set the internal state flag to
* true
.
*
*
*
* If or one or more listeners have been added by a call to {@link #addPhaseListener}, invoke the
* beforePhase
method on each one whose {@link PhaseListener#getPhaseId} matches the current phaseId,
* passing in the same PhaseId
as in the previous step.
*
*
*
* Upon return from each listener, call {@link FacesContext#getResponseComplete} and
* {@link FacesContext#getRenderResponse}. If either return true
set the internal state flag to
* true
.
*
*
*
*
* Execute any processing for this phase if the internal state flag was not set.
*
*
*
* If {@link #getAfterPhaseListener} returns non-null
, invoke the listener, passing in the correct
* corresponding {@link PhaseId} for this phase.
*
*
*
* If or one or more listeners have been added by a call to {@link #addPhaseListener}, invoke the
* afterPhase
method on each one whose {@link PhaseListener#getPhaseId} matches the current phaseId,
* passing in the same PhaseId
as in the previous step.
*
*
*
*/
public class UIViewRoot extends UIComponentBase implements UniqueIdVendor {
// ------------------------------------------------------ Manifest Constants
/**
* The key in the facet collection that contains the meta data of the view root.
* For example, the UIViewParameter
s are stored here.
*/
public static final String METADATA_FACET_NAME = "jakarta_faces_metadata";
/**
*
* The key in the value set of the view metadata BeanDescriptor, the value of which is a
* List<{@link UIViewParameter.Reference}>
.
*
*
* @since 2.0
*/
public static final String VIEW_PARAMETERS_KEY = "jakarta.faces.component.VIEW_PARAMETERS_KEY";
/**
*
* The standard component type for this component.
*
*/
public static final String COMPONENT_TYPE = "jakarta.faces.ViewRoot";
/**
*
* The standard component family for this component.
*
*/
public static final String COMPONENT_FAMILY = "jakarta.faces.ViewRoot";
/**
*
* If this param is set, and calling toLowerCase().equals("true") on a String representation of its value returns true,
* exceptions thrown by {@link PhaseListener}s installed on the {@code UIViewRoot} are queued to the
* {@link jakarta.faces.context.ExceptionHandler} instead of being logged and swallowed.
*
*
* @since 2.3
*/
public static final String VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME = "jakarta.faces.VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS";
/**
*
* The prefix that will be used for identifiers generated by the createUniqueId()
method.
*/
public static final String UNIQUE_ID_PREFIX = "j_id";
private static Lifecycle lifecycle;
private static final Logger LOGGER = Logger.getLogger("jakarta.faces", "jakarta.faces.LogStrings");
private static final String LOCATION_IDENTIFIER_PREFIX = "jakarta_faces_location_";
private static final Map LOCATION_IDENTIFIER_MAP = new LinkedHashMap<>(3, 1.0f);
static {
LOCATION_IDENTIFIER_MAP.put("head", LOCATION_IDENTIFIER_PREFIX + "HEAD");
LOCATION_IDENTIFIER_MAP.put("form", LOCATION_IDENTIFIER_PREFIX + "FORM");
LOCATION_IDENTIFIER_MAP.put("body", LOCATION_IDENTIFIER_PREFIX + "BODY");
}
enum PropertyKeys {
/**
*
* The render kit identifier of the {@link jakarta.faces.render.RenderKit} associated wth this view.
*
*/
renderKitId,
/**
*
* The view identifier of this view.
*
*/
viewId, locale, lastId, beforePhase, afterPhase, phaseListeners, resourceLibraryContracts
}
// ------------------------------------------------------------ Constructors
/**
*
* Create a new {@link UIViewRoot} instance with default property values.
*
*/
public UIViewRoot() {
super();
setRendererType(null);
setId(createUniqueId());
}
// ------------------------------------------------------ Instance Variables
/**
*
* Set during view build time.
*/
private Doctype doctype;
/**
*
* Set and cleared during the lifetime of a lifecycle phase. Has no meaning between phases. If true
, the
* lifecycle processing for the current phase must not take place.
*
*/
private boolean skipPhase;
/**
*
* Set and cleared during the lifetime of a lifecycle phase. Has no meaning between phases. If true
, the
* MethodExpression
associated with afterPhase
will not be invoked nor will any PhaseListeners
* associated with this UIViewRoot.
*/
private boolean beforeMethodException;
/**
*
* Set and cleared during the lifetime of a lifecycle phase. Has no meaning between phases.
*/
private ListIterator phaseListenerIterator;
// -------------------------------------------------------------- Properties
/**
*
* Override superclass method to always return {@code true} because a {@code UIViewRoot} is defined to always be in a
* view.
*
*
* @since 2.0
*/
@Override
public boolean isInView() {
return true;
}
/**
*
* Overridden to take no action.
*
*
* @since 2.0
* @param isInView ignore the value.
*/
@Override
public void setInView(boolean isInView) {
// no-op
}
/**
* @see UIComponent#getFamily()
*/
@Override
public String getFamily() {
return COMPONENT_FAMILY;
}
/**
*
* Return the render kit identifier of the {@link jakarta.faces.render.RenderKit} associated with this view. Unless
* explicitly set, as in {@link jakarta.faces.application.ViewHandler#createView}, the returned value will be
* null.
*
*
* @return the render kit id, or null
.
*/
public String getRenderKitId() {
return (String) getStateHelper().eval(PropertyKeys.renderKitId);
}
/**
*
* Set the render kit identifier of the {@link jakarta.faces.render.RenderKit} associated with this view. This method
* may be called at any time between the end of Apply Request Values phase of the request processing lifecycle
* (i.e. when events are being broadcast) and the beginning of the Render Response phase.
*
*
* @param renderKitId The new {@link jakarta.faces.render.RenderKit} identifier, or null
to disassociate
* this view with any specific {@link jakarta.faces.render.RenderKit} instance
*/
public void setRenderKitId(String renderKitId) {
getStateHelper().put(PropertyKeys.renderKitId, renderKitId);
}
/**
*
* Return the view identifier for this view.
*
*
* @return the view id.
*/
public String getViewId() {
return (String) getStateHelper().get(PropertyKeys.viewId);
}
/**
*
* Set the view identifier for this view.
*
*
* @param viewId The new view identifier
*/
public void setViewId(String viewId) {
getStateHelper().put(PropertyKeys.viewId, viewId);
}
/**
*
* Return the doctype of this view.
*
*
* @return the doctype of this view.
*
* @since 4.0
*/
public Doctype getDoctype() {
return doctype;
}
/**
*
* Set the doctype of this view.
*
*
* @param doctype The doctype.
*
* @since 4.0
*/
public void setDoctype(Doctype doctype) {
this.doctype = doctype;
}
// ------------------------------------------------ Event Management Methods
/**
*
* Return the {@link MethodExpression} that will be invoked before this view is rendered.
*
*
* @return the {@link MethodExpression} that will be invoked before this view is rendered.
* @since 1.2
*/
public MethodExpression getBeforePhaseListener() {
return (MethodExpression) getStateHelper().get(PropertyKeys.beforePhase);
}
/**
*
* Allow an arbitrary method to be called for the
* "beforePhase" event as the UIViewRoot runs through its lifecycle. This method will be called for all phases
* except {@link PhaseId#RESTORE_VIEW}. Unlike a true {@link PhaseListener},
* this approach doesn't allow for only receiving {@link PhaseEvent}s for a given phase.
*
*
*
* The method must conform to the signature of {@link PhaseListener#beforePhase}.
*
*
* @param newBeforePhase the {@link MethodExpression} that will be invoked before this view is rendered.
* @since 1.2
*/
public void setBeforePhaseListener(MethodExpression newBeforePhase) {
getStateHelper().put(PropertyKeys.beforePhase, newBeforePhase);
}
/**
*
* Return the {@link MethodExpression} that will be invoked after this view is rendered.
*
*
* @return the {@link MethodExpression} that will be invoked after this view is rendered.
*
* @since 1.2
*/
public MethodExpression getAfterPhaseListener() {
return (MethodExpression) getStateHelper().get(PropertyKeys.afterPhase);
}
/**
*
* Allow an arbitrary method to be called for the "afterPhase" event as the
* UIViewRoot runs through its lifecycle. This method will be called for all phases
* including {@link PhaseId#RESTORE_VIEW}. Unlike a true
* {@link PhaseListener}, this approach doesn't allow for only receiving {@link PhaseEvent}s for a given phase.
*
*
* The method must conform to the signature of {@link PhaseListener#afterPhase}.
*
*
* @param newAfterPhase the {@link MethodExpression} that will be invoked after this view is rendered.
*
* @since 1.2
*/
public void setAfterPhaseListener(MethodExpression newAfterPhase) {
getStateHelper().put(PropertyKeys.afterPhase, newAfterPhase);
}
/**
*
* If the argument toRemove
is in the list of {@link PhaseListener}s for this instance, it must be removed.
*
*
* @param toRemove the {@link PhaseListener} to remove.
*
* @since 1.2
*/
public void removePhaseListener(PhaseListener toRemove) {
getStateHelper().remove(PropertyKeys.phaseListeners, toRemove);
}
/**
*
* Add the argument newPhaseListener
to the list of {@link PhaseListener}s on this UIViewRoot
.
*
*
* @param newPhaseListener the {@link PhaseListener} to add
*
* @since 1.2
*/
public void addPhaseListener(PhaseListener newPhaseListener) {
getStateHelper().add(PropertyKeys.phaseListeners, newPhaseListener);
}
/**
*
*
* Return an unmodifiable list of the PhaseListener
instances attached to this UIViewRoot
* instance.
*
*
* @return the list of phase listeners.
* @since 2.0
*/
@SuppressWarnings("unchecked")
public List getPhaseListeners() {
List result = (List) getStateHelper().get(PropertyKeys.phaseListeners);
return result != null ? unmodifiableList(result) : Collections.emptyList();
}
/**
*
* Add argument component
, which is assumed to represent a resource instance, as a resource to this view. A
* resource instance is rendered by a resource Renderer
, as described in the Standard HTML RenderKit. The
* default implementation must call through to
* {@link #addComponentResource(jakarta.faces.context.FacesContext, jakarta.faces.component.UIComponent, java.lang.String)}.
*
*
* @param context {@link FacesContext} for the current request
* @param componentResource The {@link UIComponent} representing a {@link jakarta.faces.application.Resource} instance
*
* @since 2.0
*/
public void addComponentResource(FacesContext context, UIComponent componentResource) {
addComponentResource(context, componentResource, null);
}
/**
*
* Add argument component
, which is assumed to represent a resource instance, as a resource to this view. A
* resource instance is rendered by a resource Renderer
, as described in the Standard HTML RenderKit.
*
*
*
*
*
* The component
must be added using the following algorithm:
*
*
*
*
* -
*
* If the target
argument is null
, look for a target
attribute on the
* component
. If there is no target
attribute, set target
to be the default value
* head
*
*
*
* -
*
* Call {@link #getComponentResources} to obtain the child list for the given target.
*
*
*
* -
*
* If the component ID of componentResource
matches the the ID of a resource that has allready been added,
* remove the old resource.
*
*
*
* -
*
* Add the component
resource to the list.
*
*
*
*
*
*
*
*
*
* The resource Renderer
must ensure of the following:
*
* - Do not render when {@link ResourceHandler#isResourceRendered(FacesContext, String, String)} returns
*
true
.
* - After rendering, call {@link ResourceHandler#markResourceRendered(FacesContext, String, String)}.
*
*
*
* @param context {@link FacesContext} for the current request
* @param componentResource The {@link UIComponent} representing a {@link jakarta.faces.application.Resource} instance
* @param target The name of the facet for which the {@link UIComponent} will be added
*
* @since 2.0
*/
public void addComponentResource(FacesContext context, UIComponent componentResource, String target) {
final Map attributes = componentResource.getAttributes();
// look for a target in the component attribute set if arg is not set.
if (target == null) {
target = (String) attributes.get("target");
}
if (target == null) {
target = "head";
}
// this is not a normal "List" but a custom implementation called "ChildrenList"
List facetChildren = getComponentResources(context, target, true);
String id = componentResource.getId();
if (id != null) {
// normally the following code may produce a ConcurrentModificationException
// but probably the ChildrenList implementation knows how and when delete an UIComponent.
// Normally we should use "removeIf" but doing this will produce other errors during rendering
for (UIComponent c : facetChildren) {
if (id.equals(c.getId())) {
facetChildren.remove(c);
}
}
}
// add the resource to the facet
facetChildren.add(componentResource);
}
/**
*
* Return an unmodifiable List
of
* {@link UIComponent}s for the provided target
agrument. Each component
in the
* List
is assumed to represent a resource instance.
*
*
*
*
* The default implementation must use an algorithm equivalent to the the following.
*
*
* - Locate the facet for the
component
by calling getFacet()
using target
as
* the argument.
*
* - If the facet is not found, create the facet by calling
context.getApplication().createComponent().
* The argument to this method must refer to a component that extends
* {@link UIPanel} and overrides the encodeAll()
method to take no action. This is necessary to prevent
* component resources from being inadvertently rendered.
*
*
*
* - Set the
id
of the facet to be a string created by prepending the
* literal string “jakarta_faces_location_
” (without the quotes) to the value of the
* target
argument
*
* - Add the facet to the facets
Map
using target
as the key
*
* - return the children of the facet
*
*
*
*
* @param context the Faces context.
* @param target The name of the facet for which the components will be returned.
*
* @return A List
of {@link UIComponent} children of the facet with the name target
. If no
* children are found for the facet, return Collections.emptyList()
.
*
* @throws NullPointerException if target
or context
is null
*
* @since 2.0
*/
public List getComponentResources(FacesContext context, String target) {
if (target == null) {
throw new NullPointerException();
}
List resources = getComponentResources(context, target, false);
return resources != null ? resources : Collections.emptyList();
}
/**
*
* Return an unmodifiable ordered List
of all {@link UIComponent} resources of all supported targets. Each
* component
in the List
is assumed to represent a resource instance. The ordering is the same
* as the resources would appear in the component tree.
*
*
* @param context The Faces context.
*
* @return A List
of all {@link UIComponent} resources of all supported targets. If no resources are found,
* return an empty List
.
*
* @throws NullPointerException If context
is null
.
*
* @since 2.3
*/
public List getComponentResources(FacesContext context) {
List resources = new ArrayList<>();
for (String target : LOCATION_IDENTIFIER_MAP.keySet()) {
resources.addAll(getComponentResources(context, target));
}
return unmodifiableList(resources);
}
/**
*
* Remove argument component
, which is assumed to represent a resource instance, as a resource to this
* view.
*
*
* @param context {@link FacesContext} for the current request
* @param componentResource The {@link UIComponent} representing a {@link jakarta.faces.application.Resource} instance
*
* @since 2.0
*/
public void removeComponentResource(FacesContext context, UIComponent componentResource) {
removeComponentResource(context, componentResource, null);
}
/**
*
* Remove argument component
, which is assumed to represent a resource instance, as a resource to this
* view. A resource instance is rendered by a resource Renderer
, as described in the Standard HTML
* RenderKit.
*
*
*
*
* The component
must be removed using the following algorithm:
*
* - If the
target
argument is null
, look for a target
attribute on the
* component
.
* If there is no target
attribute, set target
to be the default value head
* - Call {@link #getComponentResources} to obtain the child list for the given target.
* - Remove the
component
resource from the child list.
*
*
*
* @param context {@link FacesContext} for the current request
* @param componentResource The {@link UIComponent} representing a {@link jakarta.faces.application.Resource} instance
* @param target The name of the facet for which the {@link UIComponent} will be added
*
* @since 2.0
*/
public void removeComponentResource(FacesContext context, UIComponent componentResource, String target) {
final Map attributes = componentResource.getAttributes();
// look for a target in the component attribute set if arg is not set.
if (target == null) {
target = (String) attributes.get("target");
}
if (target == null) {
target = "head";
}
List facetChildren = getComponentResources(context, target, false);
if (facetChildren != null) {
facetChildren.remove(componentResource);
}
}
/**
*
* An array of Lists of events that have been queued for later broadcast, with one List for each lifecycle phase. The
* list indices match the ordinals of the PhaseId instances. This instance is lazily instantiated. This list is
* NOT part of the state that is saved and restored for this component.
*
*/
private List> events;
/**
*
* Override the default {@link UIComponentBase#queueEvent} behavior to accumulate the queued events for later
* broadcasting.
*
*
* @param event {@link FacesEvent} to be queued
*
* @throws IllegalStateException if this component is not a descendant of a {@link UIViewRoot}
* @throws NullPointerException if event
is null
*/
@Override
public void queueEvent(FacesEvent event) {
if (event == null) {
throw new NullPointerException();
}
// We are a UIViewRoot, so no need to check for the ISE
if (events == null) {
int len = PhaseId.VALUES.size();
List> events = new ArrayList<>(len);
for (int i = 0; i < len; i++) {
events.add(new ArrayList<>(5));
}
this.events = events;
}
events.get(event.getPhaseId().getOrdinal()).add(event);
}
/**
*
* Broadcast any events that have been queued. First broadcast events that have been queued for
* {@link PhaseId#ANY_PHASE}. Then broadcast ane events that have been queued for the current phase. In both cases,
* {@link UIComponent#pushComponentToEL} must be called before the event is broadcast, and
* {@link UIComponent#popComponentFromEL} must be called after the return from the broadcast, even in the case of an
* exception.
*
*
* @param context {@link FacesContext} for the current request
* @param phaseId {@link PhaseId} of the current phase
*
* @since 2.0
*/
public void broadcastEvents(FacesContext context, PhaseId phaseId) {
if (events == null) {
// no events have been queued
return;
}
boolean hasMoreAnyPhaseEvents;
boolean hasMoreCurrentPhaseEvents;
List eventsForPhaseId = events.get(PhaseId.ANY_PHASE.getOrdinal());
// keep iterating till we have no more events to broadcast.
// This is necessary for events that cause other events to be
// queued. PENDING(edburns): here's where we'd put in a check
// to prevent infinite event queueing.
do {
// broadcast the ANY_PHASE events first
if (null != eventsForPhaseId) {
// We cannot use an Iterator because we will get
// ConcurrentModificationException errors, so fake it
while (!eventsForPhaseId.isEmpty()) {
FacesEvent event = eventsForPhaseId.get(0);
UIComponent source = event.getComponent();
UIComponent compositeParent = null;
try {
if (!UIComponent.isCompositeComponent(source)) {
compositeParent = UIComponent.getCompositeComponentParent(source);
}
if (compositeParent != null) {
compositeParent.pushComponentToEL(context, null);
}
source.pushComponentToEL(context, null);
source.broadcast(event);
} catch (AbortProcessingException e) {
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class,
new ExceptionQueuedEventContext(context, e, source, phaseId));
} finally {
source.popComponentFromEL(context);
if (compositeParent != null) {
compositeParent.popComponentFromEL(context);
}
}
eventsForPhaseId.remove(0); // Stay at current position
}
}
// then broadcast the events for this phase.
if ((eventsForPhaseId = events.get(phaseId.getOrdinal())) != null) {
// We cannot use an Iterator because we will get
// ConcurrentModificationException errors, so fake it
while (!eventsForPhaseId.isEmpty()) {
FacesEvent event = eventsForPhaseId.get(0);
UIComponent source = event.getComponent();
UIComponent compositeParent = null;
try {
if (!UIComponent.isCompositeComponent(source)) {
compositeParent = getCompositeComponentParent(source);
}
if (compositeParent != null) {
compositeParent.pushComponentToEL(context, null);
}
source.pushComponentToEL(context, null);
source.broadcast(event);
} catch (AbortProcessingException ape) {
// A "return" here would abort remaining events too
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class,
new ExceptionQueuedEventContext(context, ape, source, phaseId));
} finally {
source.popComponentFromEL(context);
if (compositeParent != null) {
compositeParent.popComponentFromEL(context);
}
}
eventsForPhaseId.remove(0); // Stay at current position
}
}
// true if we have any more ANY_PHASE events
hasMoreAnyPhaseEvents = null != (eventsForPhaseId = events.get(PhaseId.ANY_PHASE.getOrdinal())) && !eventsForPhaseId.isEmpty();
// true if we have any more events for the argument phaseId
hasMoreCurrentPhaseEvents = null != events.get(phaseId.getOrdinal()) && !events.get(phaseId.getOrdinal()).isEmpty();
} while (hasMoreAnyPhaseEvents || hasMoreCurrentPhaseEvents);
}
// ------------------------------------------------ Lifecycle Phase Handlers
@SuppressWarnings("unchecked")
private void initState() {
skipPhase = false;
beforeMethodException = false;
List listeners = (List) getStateHelper().get(PropertyKeys.phaseListeners);
phaseListenerIterator = listeners != null ? listeners.listIterator() : null;
}
// avoid creating the PhaseEvent if possible by doing redundant
// null checks.
private void notifyBefore(FacesContext context, PhaseId phaseId) {
if (getBeforePhaseListener() != null || phaseListenerIterator != null) {
notifyPhaseListeners(context, phaseId, true);
}
}
// avoid creating the PhaseEvent if possible by doing redundant
// null checks.
private void notifyAfter(FacesContext context, PhaseId phaseId) {
if (getAfterPhaseListener() != null || phaseListenerIterator != null) {
notifyPhaseListeners(context, phaseId, false);
}
}
/**
*
* The default implementation must call
* {@link UIComponentBase#processRestoreState} from within a try
block. The try
block must
* have a finally
block that ensures that no {@link FacesEvent}s remain in the event queue.
*
*
*
* @param context the FacesContext
for this requets
* @param state the opaque state object obtained from the {@link jakarta.faces.application.StateManager}
*/
@Override
public void processRestoreState(FacesContext context, Object state) {
// hack to work around older state managers that may not set the
// view root early enough
if (context.getViewRoot() == null) {
context.setViewRoot(this);
}
super.processRestoreState(context, state);
}
/**
*
* If the argument event
is an instance of {@link PostRestoreStateEvent} and
* {@link PartialViewContext#isPartialRequest()} returns true
, then loop over all component resources and
* call {@link ResourceHandler#markResourceRendered(FacesContext, String, String)} for each of them. Finally, delegate
* to super.
*
*/
@Override
public void processEvent(ComponentSystemEvent event) throws AbortProcessingException {
FacesContext context = event.getFacesContext();
if (event instanceof PostRestoreStateEvent && context.getPartialViewContext().isPartialRequest() && !context.getPartialViewContext().isRenderAll()) {
ResourceHandler resourceHandler = context.getApplication().getResourceHandler();
for (UIComponent resource : getComponentResources(context)) {
String name = (String) resource.getAttributes().get("name");
String library = (String) resource.getAttributes().get("library");
resourceHandler.markResourceRendered(context, name, library);
}
}
super.processEvent(event);
}
/**
*
*
* Perform partial processing by calling {@link jakarta.faces.context.PartialViewContext#processPartial} with
* {@link PhaseId#APPLY_REQUEST_VALUES} if:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we don't have a
* request to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* false
)
*
* Perform full processing by calling {@link UIComponentBase#processDecodes} if one of the following conditions are met:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we have a request
* to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* true
)
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
false
*
*
*
* Override the default {@link UIComponentBase#processDecodes} behavior to broadcast any queued events after the default
* processing or partial processing has been completed and to clear out any events for later phases if the event
* processing for this phase caused {@link FacesContext#renderResponse} or {@link FacesContext#responseComplete} to be
* called.
*
*
* @param context {@link FacesContext} for the request we are processing
*
* @throws NullPointerException if context
is null
*/
@Override
public void processDecodes(FacesContext context) {
initState();
notifyBefore(context, PhaseId.APPLY_REQUEST_VALUES);
try {
if (!skipPhase) {
if (context.getPartialViewContext().isPartialRequest() && !context.getPartialViewContext().isExecuteAll()) {
context.getPartialViewContext().processPartial(PhaseId.APPLY_REQUEST_VALUES);
} else {
super.processDecodes(context);
}
broadcastEvents(context, PhaseId.APPLY_REQUEST_VALUES);
}
} finally {
clearFacesEvents(context);
notifyAfter(context, PhaseId.APPLY_REQUEST_VALUES);
}
}
/**
*
* Visit the clientIds and, if the component is an instance of {@link EditableValueHolder}, call its
* {@link EditableValueHolder#resetValue} method. Use {@link #visitTree} to do the visiting.
*
*
* @since 2.2
*
* @param context the {@link FacesContext} for the request we are processing.
* @param clientIds The client ids to be visited, on which the described action will be taken.
*/
public void resetValues(FacesContext context, Collection clientIds) {
visitTree(VisitContext.createVisitContext(context, clientIds, null), DoResetValues.INSTANCE);
}
private static class DoResetValues implements VisitCallback {
private static final DoResetValues INSTANCE = new DoResetValues();
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof EditableValueHolder) {
((EditableValueHolder) target).resetValue();
}
// If render ID didn't specifically point to an EditableValueHolder. Visit all children as well.
else if (!VisitContext.ALL_IDS.equals(context.getIdsToVisit())) {
target.visitTree(VisitContext.createVisitContext(context.getFacesContext(), null, context.getHints()), this);
}
return VisitResult.ACCEPT;
}
}
/**
*
* Override the default {@link UIComponentBase#encodeBegin}
* behavior. If {@link #getBeforePhaseListener} returns non-null
, invoke it, passing a {@link PhaseEvent}
* for the {@link PhaseId#RENDER_RESPONSE} phase. If the internal list populated by calls to {@link #addPhaseListener}
* is non-empty, any listeners in that list must have their {@link PhaseListener#beforePhase} method called, passing the
* PhaseEvent
. Any {@code Exception}s that occur during invocation of any of the beforePhase listeners must
* be logged and swallowed, unless the
* {@link #VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME} parameter is set. In that case, the {@code Exception}
* must be passed to the {@link jakarta.faces.context.ExceptionHandler} as well.
*
*/
@Override
public void encodeBegin(FacesContext context) throws IOException {
initState();
notifyBefore(context, PhaseId.RENDER_RESPONSE);
if (!context.getResponseComplete()) {
super.encodeBegin(context);
}
}
/**
*
* If {@link jakarta.faces.context.PartialViewContext#isAjaxRequest} returns true
, perform partial
* rendering by calling {@link jakarta.faces.context.PartialViewContext#processPartial} with
* {@link PhaseId#RENDER_RESPONSE}. If {@link jakarta.faces.context.PartialViewContext#isAjaxRequest} returns
* false
, delegate to the parent {@link jakarta.faces.component.UIComponentBase#encodeChildren} method.
*
*
*
* If this {@link UIViewRoot} is an instance of {@link NamingContainer}, then the Jakarta Faces implementation
* must ensure that all encoded POST request parameter names are prefixed with
* {@link UIViewRoot#getContainerClientId(FacesContext)} as per rules of {@link UIComponent#getClientId(FacesContext)}.
* This also covers all predefined POST request parameters which are listed below:
*
*
* - {@link ResponseStateManager#VIEW_STATE_PARAM}
* - {@link ResponseStateManager#CLIENT_WINDOW_PARAM}
* - {@link ResponseStateManager#RENDER_KIT_ID_PARAM}
* - {@link ClientBehaviorContext#BEHAVIOR_SOURCE_PARAM_NAME}
* - {@link ClientBehaviorContext#BEHAVIOR_EVENT_PARAM_NAME}
* - {@link PartialViewContext#PARTIAL_EVENT_PARAM_NAME}
* - {@link PartialViewContext#PARTIAL_EXECUTE_PARAM_NAME}
* - {@link PartialViewContext#PARTIAL_RENDER_PARAM_NAME}
* - {@link PartialViewContext#RESET_VALUES_PARAM_NAME}
*
*
* @since 2.0
*/
@Override
public void encodeChildren(FacesContext context) throws IOException {
if (context.getPartialViewContext().isAjaxRequest()) {
context.getPartialViewContext().processPartial(PhaseId.RENDER_RESPONSE);
} else {
super.encodeChildren(context);
}
}
/**
*
* If {@link #getAfterPhaseListener} returns non-null
, invoke it,
* passing a {@link PhaseEvent} for the {@link PhaseId#RENDER_RESPONSE} phase. Any {@code Exception}s that occur during
* invocation of the afterPhase listener must be logged and swallowed, unless the
* {@link #VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME} parameter is set. In that case, the {@code Exception}
* must be passed to the {@link jakarta.faces.context.ExceptionHandler} as well.. If the current view has view
* parameters, as indicated by a non-empty and non-UnsupportedOperationException
throwing return from
* {@link jakarta.faces.view.ViewDeclarationLanguage#getViewMetadata(jakarta.faces.context.FacesContext, String)}, call
* {@link UIViewParameter#encodeAll} on each parameter. If calling getViewParameters()
causes
* UnsupportedOperationException
to be thrown, the exception must be silently swallowed.
*
*/
@Override
public void encodeEnd(FacesContext context) throws IOException {
super.encodeEnd(context);
encodeViewParameters(context);
notifyAfter(context, PhaseId.RENDER_RESPONSE);
}
/**
*
* Call {@link UIComponentBase#getRendersChildren} If {@link jakarta.faces.context.PartialViewContext#isAjaxRequest}
* returns true
this method must return true
.
*
*
* @since 2.0
*/
@Override
public boolean getRendersChildren() {
boolean value = super.getRendersChildren();
FacesContext context = FacesContext.getCurrentInstance();
if (context.getPartialViewContext().isAjaxRequest()) {
value = true;
}
return value;
}
/**
*
* Utility method that notifies phaseListeners for the given phaseId. Assumes that either or both the MethodExpression
* or phaseListeners data structure are non-null.
*
*
* @param context the context for this request
* @param phaseId the {@link PhaseId} of the current phase
* @param isBefore if true, notify beforePhase listeners. Notify afterPhase listeners otherwise.
*/
private void notifyPhaseListeners(FacesContext context, PhaseId phaseId, boolean isBefore) {
PhaseEvent event = createPhaseEvent(context, phaseId);
MethodExpression beforePhase = getBeforePhaseListener();
MethodExpression afterPhase = getAfterPhaseListener();
boolean hasPhaseMethodExpression = isBefore && null != beforePhase || !isBefore && null != afterPhase && !beforeMethodException;
MethodExpression expression = isBefore ? beforePhase : afterPhase;
if (hasPhaseMethodExpression) {
try {
expression.invoke(context.getELContext(), new Object[] { event });
skipPhase = context.getResponseComplete() || context.getRenderResponse();
} catch (Exception e) {
if (isBefore) {
beforeMethodException = true;
}
if (LOGGER.isLoggable(SEVERE)) {
LOGGER.log(SEVERE, "severe.component.unable_to_process_expression",
new Object[] { expression.getExpressionString(), isBefore ? "beforePhase" : "afterPhase" });
}
if (context.getAttributes().containsKey(VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME)) {
ExceptionQueuedEventContext extx = new ExceptionQueuedEventContext(context, e);
String booleanKey = isBefore ? ExceptionQueuedEventContext.IN_BEFORE_PHASE_KEY : ExceptionQueuedEventContext.IN_AFTER_PHASE_KEY;
extx.getAttributes().put(booleanKey, Boolean.TRUE);
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, extx);
}
return;
}
}
if (phaseListenerIterator != null && !beforeMethodException) {
while (isBefore ? phaseListenerIterator.hasNext() : phaseListenerIterator.hasPrevious()) {
PhaseListener curListener = isBefore ? phaseListenerIterator.next() : phaseListenerIterator.previous();
if (phaseId == curListener.getPhaseId() || PhaseId.ANY_PHASE == curListener.getPhaseId()) {
try {
if (isBefore) {
curListener.beforePhase(event);
} else {
curListener.afterPhase(event);
}
skipPhase = context.getResponseComplete() || context.getRenderResponse();
} catch (Exception e) {
if (isBefore && phaseListenerIterator.hasPrevious()) {
phaseListenerIterator.previous();
}
if (LOGGER.isLoggable(SEVERE)) {
LOGGER.log(SEVERE, "severe.component.uiviewroot_error_invoking_phaselistener", curListener.getClass().getName());
}
if (context.getAttributes().containsKey(VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME)
&& (Boolean) context.getAttributes().get(VIEWROOT_PHASE_LISTENER_QUEUES_EXCEPTIONS_PARAM_NAME)) {
ExceptionQueuedEventContext extx = new ExceptionQueuedEventContext(context, e);
String booleanKey = isBefore ? ExceptionQueuedEventContext.IN_BEFORE_PHASE_KEY : ExceptionQueuedEventContext.IN_AFTER_PHASE_KEY;
extx.getAttributes().put(booleanKey, Boolean.TRUE);
context.getApplication().publishEvent(context, ExceptionQueuedEvent.class, extx);
}
return;
}
}
}
}
}
private static PhaseEvent createPhaseEvent(FacesContext context, PhaseId phaseId) throws FacesException {
if (lifecycle == null) {
LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
String lifecycleId = context.getExternalContext().getInitParameter(FacesServlet.LIFECYCLE_ID_ATTR);
if (lifecycleId == null) {
lifecycleId = LifecycleFactory.DEFAULT_LIFECYCLE;
}
lifecycle = lifecycleFactory.getLifecycle(lifecycleId);
}
return new PhaseEvent(context, phaseId, lifecycle);
}
/**
*
*
* Perform partial processing by calling {@link jakarta.faces.context.PartialViewContext#processPartial} with
* {@link PhaseId#PROCESS_VALIDATIONS} if:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we don't have a
* request to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* false
)
*
* Perform full processing by calling {@link UIComponentBase#processValidators} if one of the following conditions are
* met:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we have a request
* to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* true
)
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
false
*
*
*
* Override the default {@link UIComponentBase#processValidators} behavior to broadcast any queued events after the
* default processing or partial processing has been completed and to clear out any events for later phases if the event
* processing for this phase caused {@link FacesContext#renderResponse} or {@link FacesContext#responseComplete} to be
* called.
*
*
* @param context {@link FacesContext} for the request we are processing
*
* @throws NullPointerException if context
is null
*/
@Override
public void processValidators(FacesContext context) {
initState();
notifyBefore(context, PhaseId.PROCESS_VALIDATIONS);
try {
if (!skipPhase) {
if (context.getPartialViewContext().isPartialRequest() && !context.getPartialViewContext().isExecuteAll()) {
context.getPartialViewContext().processPartial(PhaseId.PROCESS_VALIDATIONS);
} else {
super.processValidators(context);
}
broadcastEvents(context, PhaseId.PROCESS_VALIDATIONS);
}
} finally {
clearFacesEvents(context);
notifyAfter(context, PhaseId.PROCESS_VALIDATIONS);
}
}
/**
*
*
* Perform partial processing by calling {@link jakarta.faces.context.PartialViewContext#processPartial} with
* {@link PhaseId#UPDATE_MODEL_VALUES} if:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we don't have a
* request to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* false
)
*
* Perform full processing by calling {@link UIComponentBase#processUpdates} if one of the following conditions are met:
*
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
true
and we have a request
* to process all components in the view ({@link jakarta.faces.context.PartialViewContext#isExecuteAll} returns
* true
)
* - {@link jakarta.faces.context.PartialViewContext#isPartialRequest} returns
false
*
*
*
* Override the default {@link UIComponentBase} behavior to broadcast any queued events after the default processing or
* partial processing has been completed and to clear out any events for later phases if the event processing for this
* phase caused {@link FacesContext#renderResponse} or {@link FacesContext#responseComplete} to be called.
*
*
* @param context {@link FacesContext} for the request we are processing
*
* @throws NullPointerException if context
is null
*/
@Override
public void processUpdates(FacesContext context) {
initState();
notifyBefore(context, PhaseId.UPDATE_MODEL_VALUES);
try {
if (!skipPhase) {
if (context.getPartialViewContext().isPartialRequest() && !context.getPartialViewContext().isExecuteAll()) {
context.getPartialViewContext().processPartial(PhaseId.UPDATE_MODEL_VALUES);
} else {
super.processUpdates(context);
}
broadcastEvents(context, PhaseId.UPDATE_MODEL_VALUES);
}
} finally {
clearFacesEvents(context);
notifyAfter(context, PhaseId.UPDATE_MODEL_VALUES);
}
}
/**
*
* Broadcast any events that have been queued for the Invoke Application phase of the request processing
* lifecycle and to clear out any events for later phases if the event processing for this phase caused
* {@link FacesContext#renderResponse} or {@link FacesContext#responseComplete} to be called.
*
*
* @param context {@link FacesContext} for the request we are processing
*
* @throws NullPointerException if context
is null
*/
public void processApplication(FacesContext context) {
initState();
notifyBefore(context, PhaseId.INVOKE_APPLICATION);
try {
if (!skipPhase) {
// NOTE - no tree walk is performed; this is a UIViewRoot-only operation
broadcastEvents(context, PhaseId.INVOKE_APPLICATION);
}
} finally {
clearFacesEvents(context);
notifyAfter(context, PhaseId.INVOKE_APPLICATION);
}
}
// clear out the events if we're skipping to render-response
// or if there is a response complete signal.
private void clearFacesEvents(FacesContext context) {
if (context.getRenderResponse() || context.getResponseComplete()) {
if (events != null) {
for (List eventList : events) {
if (eventList != null) {
eventList.clear();
}
}
events = null;
}
}
}
/**
*
* Generate an identifier for a component. The identifier will be prefixed
* with UNIQUE_ID_PREFIX, and will be unique within the non-{@link NamingContainer}
* child sub-trees of this UIViewRoot.
*
*
* @return the identifier.
*/
public String createUniqueId() {
return createUniqueId(getFacesContext(), null);
}
/**
*
* Generate an identifier for a component. The identifier will be prefixed with UNIQUE_ID_PREFIX, and will be unique
* within this UIViewRoot. Optionally, a unique seed value can be supplied by component creators which should be
* included in the generated unique id.
*
*
* @param context FacesContext
* @param seed an optional seed value - e.g. based on the position of the component in the VDL-template
* @return a unique-id in this component-container
*/
@Override
public String createUniqueId(FacesContext context, String seed) {
if (seed != null) {
return UIViewRoot.UNIQUE_ID_PREFIX + seed;
}
Integer i = (Integer) getStateHelper().get(PropertyKeys.lastId);
int lastId = i != null ? i : 0;
getStateHelper().put(PropertyKeys.lastId, ++lastId);
return UIViewRoot.UNIQUE_ID_PREFIX + lastId;
}
/**
*
* Return the Locale
to be used in localizing the response being created for this view.
*
*
* Algorithm:
*
*
* If we have a locale
ivar, return it. If we have a value expression for "locale", get its value. If the
* value is null
, return the result of calling
* {@link jakarta.faces.application.ViewHandler#calculateLocale}. If the value is an instance of
* java.util.Locale
return it. If the value is a String, convert it to a java.util.Locale
and
* return it. If there is no value expression for "locale", return the result of calling
* {@link jakarta.faces.application.ViewHandler#calculateLocale}.
*
*
* @return The current Locale
obtained by executing the above algorithm.
*/
public Locale getLocale() {
Object result = getStateHelper().eval(PropertyKeys.locale);
if (result != null) {
Locale locale = null;
if (result instanceof Locale) {
locale = (Locale) result;
} else if (result instanceof String) {
locale = getLocaleFromString((String) result);
}
return locale;
}
FacesContext context = getFacesContext();
return context.getApplication().getViewHandler().calculateLocale(context);
}
// W3C XML specification refers to IETF RFC 1766 for language code
// structure, therefore the value for the xml:lang attribute should
// be in the form of language or language-country or
// language-country-variant.
private static Locale getLocaleFromString(String localeStr) throws IllegalArgumentException {
// length must be at least 2.
if (localeStr == null || localeStr.length() < 2) {
throw new IllegalArgumentException("Illegal locale String: " + localeStr);
}
Locale result = null;
String lang = null;
String country = null;
String variant = null;
char[] seps = { '-', '_' };
int inputLength = localeStr.length();
int i = 0;
int j = 0;
// to have a language, the length must be >= 2
if ( (i = indexOfSet(localeStr, seps, 0)) == -1 ) {
// we have only Language, no country or variant
if (2 != localeStr.length()) {
throw new IllegalArgumentException("Illegal locale String: " + localeStr);
}
lang = localeStr.toLowerCase();
}
// we have a separator, it must be either '-' or '_'
if (i != -1) {
lang = localeStr.substring(0, i);
// look for the country sep.
// to have a country, the length must be >= 5
if (inputLength >= 5 && -1 == (j = indexOfSet(localeStr, seps, i + 1))) {
// no further separators, length must be 5
if (inputLength != 5) {
throw new IllegalArgumentException("Illegal locale String: " + localeStr);
}
country = localeStr.substring(i + 1);
}
if (j != -1) {
country = localeStr.substring(i + 1, j);
// if we have enough separators for language, locale,
// and variant, the length must be >= 8.
if (inputLength >= 8) {
variant = localeStr.substring(j + 1);
} else {
throw new IllegalArgumentException("Illegal locale String: " + localeStr);
}
}
}
if (variant != null) {
result = new Locale(lang, country, variant);
} else if (country != null) {
result = new Locale(lang, country);
} else {
result = new Locale(lang, "");
}
return result;
}
/**
* @param str local string
* @param set the substring
* @param fromIndex starting index
* @return starting at fromIndex
, the index of the first occurrence of any substring from set
* in toSearch
, or -1 if no such match is found
*/
private static int indexOfSet(String str, char[] set, int fromIndex) {
int result = -1;
for (int i = fromIndex, len = str.length(); i < len; i++) {
for (int j = 0, innerLen = set.length; j < innerLen; j++) {
if (str.charAt(i) == set[j]) {
result = i;
break;
}
}
if (-1 != result) {
break;
}
}
return result;
}
/**
*
* Set the Locale
to be used in localizing the response being created for this view.
*
*
* @param locale The new localization Locale
*/
public void setLocale(Locale locale) {
getStateHelper().put(PropertyKeys.locale, locale);
// Make sure to appraise the Jakarta Expression Language of this switch in Locale.
FacesContext.getCurrentInstance().getELContext().setLocale(locale);
}
/**
*
* This implementation simply calls through to {@link #getViewMap(boolean)}, passing true
as the argument,
* and returns the result.
*
*
*
* @return the view map, or null
.
* @since 2.0
*/
public Map getViewMap() {
return getViewMap(true);
}
/**
*
* Returns a Map
that acts as the interface
* to the data store that is the "view scope", or, if this instance does not have such a Map
and the
* create
argument is true
, creates one and returns it. This map must be instantiated lazily
* and cached for return from subsequent calls to this method on this UIViewRoot
instance.
* {@link jakarta.faces.application.Application#publishEvent} must be called, passing
* the current FacesContext
as the first argument,
* {@link PostConstructViewMapEvent}.class
as the second argument,
* UIViewRoot.class
as the third argument and this
* UIViewRoot
instance as the fourth argument. It is necessary to pass the
* UIViewRoot.class
argument to account for cases when the UIViewRoot
has been extended with a
* custom class.
*
*
*
* The returned Map
must be implemented such that calling clear()
on the Map
* causes {@link jakarta.faces.application.Application#publishEvent} to be called, passing
* {@link PreDestroyViewMapEvent}.class
as the first argument and this UIViewRoot
instance as
* the second argument.
*
*
*
* Depending upon application configuration, objects stored in the view map may need to be Serializable
. In
* general, it is a good idea to ensure that any objects stored in the view map are Serializable
.
*
*
*
* For reasons made clear in {@link jakarta.faces.view.ViewScoped}, this map must ultimately be stored in the session.
* For this reason, a {@code true} value for the {@code create} argument will force the session to be created with a
* call to {@link jakarta.faces.context.ExternalContext#getSession(boolean)}.
*
*
*
*
* See {@link FacesContext#setViewRoot} for the specification of when the clear()
method must be called.
*
*
* @param create true
to create a new Map
for this instance if necessary; false
* to return null
if there's no current Map
.
* @return the view map, or null
.
* @since 2.0
*/
@SuppressWarnings("unchecked")
public Map getViewMap(boolean create) {
Map viewMap = (Map) getTransientStateHelper().getTransient("com.sun.faces.application.view.viewMap");
if (create && viewMap == null) {
viewMap = new ViewMap(getFacesContext().getApplication().getProjectStage());
getTransientStateHelper().putTransient("com.sun.faces.application.view.viewMap", viewMap);
getFacesContext().getApplication().publishEvent(getFacesContext(), PostConstructViewMapEvent.class, UIViewRoot.class, this);
}
return viewMap;
}
Map, List> viewListeners;
/**
*
* Install the listener instance referenced by argument listener
into the UIViewRoot
as a
* listener for events of type systemEventClass
.
*
*
*
* Note that installed listeners are not maintained as part of the UIViewRoot
's state.
*
*
* @param systemEvent the Class
of event for which listener
must be fired.
*
* @param listener the implementation of {@link jakarta.faces.event.SystemEventListener} whose
* {@link jakarta.faces.event.SystemEventListener#processEvent} method must be called when events of type
* systemEventClass
are fired.
*
* @throws NullPointerException if systemEventClass
or listener
are null
.
*
* @since 2.0
*/
public void subscribeToViewEvent(Class extends SystemEvent> systemEvent, SystemEventListener listener) {
if (systemEvent == null) {
throw new NullPointerException();
}
if (listener == null) {
throw new NullPointerException();
}
if (viewListeners == null) {
viewListeners = new HashMap<>(4, 1.0f);
}
viewListeners.computeIfAbsent(systemEvent, k -> new CopyOnWriteArrayList<>())
.add(listener);
}
/**
*
* Remove the listener instance referenced by argument listener
from the UIViewRoot
as a
* listener for events of type systemEventClass
.
*
* @param systemEvent the Class
of event for which listener
must be fired.
* @param listener the implementation of {@link jakarta.faces.event.SystemEventListener} whose
* {@link jakarta.faces.event.SystemEventListener#processEvent} method must be called when events of type
* systemEventClass
are fired.
*
* @throws NullPointerException if systemEventClass
or listener
are null
.
*
* @since 2.0
*/
public void unsubscribeFromViewEvent(Class extends SystemEvent> systemEvent, SystemEventListener listener) {
if (systemEvent == null) {
throw new NullPointerException();
}
if (listener == null) {
throw new NullPointerException();
}
if (viewListeners != null) {
List listeners = viewListeners.get(systemEvent);
if (listeners != null) {
listeners.remove(listener);
}
}
}
/**
*
* Return the SystemEventListener
instances registered on this UIComponent
instance that are
* interested in events of type eventClass
.
*
*
* @param systemEvent the Class
of event for which the listeners must be returned.
*
* @return Collection of view listeners.
*
* @throws NullPointerException if argument systemEvent
is null
.
*
* @since 2.0
*
*/
public List getViewListenersForEventClass(Class extends SystemEvent> systemEvent) {
if (systemEvent == null) {
throw new NullPointerException();
}
if (viewListeners != null) {
return viewListeners.get(systemEvent);
}
return null;
}
private void encodeViewParameters(FacesContext context) {
ViewDeclarationLanguage vdl = context.getApplication().getViewHandler().getViewDeclarationLanguage(context, getViewId());
if (vdl == null) {
return;
}
ViewMetadata metadata = vdl.getViewMetadata(context, getViewId());
if (metadata != null) { // perhaps it's not supported
Collection params = ViewMetadata.getViewParameters(this);
if (params.isEmpty()) {
return;
}
try {
for (UIViewParameter param : params) {
param.encodeAll(context);
}
} catch (IOException e) {
// IOException is forced by contract and is not expected to be thrown in this case
throw new RuntimeException("Unexpected IOException", e);
}
}
}
/**
*
*
* Restore ViewScope state. This is needed to allow the use of view scoped beans for EL-expressions in the template from
* which the component tree is built. For example: <ui:include
* src="#{viewScopedBean.includeFileName}"/>
.
*
*
*
* @param context current FacesContext.
* @param state the state object.
*/
public void restoreViewScopeState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state == null) {
return;
}
values = (Object[]) state;
super.restoreState(context, values[0]);
}
// END TENATIVE <== ???
// ----------------------------------------------------- StateHolder Methods
private Object[] values;
@Override
public Object saveState(FacesContext context) {
if (context == null) {
throw new NullPointerException();
}
String viewMapId = (String) getTransientStateHelper().getTransient("com.sun.faces.application.view.viewMapId");
Object superState = super.saveState(context);
if (superState != null || viewMapId != null) {
values = new Object[] { superState, viewMapId };
}
return values;
}
@Override
@SuppressWarnings("unchecked")
public void restoreState(FacesContext context, Object state) {
if (context == null) {
throw new NullPointerException();
}
if (state == null) {
return;
}
values = (Object[]) state;
if (!context.getAttributes().containsKey("com.sun.faces.application.view.restoreViewScopeOnly")) {
super.restoreState(context, values[0]);
}
String viewMapId = (String) values[1];
getTransientStateHelper().putTransient("com.sun.faces.application.view.viewMapId", viewMapId);
Map viewMaps = (Map) context.getExternalContext().getSessionMap().get("com.sun.faces.application.view.activeViewMaps");
if (viewMaps != null) {
Map viewMap = (Map) viewMaps.get(viewMapId);
getTransientStateHelper().putTransient("com.sun.faces.application.view.viewMap", viewMap);
}
}
// --------------------------------------------------------- Private Methods
private static String getIdentifier(String target) {
// check map
String id = LOCATION_IDENTIFIER_MAP.computeIfAbsent(target, t -> LOCATION_IDENTIFIER_PREFIX + t);
return id;
}
private List getComponentResources(FacesContext context, String target, boolean create) {
String location = getIdentifier(target);
UIComponent facet = getFacet(location);
if (facet == null && create) {
// Using an implementation specific component type to prevent
// component resources being rendered at the incorrect time if
// a caller calls UIViewRoot.encodeAll().
facet = context.getApplication().createComponent("jakarta.faces.ComponentResourceContainer");
facet.setId(location);
getFacets().put(location, facet);
}
return facet != null ? facet.getChildren() : null;
}
private static final class ViewMap extends HashMap {
private static final long serialVersionUID = -1L;
private final ProjectStage stage;
// -------------------------------------------------------- Constructors
ViewMap(ProjectStage stage) {
this.stage = stage;
}
// ---------------------------------------------------- Methods from Map
@Override
public void clear() {
FacesContext context = FacesContext.getCurrentInstance();
context.getApplication().publishEvent(context, PreDestroyViewMapEvent.class, UIViewRoot.class, context.getViewRoot());
super.clear();
}
@Override
public Object put(String key, Object value) {
if (value != null && ProjectStage.Development.equals(stage) && !(value instanceof Serializable)) {
LOGGER.log(WARNING, "warning.component.uiviewroot_non_serializable_attribute_viewmap", new Object[] { key, value.getClass().getName() });
}
return super.put(key, value);
}
@Override
public void putAll(Map extends String, ?> map) {
// the HashMap.putAll use an internal algorithm instead of the put method.
// We want that our putAll calls our put method to log the warn message
map.forEach(this::put);
}
} // END ViewMap
}