All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jakarta.faces.component.UIViewAction Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 1997, 2020 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 java.util.Map;

import jakarta.el.MethodExpression;
import jakarta.faces.FacesException;
import jakarta.faces.application.NavigationHandler;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.FacesContextWrapper;
import jakarta.faces.event.AbortProcessingException;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.event.ActionListener;
import jakarta.faces.event.FacesEvent;
import jakarta.faces.event.PhaseId;
import jakarta.faces.event.PreRenderViewEvent;
import jakarta.faces.view.ViewMetadata;

/**
 *
 * 

* UIViewAction represents a method invocation that occurs during the * request processing lifecycle, usually in response to an initial request, as opposed to a postback. *

* *
* *

* The {@link jakarta.faces.view.ViewDeclarationLanguage} implementation must cause an instance of this component to be * placed in the view for each occurrence of an <f:viewAction * /> element placed inside of an <f:metadata * /> element. The user must place <f:metadata * /> as a direct child of the UIViewRoot. *

* *

* Because this class implements {@link ActionSource}, any actions that one would normally take on a component that * implements ActionSource, such as {@link UICommand}, are valid for instances of this class. Instances of * this class participate in the regular Jakarta Faces lifecycle, including on Ajax requests. *

* *

* The purpose of this component is to provide a light-weight front-controller solution for executing code upon the * loading of a Jakarta Faces view to support the integration of system services, content retrieval, view * management, and navigation. This functionality is especially useful for non-faces (initial) requests. *

* *

* The most common use case for this component is to take actions necessary for a particular view, often with the help * of one or more {@link UIViewParameter}s. *

* *

* The {@link NavigationHandler} is consulted after the action is invoked to carry out the navigation case that matches * the action signature and outcome. If a navigation case is matched that causes the new viewId to be different from the * current viewId, the runtime must force a redirect to that matched navigation case with different viewId, regardless * of whether or not the matched navigation case with different viewId called for a redirect. * If the navigation will result in a flow transition, the appropriate metadata must be * included in the query string for the redirect. * See section 7.4.2 "Default NavigationHandler Algorithm" of the Jakarta Faces Specification Document, for the * specification of how to handle {@code <redirect />} cases. *

* *

* It's important to note that the full component tree is not built before the UIViewAction components are processed on * an non-faces (initial) request. Rather, the component tree only contains the {@link ViewMetadata}, an important part * of the optimization of this component and what sets it apart from a {@link PreRenderViewEvent} listener. *

* *
* * @since 2.2 */ public class UIViewAction extends UIComponentBase implements ActionSource { // ------------------------------------------------------ Manifest Constants /** *

* The standard component type for this component. *

*/ public static final String COMPONENT_TYPE = "jakarta.faces.ViewAction"; /** *

* The standard component family for this component. *

*/ public static final String COMPONENT_FAMILY = "jakarta.faces.ViewAction"; private static final String UIVIEWACTION_BROADCAST = "jakarta.faces.ViewAction.broadcast"; private static final String UIVIEWACTION_EVENT_COUNT = "jakarta.faces.ViewAction.eventCount"; /** * Properties that are tracked by state saving. */ enum PropertyKeys { onPostback, actionExpression, immediate, phase, renderedAttr("if"); private String name; PropertyKeys() { } PropertyKeys(final String name) { this.name = name; } @Override public String toString() { return name != null ? name : super.toString(); } } // ------------------------------------------------------------ Constructors /** *

* Create a new {@link UIViewAction} instance with default property values. *

*/ public UIViewAction() { super(); setRendererType(null); } // -------------------------------------------------------------- Properties @Override public String getFamily() { return COMPONENT_FAMILY; } private void incrementEventCount(FacesContext context) { Map attrs = context.getAttributes(); Integer count = (Integer) attrs.get(UIVIEWACTION_EVENT_COUNT); if (null == count) { attrs.put(UIVIEWACTION_EVENT_COUNT, 1); } else { attrs.put(UIVIEWACTION_EVENT_COUNT, count + 1); } } private boolean decrementEventCountAndReturnTrueIfZero(FacesContext context) { boolean result = true; Map attrs = context.getAttributes(); Integer count = (Integer) attrs.get(UIVIEWACTION_EVENT_COUNT); if (null != count) { count = count - 1; if (count < 1) { attrs.remove(UIVIEWACTION_EVENT_COUNT); result = true; } else { attrs.put(UIVIEWACTION_EVENT_COUNT, count); result = false; } } return result; } /** *

* If the value of the component's immediate attribute is true, the action will be invoked * during the Apply Request Values Jakarta Faces lifecycle phase. Otherwise, the action will be invoked * during the Invoke Application phase, the default behavior. The phase can be set explicitly in the * phase attribute, which takes precedence over the immediate attribute. *

* * @since 2.2 */ @Override public boolean isImmediate() { return (Boolean) getStateHelper().eval(PropertyKeys.immediate, false); } /** * {@inheritDoc} * * @since 2.2 */ @Override public void setImmediate(final boolean immediate) { getStateHelper().put(PropertyKeys.immediate, immediate); } /** *

* Returns the name of the lifecycle phase in which the action is to be queued. *

* * @return the phase (as string). * @since 2.2 */ public String getPhase() { PhaseId myPhaseId = getPhaseId(); String result = null; if (null != myPhaseId) { result = myPhaseId.getName(); } return result; } /** *

* Attempt to set the lifecycle phase in which this instance will queue its {@link ActionEvent}. Pass the argument * phase to {@link PhaseId#phaseIdValueOf}. If the result is not one of the following values, * FacesException must be thrown. *

* *
    * *
  • *

    * {@link PhaseId#APPLY_REQUEST_VALUES} *

    *
  • *
  • *

    * {@link PhaseId#PROCESS_VALIDATIONS} *

    *
  • *
  • *

    * {@link PhaseId#UPDATE_MODEL_VALUES} *

    *
  • *
  • *

    * {@link PhaseId#INVOKE_APPLICATION} *

    *
  • * *
* *

* If set, this value takes precedence over the immediate flag. *

* * @param phase the phase id (as string value). * @since 2.2 */ public void setPhase(final String phase) { PhaseId myPhaseId = PhaseId.phaseIdValueOf(phase); if (PhaseId.ANY_PHASE.equals(myPhaseId) || PhaseId.RESTORE_VIEW.equals(myPhaseId) || PhaseId.RENDER_RESPONSE.equals(myPhaseId)) { throw new FacesException("View actions cannot be executed in specified phase: [" + myPhaseId + "]"); } getStateHelper().put(PropertyKeys.phase, myPhaseId.getName()); } private void setIsProcessingUIViewActionBroadcast(FacesContext context, boolean value) { Map attrs = context.getAttributes(); if (value) { attrs.put(UIVIEWACTION_BROADCAST, Boolean.TRUE); } else { attrs.remove(UIVIEWACTION_BROADCAST); } } /** *

* Returns true if the current request processing lifecycle is in the midst of processing the broadcast of * an event queued during a call to {@link #decode}. The implementation of {@link #broadcast} is responsible for * ensuring that calls to this method accurately reflect this fact. *

* * @param context {@link FacesContext} for the current request * @return true is currently processing broadcast, false otherwise. * @since 2.2 * */ public static boolean isProcessingBroadcast(FacesContext context) { boolean result = context.getAttributes().containsKey(UIVIEWACTION_BROADCAST); return result; } private PhaseId getPhaseId() { PhaseId myPhaseId = null; String phaseIdString = (String) getStateHelper().eval(PropertyKeys.phase); if (phaseIdString != null) { myPhaseId = PhaseId.phaseIdValueOf(phaseIdString); } return myPhaseId; } /** * {@inheritDoc} * * @since 2.2 */ @Override public void addActionListener(final ActionListener listener) { addFacesListener(listener); } /** * {@inheritDoc} * * @since 2.2 */ @Override public ActionListener[] getActionListeners() { ActionListener[] al = (ActionListener[]) getFacesListeners(ActionListener.class); return al; } /** * {@inheritDoc} * * @since 2.2 */ @Override public void removeActionListener(final ActionListener listener) { removeFacesListener(listener); } /** * {@inheritDoc} * * @since 2.2 */ @Override public MethodExpression getActionExpression() { return (MethodExpression) getStateHelper().get(PropertyKeys.actionExpression); } /** * {@inheritDoc} * * @param actionExpression the action expression. * @since 2.2 */ @Override public void setActionExpression(final MethodExpression actionExpression) { getStateHelper().put(PropertyKeys.actionExpression, actionExpression); } /** *

* If true this component will operate on postback. *

* * @return true if operating upon postback, false otherwise. * @since 2.2 */ public boolean isOnPostback() { return (Boolean) getStateHelper().eval(PropertyKeys.onPostback, false); } /** *

* Controls whether or not this component operates on postback. *

* * @param onPostback the onPostback flag. * @since 2.2 */ public void setOnPostback(final boolean onPostback) { getStateHelper().put(PropertyKeys.onPostback, onPostback); } /** *

* Returns the if property of this component. If true * then this component should take the actions specified in the {@link #decode} method. *

* * @return true if it should take the actions, * false otherwise. * @since 2.2 */ @Override public boolean isRendered() { return (Boolean) getStateHelper().eval(PropertyKeys.renderedAttr, true); } /** *

* Sets the if property of this component. *

* * @param condition the new value of the property. * * @since 2.2 */ @Override public void setRendered(final boolean condition) { getStateHelper().put(PropertyKeys.renderedAttr, condition); } // ----------------------------------------------------- UIComponent Methods /** *

* Enable the method invocation specified by this component instance to return a value that performs navigation, similar * in spirit to {@link UICommand#broadcast}. *

* *
* *

* Take no action and return immediately if any of the following conditions are true. *

* *
    * *
  • *

    * The response has already been marked as complete. *

    *
  • * *
  • *

    * The current UIViewRoot is different from the event's source's UIViewRoot. *

    *
  • * *
* *

* Save a local reference to the viewId of the current UIViewRoot. For discussion, let this reference be * viewIdBeforeAction. *

* *

* Obtain the {@link ActionListener} from the {@link jakarta.faces.application.Application}. Wrap the current * {@link FacesContext} in an implementation of {@link jakarta.faces.context.FacesContextWrapper} that overrides the * {@link FacesContext#renderResponse} method such that it takes no action. Set the current FacesContext to * be the FacesContextWrapper instance. Make it so a call to {@link #isProcessingBroadcast} on the current * FacesContext will return true. This is necessary because the * {@link jakarta.faces.application.NavigationHandler} will call this method to determine if the navigation is happening * as the result of a UIViewAction. Invoke {@link ActionListener#processAction}. In a finally * block, restore the original FacesContext, make it so a call to {@link #isProcessingBroadcast} on the * current context will return false and discard the wrapper. *

* *

* If the response has been marked as complete during the invocation of processAction(), take no further * action and return. Otherwise, compare viewIdBeforeAction with the viewId of the UIViewRoot on * the FacesContext after the invocation of processAction(). If the two viewIds are the same * and no more UIViewAction events have been queued by a call to {@link #decode}, call * {@link FacesContext#renderResponse} and return. It is possible to detect the case where no more * UIViewAction events have been queued because the number of such events queued has been noted in the * specification for {@link #decode}. Otherwise, execute the lifecycle on the new UIViewRoot. *

* *
* * @param event {@link FacesEvent} to be broadcast * * @throws AbortProcessingException Signal the Jakarta Faces implementation that no further processing on the * current event should be performed * @throws IllegalArgumentException if the implementation class of this {@link FacesEvent} is not supported by this * component * @throws NullPointerException if event is null * * @since 2.2 */ @Override public void broadcast(final FacesEvent event) throws AbortProcessingException { super.broadcast(event); FacesContext context = event.getFacesContext(); if (!(event instanceof ActionEvent)) { throw new IllegalArgumentException(); } // OPEN QUESTION: should we consider a navigation to the same view as a // no-op navigation? // only proceed if the response has not been marked complete and // navigation to another view has not occurred if (!context.getResponseComplete() && context.getViewRoot() == getViewRootOf(event)) { ActionListener listener = context.getApplication().getActionListener(); if (listener != null) { boolean hasMoreViewActionEvents = false; UIViewRoot viewRootBefore = context.getViewRoot(); assert null != viewRootBefore; InstrumentedFacesContext instrumentedContext = null; try { instrumentedContext = new InstrumentedFacesContext(context); setIsProcessingUIViewActionBroadcast(context, true); // defer the call to renderResponse() that happens in // ActionListener#processAction(ActionEvent) instrumentedContext.disableRenderResponseControl().set(); listener.processAction((ActionEvent) event); hasMoreViewActionEvents = !decrementEventCountAndReturnTrueIfZero(context); } finally { setIsProcessingUIViewActionBroadcast(context, false); if (null != instrumentedContext) { instrumentedContext.restore(); } } // if the response is marked complete, the story is over if (!context.getResponseComplete()) { UIViewRoot viewRootAfter = context.getViewRoot(); assert null != viewRootAfter; // if the view id changed as a result of navigation, then // execute the Jakarta Faces lifecycle for the new view // id String viewIdBefore = viewRootBefore.getViewId(); String viewIdAfter = viewRootAfter.getViewId(); assert null != viewIdBefore && null != viewIdAfter; boolean viewIdsSame = viewIdBefore.equals(viewIdAfter); if (viewIdsSame && !hasMoreViewActionEvents) { // apply the deferred call (relevant when immediate is true) context.renderResponse(); } } } } } /** *

* Override behavior from the superclass to queue an {@link ActionEvent} that may result in the invocation of the * action or any actionListeners that may be associated with this instance. *

* *
* *

* Take no action if any of the following conditions are true: *

* *
    * *
  • *

    * The current request is a postback and the instance has been configured to not operate on postback. See * {@link #isOnPostback}. *

    *
  • * *
  • *

    * The condition stated in the if property evaluates to false. See {@link #isRendered} *

    * .
  • * *
* *

* Instantiate an {@link ActionEvent}, passing this component instance as the source. Set the phaseId * property of the ActionEvent as follows. *

* *
    * *
  • *

    * If this component instance has been configured with a specific lifecycle phase with a call to {@link #setPhase} use * that as the phaseId *

    *
  • * *
  • *

    * If the value of the immediate property is true, use {@link PhaseId#APPLY_REQUEST_VALUES}. *

    *
  • * *
  • *

    * Otherwise, use {@link PhaseId#INVOKE_APPLICATION}. *

    *
  • * *
* *

* Queue the event with a call to {@link #queueEvent}. Keep track of the number of events that are queued in this way on * this run through the lifecycle. This information is necessary during processing in {@link #broadcast}. *

* *
* * @since 2.2 * */ @Override public void decode(final FacesContext context) { if (context == null) { throw new NullPointerException(); } if (context.isPostback() && !isOnPostback() || !isRendered()) { return; } ActionEvent e = new ActionEvent(context, this); PhaseId phaseId = getPhaseId(); if (phaseId != null) { e.setPhaseId(phaseId); } else if (isImmediate()) { e.setPhaseId(PhaseId.APPLY_REQUEST_VALUES); } else { e.setPhaseId(PhaseId.INVOKE_APPLICATION); } incrementEventCount(context); queueEvent(e); } private UIViewRoot getViewRootOf(final FacesEvent e) { UIComponent c = e.getComponent(); do { if (c instanceof UIViewRoot) { return (UIViewRoot) c; } c = c.getParent(); } while (c != null); return null; } /** * A FacesContext delegator that gives us the necessary controls over the FacesContext to allow the execution of the * lifecycle to accomodate the UIViewAction sequence. */ private static class InstrumentedFacesContext extends FacesContextWrapper { private boolean viewRootCleared = false; private boolean renderedResponseControlDisabled = false; private Boolean postback = null; public InstrumentedFacesContext(final FacesContext wrapped) { super(wrapped); } @Override public UIViewRoot getViewRoot() { if (viewRootCleared) { return null; } return super.getViewRoot(); } @Override public void setViewRoot(final UIViewRoot viewRoot) { viewRootCleared = false; super.setViewRoot(viewRoot); } @Override public boolean isPostback() { return postback == null ? super.isPostback() : postback; } @Override public void renderResponse() { if (!renderedResponseControlDisabled) { super.renderResponse(); } } /** * Make it look like we have dispatched a request using the include method. */ public InstrumentedFacesContext pushViewIntoRequestMap() { getExternalContext().getRequestMap().put("jakarta.servlet.include.servlet_path", super.getViewRoot().getViewId()); return this; } public InstrumentedFacesContext clearPostback() { postback = false; return this; } public InstrumentedFacesContext clearViewRoot() { viewRootCleared = true; return this; } public InstrumentedFacesContext disableRenderResponseControl() { renderedResponseControlDisabled = true; return this; } public void set() { setCurrentInstance(this); } public void restore() { setCurrentInstance(getWrapped()); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy