org.omnifaces.util.Components Maven / Gradle / Ivy
/*
* Copyright OmniFaces
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/
package org.omnifaces.util;
import static jakarta.faces.application.ResourceHandler.JSF_SCRIPT_LIBRARY_NAME;
import static jakarta.faces.application.ResourceHandler.JSF_SCRIPT_RESOURCE_NAME;
import static jakarta.faces.application.StateManager.IS_BUILDING_INITIAL_STATE;
import static jakarta.faces.component.UIComponent.getCompositeComponentParent;
import static jakarta.faces.component.behavior.ClientBehaviorContext.BEHAVIOR_EVENT_PARAM_NAME;
import static jakarta.faces.component.behavior.ClientBehaviorContext.BEHAVIOR_SOURCE_PARAM_NAME;
import static jakarta.faces.component.search.SearchExpressionContext.createSearchExpressionContext;
import static jakarta.faces.component.search.SearchExpressionHint.IGNORE_NO_RESULT;
import static jakarta.faces.component.search.SearchExpressionHint.RESOLVE_SINGLE_COMPONENT;
import static jakarta.faces.component.visit.VisitContext.createVisitContext;
import static jakarta.faces.component.visit.VisitHint.SKIP_ITERATION;
import static jakarta.faces.component.visit.VisitResult.ACCEPT;
import static jakarta.faces.event.PhaseId.RENDER_RESPONSE;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableList;
import static java.util.logging.Level.FINEST;
import static java.util.regex.Pattern.quote;
import static org.omnifaces.resourcehandler.DefaultResourceHandler.FACES_SCRIPT_RESOURCE_NAME;
import static org.omnifaces.util.Ajax.load;
import static org.omnifaces.util.Ajax.oncomplete;
import static org.omnifaces.util.Events.subscribeToRequestBeforePhase;
import static org.omnifaces.util.Faces.getContext;
import static org.omnifaces.util.Faces.getELContext;
import static org.omnifaces.util.Faces.getFaceletContext;
import static org.omnifaces.util.Faces.getRequestParameter;
import static org.omnifaces.util.Faces.getViewRoot;
import static org.omnifaces.util.Faces.isDevelopment;
import static org.omnifaces.util.Faces.setContext;
import static org.omnifaces.util.FacesLocal.getRenderKit;
import static org.omnifaces.util.FacesLocal.getRequestQueryStringMap;
import static org.omnifaces.util.FacesLocal.getViewParameterMap;
import static org.omnifaces.util.FacesLocal.isAjaxRequestWithPartialRendering;
import static org.omnifaces.util.FacesLocal.normalizeViewId;
import static org.omnifaces.util.Hacks.isFacesScriptResourceAvailable;
import static org.omnifaces.util.Renderers.RENDERER_TYPE_JS;
import static org.omnifaces.util.Utils.isEmpty;
import static org.omnifaces.util.Utils.isOneInstanceOf;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UncheckedIOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.logging.Logger;
import jakarta.el.MethodExpression;
import jakarta.el.ValueExpression;
import jakarta.faces.application.ResourceHandler;
import jakarta.faces.application.ViewHandler;
import jakarta.faces.component.ActionSource2;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.NamingContainer;
import jakarta.faces.component.UICommand;
import jakarta.faces.component.UIComponent;
import jakarta.faces.component.UIComponentBase;
import jakarta.faces.component.UIForm;
import jakarta.faces.component.UIInput;
import jakarta.faces.component.UIMessage;
import jakarta.faces.component.UIMessages;
import jakarta.faces.component.UINamingContainer;
import jakarta.faces.component.UIOutput;
import jakarta.faces.component.UIParameter;
import jakarta.faces.component.UIViewRoot;
import jakarta.faces.component.behavior.AjaxBehavior;
import jakarta.faces.component.behavior.BehaviorBase;
import jakarta.faces.component.behavior.ClientBehavior;
import jakarta.faces.component.behavior.ClientBehaviorHolder;
import jakarta.faces.component.html.HtmlBody;
import jakarta.faces.component.search.SearchExpressionContext;
import jakarta.faces.component.search.SearchExpressionHint;
import jakarta.faces.component.visit.VisitCallback;
import jakarta.faces.component.visit.VisitContext;
import jakarta.faces.component.visit.VisitHint;
import jakarta.faces.component.visit.VisitResult;
import jakarta.faces.context.FacesContext;
import jakarta.faces.context.FacesContextWrapper;
import jakarta.faces.context.ResponseWriter;
import jakarta.faces.event.ActionEvent;
import jakarta.faces.event.ActionListener;
import jakarta.faces.event.AjaxBehaviorEvent;
import jakarta.faces.event.BehaviorListener;
import jakarta.faces.event.MethodExpressionActionListener;
import jakarta.faces.render.RenderKit;
import jakarta.faces.view.ViewDeclarationLanguage;
import jakarta.faces.view.facelets.FaceletContext;
import org.omnifaces.component.ParamHolder;
import org.omnifaces.component.SimpleParam;
import org.omnifaces.component.input.Form;
import org.omnifaces.config.OmniFaces;
import org.omnifaces.el.ScopedRunner;
/**
*
* Collection of utility methods for the Faces API with respect to working with {@link UIComponent}. There are several
* traversal/lookup methods, there are several {@link UIForm} and {@link UIInput} related methods which makes it easier
* to deal with forms and inputs.
*
*
Usage
*
* Some examples:
*
* // Get closest parent of given type.
* UIForm form = Components.getClosestParent(someUIInputComponent, UIForm.class);
*
*
* // Get currently submitted form.
* UIForm form = Components.getCurrentForm();
*
*
* // Get currently invoked command, useful for logging actions in a phase listener.
* UICommand command = Components.getCurrentCommand();
*
*
* // Get the label of the given UIInput component as Faces uses for validation messages.
* String label = Components.getLabel(someUIInputComponent);
*
*
* // Inside decode() and/or encode() of some custom component, validate if it has no children.
* Components.validateHasNoChildren(this);
*
*
* // Programmatically include composite component.
* Components.includeCompositeComponent(someParentComponent, libraryName, tagName, id);
*
*
* // Programmatically create value and action expressions.
* UICommand command = new HtmlCommandButton();
* command.setId("foo");
* command.setValue(Components.createValueExpression("#{bundle['button.foo']}", String.class));
* command.addClientBehavior("action", Components.createAjaxBehavior("#{bean.ajaxListener}"));
* command.addActionListener(Components.createActionListenerMethodExpression("#{bean.actionListener}"));
* command.setActionExpression(Components.createVoidMethodExpression("#{bean.action}"));
*
*
* // Programmatically capture HTML output of a given view.
* String mailHtml = Components.encodeHtml(Components.buildView("/WEB-INF/mail-template.xhtml"));
*
*
* // Collecting all queued actions and action listeners as method expression strings in a logging phase listener.
* List<String> actions = Components.getActionExpressionsAndListeners(Components.getCurrentActionSource());
*
*
* @author Bauke Scholtz
* @author Arjan Tijms
*/
public final class Components {
// Constants ------------------------------------------------------------------------------------------------------
private static final Logger logger = Logger.getLogger(Components.class.getName());
/** The name of the label attribute. */
public static final String LABEL_ATTRIBUTE = "label";
/** The name of the value attribute. */
public static final String VALUE_ATTRIBUTE = "value";
private static final String ERROR_MISSING_PARENT =
"Component '%s' must have a parent of type '%s', but it cannot be found.";
private static final String ERROR_MISSING_DIRECT_PARENT =
"Component '%s' must have a direct parent of type '%s', but it cannot be found.";
private static final String ERROR_MISSING_CHILD =
"Component '%s' must have at least one child of type '%s', but it cannot be found.";
private static final String ERROR_ILLEGAL_PARENT =
"Component '%s' may not have a parent of type '%s'.";
private static final String ERROR_ILLEGAL_CHILDREN =
"Component '%s' may only have children of type '%s'. Encountered children of types '%s'.";
private static final String ERROR_CHILDREN_DISALLOWED =
"Component '%s' may not have any children. Encountered children of types '%s'.";
private static final Set RESOLVE_LABEL_FOR = EnumSet.of(RESOLVE_SINGLE_COMPONENT, IGNORE_NO_RESULT);
// Constructors ---------------------------------------------------------------------------------------------------
private Components() {
// Hide constructor.
}
// General --------------------------------------------------------------------------------------------------------
/**
* Returns the current UI component from the EL context.
* @param The expected component type.
* @return The current UI component from the EL context.
* @throws ClassCastException When C
is of wrong type.
* @see UIComponent#getCurrentComponent(FacesContext)
*/
@SuppressWarnings("unchecked")
public static C getCurrentComponent() {
return (C) UIComponent.getCurrentComponent(getContext());
}
/**
* Returns the attribute of the given component on the given name.
* @param The expected return type.
* @param component The component to return the attribute of the given name for.
* @param name The name of the attribute of the given component to be returned.
* @return The attribute of the given component on the given name.
* @throws ClassCastException When T
is of wrong type.
* @since 1.5
*/
@SuppressWarnings("unchecked")
public static T getAttribute(UIComponent component, String name) {
return (T) component.getAttributes().get(name);
}
/**
* Returns whether the given UI component and all of its parents is rendered. This thus not only checks the
* component's own rendered
attribute, but also of all of its parents.
* @param component The component to be checked.
* @return true
if the given UI component and all of its parents is rendered.
* @since 1.8
*/
public static boolean isRendered(UIComponent component) {
for (UIComponent current = component; current.getParent() != null; current = current.getParent()) {
if (!current.isRendered()) {
return false;
}
}
return true;
}
// Traversal ------------------------------------------------------------------------------------------------------
/**
* Returns the UI component matching the given client ID search expression.
* @param The expected component type.
* @param clientId The client ID search expression.
* @return The UI component matching the given client ID search expression.
* @throws ClassCastException When C
is of wrong type.
* @see UIComponent#findComponent(String)
*/
@SuppressWarnings("unchecked")
public static C findComponent(String clientId) {
return (C) getViewRoot().findComponent(clientId);
}
/**
* Returns the UI component matching the given client ID search expression relative to the point
* in the component tree of the given component. For this search both parents and children are
* consulted, increasingly moving further away from the given component. Parents are consulted
* first, then children.
*
* @param The expected component type.
* @param component the component from which the relative search is started.
* @param clientId The client ID search expression.
* @return The UI component matching the given client ID search expression.
* @throws ClassCastException When C
is of wrong type.
* @see UIComponent#findComponent(String)
*/
@SuppressWarnings("unchecked")
public static C findComponentRelatively(UIComponent component, String clientId) {
if (isEmpty(clientId)) {
return null;
}
// Search first in the naming container parents of the given component
UIComponent result = findComponentInParents(component, clientId);
if (result == null) {
// If not in the parents, search from the root
result = findComponentInChildren(getViewRoot(), clientId);
}
return (C) result;
}
/**
* Returns the UI component matching the given client ID search expression relative to the point
* in the component tree of the given component, searching only in its parents.
*
* @param The expected component type.
* @param component the component from which the relative search is started.
* @param clientId The client ID search expression.
* @return The UI component matching the given client ID search expression.
* @throws ClassCastException When C
is of wrong type.
* @see UIComponent#findComponent(String)
*/
@SuppressWarnings("unchecked")
public static C findComponentInParents(UIComponent component, String clientId) {
if (isEmpty(clientId)) {
return null;
}
for (UIComponent parent = component; parent != null; parent = parent.getParent()) {
if (parent instanceof NamingContainer || parent instanceof UIViewRoot) {
UIComponent result = findComponentIgnoringIAE(parent, clientId);
if (result != null) {
return (C) result;
}
}
}
return null;
}
/**
* Returns the UI component matching the given client ID search expression relative to the point
* in the component tree of the given component, searching only in its children.
*
* @param The expected component type.
* @param component the component from which the relative search is started.
* @param clientId The client ID search expression.
* @return The UI component matching the given client ID search expression.
* @throws ClassCastException When C
is of wrong type.
* @see UIComponent#findComponent(String)
*/
@SuppressWarnings("unchecked")
public static C findComponentInChildren(UIComponent component, String clientId) {
if (isEmpty(clientId)) {
return null;
}
for (UIComponent child : component.getChildren()) {
UIComponent result = null;
if (child instanceof NamingContainer) {
result = findComponentIgnoringIAE(child, clientId);
}
if (result == null) {
result = findComponentInChildren(child, clientId);
}
if (result != null) {
return (C) result;
}
}
return null;
}
/**
* Returns a list of UI components matching the given type in children of the given component.
* @param The generic component type.
* @param component The component to search in its children for UI components matching the given type.
* @param type The type of the UI components to be searched in children of the given component.
* @return A list of UI components matching the given type in children of the given component.
*/
public static List findComponentsInChildren(UIComponent component, Class type) {
List components = new ArrayList<>();
findComponentsInChildren(component, type, components);
return components;
}
/**
* Helper method for {@link #findComponentsInChildren(UIComponent, Class)} utilizing tail recursion.
*/
@SuppressWarnings("unchecked")
private static void findComponentsInChildren(UIComponent component, Class type, List matches) {
for (UIComponent child : component.getChildren()) {
if (type.isInstance(child)) {
matches.add((C) child);
}
findComponentsInChildren(child, type, matches);
}
}
/**
* Returns a list of UI components matching the given type in children of the currently submitted form.
* The currently submitted form is obtained by {@link #getCurrentForm()}.
* @param The generic component type.
* @param type The type of the UI components to be searched in children of the currently submitted form.
* @return A list of UI components matching the given type in children of the currently submitted form.
* @since 3.1
*/
public static List findComponentsInCurrentForm(Class type) {
UIForm currentForm = getCurrentForm();
return currentForm != null ? findComponentsInChildren(currentForm, type) : emptyList();
}
/**
* Returns from the given component the closest parent of the given parent type, or null
if none
* is found.
* @param The generic component type.
* @param component The component to return the closest parent of the given parent type for.
* @param parentType The parent type.
* @return From the given component the closest parent of the given parent type, or null
if none
* is found.
*/
public static C getClosestParent(UIComponent component, Class parentType) {
UIComponent parent = component.getParent();
while (parent != null && !parentType.isInstance(parent)) {
parent = parent.getParent();
}
return parentType.cast(parent);
}
/**
* Finds from the given component the closest parent of the given parent type.
* @param The generic component type.
* @param component The component to find the closest parent of the given parent type for.
* @param parentType The parent type.
* @return From the given component the closest parent of the given parent type.
* @since 3.11
*/
public static Optional findClosestParent(UIComponent component, Class parentType) {
return Optional.ofNullable(getClosestParent(component, parentType));
}
// Iteration / Visiting -------------------------------------------------------------------------------------------
/**
* Invokes an operation on every component in the component tree.
*
* This is a simplified version of regular component visiting that uses the builder pattern to provide the various
* optional parameters. Includes supports for only visiting components of a certain class type and two
* simplified functional interfaces / lambdas.
*
* See {@link UIComponent#visitTree(VisitContext, VisitCallback)}
*
* @return A new instance of {@link ForEach}.
* @since 2.0
*/
public static ForEach forEachComponent() {
return new ForEach();
}
/**
* Invokes an operation on every component in the component tree.
*
* This is a simplified version of regular component visiting that uses the builder pattern to provide the various
* optional parameters. Includes supports for only visiting components of a certain class type and two
* simplified functional interfaces / lambdas.
*
* See {@link UIComponent#visitTree(VisitContext, VisitCallback)}
*
* @param facesContext the faces context used for tree visiting
* @return A new instance of {@link ForEach}, using the given faces context.
* @since 2.0
*/
public static ForEach forEachComponent(FacesContext facesContext) {
return new ForEach(facesContext);
}
/**
* Builder class used to collect a number of query parameters for a visit (for each) of components in the Faces
* component tree. The chain of collecting parameters is terminated by calling one of the invoke methods.
*
* @since 2.0
* @author Arjan Tijms
*
*/
public static class ForEach {
private FacesContext facesContext;
private UIComponent root;
private Collection ids;
private Set hints;
private Class>[] types;
public ForEach() {
facesContext = Faces.getContext();
}
public ForEach(FacesContext facesContext) {
this.facesContext = facesContext;
}
/**
* The root component where tree visiting starts
*
* @param root the root component where tree visiting starts
* @return the intermediate builder object to continue the builder chain
*/
public ForEach fromRoot(UIComponent root) {
this.root = root;
return this;
}
/**
* The IDs of the components that are visited
*
* @param ids the IDs of the components that are visited
* @return the intermediate builder object to continue the builder chain
*/
public ForEach havingIds(Collection ids) {
this.ids = ids;
return this;
}
/**
* The IDs of the components that are to be visited
*
* @param ids the IDs of the components that are to be visited
* @return the intermediate builder object to continue the builder chain
*/
public ForEach havingIds(String... ids) {
this.ids = asList(ids);
return this;
}
/**
* The VisitHints that are used for the visit.
*
* @param hints the VisitHints that are used for the visit.
* @return the intermediate builder object to continue the builder chain
*/
public ForEach withHints(Set hints) {
this.hints = hints;
return this;
}
/**
* The VisitHints that are used for the visit.
*
* @param hints the VisitHints that are used for the visit.
* @return the intermediate builder object to continue the builder chain
*/
public ForEach withHints(VisitHint... hints) {
if (hints.length > 0) {
EnumSet hintsSet = EnumSet.noneOf(hints[0].getDeclaringClass());
Collections.addAll(hintsSet, hints);
this.hints = hintsSet;
}
return this;
}
/**
* The types of the components that are to be visited
*
* @param types the types of the components that are to be visited
* @return the intermediate builder object to continue the builder chain
*/
@SafeVarargs
public final ForEach ofTypes(Class>... types) {
this.types = types;
return this;
}
/**
* Invokes the given operation on the components as specified by the
* query parameters set via this builder.
*
* @param The expected component type.
* @param operation the operation to invoke on each component
* @throws ClassCastException When C
is of wrong type.
*/
@SuppressWarnings("unchecked")
public void invoke(Consumer operation) {
invoke((context, target) -> {
operation.accept((C) target);
return ACCEPT;
});
}
/**
* Invokes the given operation on the components as specified by the
* query parameters set via this builder.
*
* @param operation the operation to invoke on each component
*/
public void invoke(VisitCallback operation) {
VisitContext visitContext = createVisitContext(getFacesContext(), getIds(), getHints());
VisitCallback visitCallback = (types == null) ? operation : new TypesVisitCallback(types, operation);
if (getFacesContext().getViewRoot().equals(getRoot())) {
getRoot().visitTree(visitContext, visitCallback);
}
else {
forEachComponent().havingIds(getRoot().getClientId()).invoke(viewRoot -> viewRoot.visitTree(visitContext, visitCallback));
}
}
protected FacesContext getFacesContext() {
return facesContext;
}
protected UIComponent getRoot() {
return root != null ? root : getFacesContext().getViewRoot();
}
protected Collection getIds() {
return ids;
}
protected Set getHints() {
return hints;
}
private static class TypesVisitCallback implements VisitCallback {
private Class>[] types;
private VisitCallback next;
public TypesVisitCallback(Class>[] types, VisitCallback next) {
this.types = types;
this.next = next;
}
@Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (isOneInstanceOf(target.getClass(), types)) {
return next.visit(context, target);
}
return ACCEPT;
}
}
}
// Manipulation ---------------------------------------------------------------------------------------------------
/**
* Include the Facelet file at the given (relative) path as child of the given UI component parent. This has the
* same effect as using <ui:include>
. The path is relative to the current view ID and absolute
* to the webcontent root.
* @param parent The parent component to include the Facelet file in.
* @param path The (relative) path to the Facelet file.
* @throws IOException Whenever given path cannot be read.
* @see FaceletContext#includeFacelet(UIComponent, String)
* @since 1.5
*/
public static void includeFacelet(UIComponent parent, String path) throws IOException {
getFaceletContext().includeFacelet(parent, path);
}
/**
* Create and include the composite component of the given library and resource name as child of the given UI
* component parent and return the created composite component.
* This has the same effect as using xmlns:my="http://xmlns.jcp.org/jsf/composite/libraryName
and
* <my:tagName>
. The given component ID must be unique relative to the current naming
* container parent and is mandatory for functioning of input components inside the composite, if any.
* @param parent The parent component to include the composite component in.
* @param libraryName The library name of the composite component (path after "http://xmlns.jcp.org/jsf/composite/").
* @param tagName The tag name of the composite component.
* @param id The component ID of the composite component.
* @return The created composite component, which can if necessary be used to set more custom attributes on it.
* @since 1.5
*/
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id) {
return includeCompositeComponent(parent, libraryName, tagName, id, null);
}
/**
* Create and include the composite component of the given library and resource name as child of the given UI
* component parent, set the given attributes on it and return the created composite component.
* This has the same effect as using xmlns:my="http://xmlns.jcp.org/jsf/composite/libraryName
and
* <my:tagName>
. The given component ID must be unique relative to the current naming
* container parent and is mandatory for functioning of input components inside the composite, if any.
*
* The attribute values must represent literal values or literal EL expressions, exactly like as you would declare
* in the view file. E.g.
*
* attributes.put("foo", "#{bean.foo}");
* attributes.put("bar", "true");
* attributes.put("baz", "#{bean.baz(" + someId + ")}");
*
* @param parent The parent component to include the composite component in.
* @param libraryName The library name of the composite component (path after "http://xmlns.jcp.org/jsf/composite/").
* @param tagName The tag name of the composite component.
* @param id The component ID of the composite component.
* @param attributes The attributes to be set on the composite component.
* @return The created composite component, which can if necessary be used to set more custom attributes on it.
* @since 2.2
*/
public static UIComponent includeCompositeComponent(UIComponent parent, String libraryName, String tagName, String id, Map attributes) {
String taglibURI = "http://xmlns.jcp.org/jsf/composite/" + libraryName;
Map attrs = (attributes == null) ? null : new HashMap<>(attributes);
FacesContext context = FacesContext.getCurrentInstance();
UIComponent composite = context.getApplication().getViewHandler()
.getViewDeclarationLanguage(context, context.getViewRoot().getViewId())
.createComponent(context, taglibURI, tagName, attrs);
composite.setId(id);
parent.getChildren().add(composite);
return composite;
}
/**
* Add given JavaScript code to the current view which is to be executed as an inline script when the rendering is
* completed. When the current request is {@link Faces#isAjaxRequestWithPartialRendering()}, then it will delegate
* to {@link Ajax#oncomplete(String...)}, else it will add given JavaScript code as inline script to end of body.
* @param script JavaScript code which is to be executed as an inline script.
* @since 3.6
*/
public static void addScript(String script) {
FacesContext context = FacesContext.getCurrentInstance();
if (isAjaxRequestWithPartialRendering(context)) {
oncomplete(script);
}
else if (context.getCurrentPhaseId() != RENDER_RESPONSE) {
subscribeToRequestBeforePhase(RENDER_RESPONSE, () -> addScriptToBody(script)); // Just to avoid it misses when view rebuilds in the meanwhile.
}
else {
addScriptToBody(script);
}
}
/**
* Add given JavaScript resource to the current view. This will first check if the resource isn't already rendered
* as per {@link ResourceHandler#isResourceRendered(FacesContext, String, String)}. If not, then continue as below:
*
* - When the current request is a {@link Faces#isAjaxRequestWithPartialRendering()}, then it will delegate to
* {@link Ajax#load(String, String)}.
* - Else when the
<h:head>
has not yet been rendered, then add given JavaScript resource to
* head.
* - Else add given JavaScript resource to end of the
<h:body>
.
*
* @param libraryName Library name of the JavaScript resource.
* @param resourceName Resource name of the JavaScript resource.
* @since 3.6
*/
public static void addScriptResource(String libraryName, String resourceName) {
FacesContext context = FacesContext.getCurrentInstance();
if (!context.getApplication().getResourceHandler().isResourceRendered(context, resourceName, libraryName)) {
if (isAjaxRequestWithPartialRendering(context)) {
load(libraryName, resourceName);
}
else if (context.getCurrentPhaseId() != RENDER_RESPONSE) {
addScriptResourceToHead(libraryName, resourceName);
subscribeToRequestBeforePhase(RENDER_RESPONSE, () -> addScriptResourceToBody(libraryName, resourceName)); // Fallback in case view rebuilds in the meanwhile. It will re-check if already added.
}
else if (TRUE.equals(context.getAttributes().get(IS_BUILDING_INITIAL_STATE))) {
addScriptResourceToHead(libraryName, resourceName);
}
else {
addScriptResourceToBody(libraryName, resourceName);
}
}
}
/**
* Add the Faces JavaScript resource to current view. If Faces 4.0+ is present, then it will add the
* jakarta.faces:faces.js
resource, else it will add the jakarta.faces:jsf.js
resource.
* @since 4.0
*/
public static void addFacesScriptResource() {
addScriptResource(JSF_SCRIPT_LIBRARY_NAME, isFacesScriptResourceAvailable() ? FACES_SCRIPT_RESOURCE_NAME : JSF_SCRIPT_RESOURCE_NAME);
}
private static UIOutput createScriptResource() {
UIOutput outputScript = new UIOutput();
outputScript.setRendererType(RENDERER_TYPE_JS);
return outputScript;
}
private static UIComponent addScriptResourceToTarget(String libraryName, String resourceName, String target) {
FacesContext context = FacesContext.getCurrentInstance();
String id = (libraryName != null ? (libraryName.replaceAll("\\W+", "_") + "_") : "") + resourceName.replaceAll("\\W+", "_");
for (UIComponent existingResource : context.getViewRoot().getComponentResources(context)) {
if (id.equals(existingResource.getId())) {
return existingResource;
}
}
UIOutput outputScript = createScriptResource();
outputScript.setId(id);
if (libraryName != null) {
outputScript.getAttributes().put("library", libraryName);
}
outputScript.getAttributes().put("name", resourceName);
return addComponentResource(outputScript, target);
}
private static void addScriptResourceToHead(String libraryName, String resourceName) {
addScriptResourceToTarget(libraryName, resourceName, "head");
}
private static void addScriptResourceToBody(String libraryName, String resourceName) {
addScriptResourceToTarget(libraryName, resourceName, "body");
}
private static UIComponent addComponentResource(UIComponent resource, String target) {
FacesContext context = FacesContext.getCurrentInstance();
if (resource.getId() == null) {
Hacks.setComponentResourceUniqueId(context, resource);
}
context.getViewRoot().addComponentResource(context, resource, target);
return resource;
}
private static void addScriptToBody(String script) {
UIOutput outputScript = createScriptResource();
UIOutput content = new UIOutput();
content.setValue(script);
outputScript.getChildren().add(content);
addComponentResource(outputScript, "body");
}
// Building / rendering -------------------------------------------------------------------------------------------
/**
* Creates and builds a local view for the given view ID independently from the current view.
* @param viewId The ID of the view which needs to be created and built.
* @return A fully populated component tree of the given view ID.
* @throws IOException Whenever something fails at I/O level. This can happen when the given view ID is unavailable or malformed.
* @since 2.2
* @see ViewHandler#createView(FacesContext, String)
* @see ViewDeclarationLanguage#buildView(FacesContext, UIViewRoot)
*/
public static UIViewRoot buildView(String viewId) throws IOException {
FacesContext context = FacesContext.getCurrentInstance();
String normalizedViewId = normalizeViewId(context, viewId);
ViewHandler viewHandler = context.getApplication().getViewHandler();
UIViewRoot view = viewHandler.createView(context, normalizedViewId);
FacesContext temporaryContext = new TemporaryViewFacesContext(context, view);
try {
setContext(temporaryContext);
viewHandler.getViewDeclarationLanguage(temporaryContext, normalizedViewId).buildView(temporaryContext, view);
}
finally {
setContext(context);
}
return view;
}
/**
* Encodes the given component locally as HTML, with UTF-8 character encoding, independently from the current view.
* The current implementation, however, uses the current faces context. The same managed beans as in the current
* faces context will be available as well, including request scoped ones. But, depending on the nature of the
* provided component, the state of the faces context may be affected because the attributes of the context,
* request, view, session and application scope could be (in)directly manipulated during the encode. This may or may
* not have the desired effect. If the given view does not have any component resources, Faces forms, dynamically
* added components, component event listeners, then it should mostly be safe.
* In other words, use this at most for "simple templates" only, e.g. a HTML based mail template, which usually
* already doesn't have a HTML head nor body.
* @param component The component to capture HTML output for.
* @return The encoded HTML output of the given component.
* @throws UncheckedIOException Whenever something fails at I/O level. This would be quite unexpected as it happens locally.
* @since 2.2
* @see UIComponent#encodeAll(FacesContext)
*/
public static String encodeHtml(UIComponent component) {
FacesContext context = FacesContext.getCurrentInstance();
ResponseWriter originalWriter = context.getResponseWriter();
StringWriter output = new StringWriter();
context.setResponseWriter(getRenderKit(context).createResponseWriter(output, "text/html", "UTF-8"));
try {
component.encodeAll(context);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
finally {
if (originalWriter != null) {
context.setResponseWriter(originalWriter);
}
}
return output.toString();
}
// Forms ----------------------------------------------------------------------------------------------------------
/**
* Returns the currently submitted UI form component, or null
if there is none, which may happen when
* the current request is not a postback request at all, or when the view has been changed by for example a
* successful navigation. If the latter is the case, you'd better invoke this method before navigation.
* @return The currently submitted UI form component.
* @see UIForm#isSubmitted()
*/
public static UIForm getCurrentForm() {
FacesContext context = FacesContext.getCurrentInstance();
if (!context.isPostback()) {
return null;
}
UIViewRoot viewRoot = context.getViewRoot();
if (viewRoot == null) {
return null;
}
// The initial implementation has visited the tree for UIForm components which returns true on isSubmitted().
// But with testing it turns out to return false on ajax requests where the form is not included in execute!
// The current implementation just walks through the request parameter map instead.
for (String name : context.getExternalContext().getRequestParameterMap().keySet()) {
if (name.startsWith("jakarta.faces.")) {
continue; // Quick skip.
}
UIComponent component = findComponentIgnoringIAE(viewRoot, name);
if (component instanceof UIForm) {
return (UIForm) component;
}
else if (component != null) {
UIForm form = getClosestParent(component, UIForm.class);
if (form != null) {
return form;
}
}
}
return null;
}
/**
* Returns the currently invoked UI command component, or null
if there is none, which may happen when
* the current request is not a postback request at all, or when the view has been changed by for example a
* successful navigation. If the latter is the case, you'd better invoke this method before navigation.
* @return The currently invoked UI command component.
* @since 1.6
*/
public static UICommand getCurrentCommand() {
UIComponent source = getCurrentActionSource();
return source instanceof UICommand ? (UICommand) source : null;
}
/**
* Returns the source of the currently invoked action, or null
if there is none, which may happen when
* the current request is not a postback request at all, or when the view has been changed by for example a
* successful navigation. If the latter is the case, you'd better invoke this method before navigation.
* @param The expected component type.
* @return The source of the currently invoked action.
* @since 2.4
*/
@SuppressWarnings("unchecked")
public static C getCurrentActionSource() {
FacesContext context = FacesContext.getCurrentInstance();
if (!context.isPostback()) {
return null;
}
return (C) getCurrentActionSource(context, context.getViewRoot());
}
/**
* Helper method for {@link #getCurrentActionSource()}.
*/
static UIComponent getCurrentActionSource(FacesContext context, UIComponent parent) {
if (parent == null) {
return null;
}
Map params = context.getExternalContext().getRequestParameterMap();
if (context.getPartialViewContext().isAjaxRequest()) {
String sourceClientId = params.get(BEHAVIOR_SOURCE_PARAM_NAME);
if (sourceClientId != null) {
UIComponent actionSource = findComponentIgnoringIAE(parent, sourceClientId);
if (actionSource != null) {
return actionSource;
}
}
}
for (String name : params.keySet()) {
if (name.startsWith("jakarta.faces.")) {
continue; // Quick skip.
}
UIComponent actionSource = findComponentIgnoringIAE(parent, name);
if (actionSource instanceof UICommand) {
return actionSource;
}
}
if (parent instanceof UIViewRoot) { // If still not found and parent is UIViewRoot, then it can happen when prependId="false" is set on form. Hopefully it will be deprecated one day.
return getCurrentActionSource(context, getCurrentForm());
}
return null;
}
/**
* Returns whether the given UI input component is editable. That is when it is rendered, not disabled and not
* readonly.
* @param input The UI input component to be checked.
* @return true
if the given UI input component is editable.
*/
public static boolean isEditable(UIInput input) {
return input.isRendered()
&& !Boolean.TRUE.equals(input.getAttributes().get("disabled"))
&& !Boolean.TRUE.equals(input.getAttributes().get("readonly"));
}
/**
* Returns the value of the label
attribute associated with the given UI component if any, else
* the client ID. It never returns null.
* @param component The UI component for which the label is to be retrieved.
* @return The value of the label
attribute associated with the given UI component if any, else
* the client ID.
*/
public static String getLabel(UIComponent component) {
String label = getOptionalLabel(component);
return (label != null) ? label : component.getClientId();
}
/**
* Returns the value of the label
attribute associated with the given UI component if any, else
* null.
* @param component The UI component for which the label is to be retrieved.
* @return The value of the label
attribute associated with the given UI component if any, else
* null.
*/
public static String getOptionalLabel(UIComponent component) {
Object[] result = new Object[1];
new ScopedRunner(getContext()).with("cc", getCompositeComponentParent(component)).invoke(() -> {
Object label = component.getAttributes().get(LABEL_ATTRIBUTE);
if (isEmpty(label)) {
ValueExpression labelExpression = component.getValueExpression(LABEL_ATTRIBUTE);
if (labelExpression != null) {
label = labelExpression.getValue(getELContext());
}
}
result[0] = label;
});
return (result[0] != null) ? result[0].toString() : null;
}
/**
* Sets the label
attribute of the given UI component with the given value.
* @param component The UI component for which the label is to be set.
* @param label The label to be set on the given UI component.
*/
public static void setLabel(UIComponent component, Object label) {
if (label instanceof ValueExpression) {
component.setValueExpression(LABEL_ATTRIBUTE, (ValueExpression) label);
}
else if (label != null) {
component.getAttributes().put(LABEL_ATTRIBUTE, label);
}
else {
component.getAttributes().remove(LABEL_ATTRIBUTE);
}
}
/**
* Returns the value of the given editable value holder component without the need to know if the given component
* has already been converted/validated or not. Note that it thus returns the unconverted submitted string value
* when the conversion/validation hasn't been taken place for the given component and it returns the converted
* object value -if applicable- when conversion/validation has been taken place for the given component.
* @param The expected return type.
* @param component The editable value holder component to obtain the value for.
* @return The value of the given editable value holder component.
* @throws ClassCastException When T
is of wrong type.
*/
@SuppressWarnings("unchecked")
public static T getValue(EditableValueHolder component) {
Object submittedValue = component.getSubmittedValue();
return (T) ((submittedValue != null) ? submittedValue : component.getLocalValue());
}
/**
* Returns the value of the given input component whereby any unconverted submitted string value will immediately
* be converted/validated as this method is called. This method thus always returns the converted/validated value.
* @param The expected return type.
* @param input The input component to obtain the converted/validated value for.
* @return The converted/validated value of the given input component.
* @throws ClassCastException When T
is of wrong type.
* @since 1.2
*/
@SuppressWarnings("unchecked")
public static T getImmediateValue(UIInput input) {
if (input.isValid() && input.getSubmittedValue() != null) {
input.validate(getContext());
}
return input.isLocalValueSet() ? (T) input.getValue() : null;
}
/**
* Returns whether the given editable value holder component has a submitted value.
* @param component The editable value holder component to be checked.
* @return true
if the given editable value holder component has a submitted value, otherwise
* false
.
*/
public static boolean hasSubmittedValue(EditableValueHolder component) {
return !Utils.isEmpty(component.getSubmittedValue());
}
/**
* Returns the expected type of the "value" attribute of the given component. This is useful in among others a
* "generic entity converter".
* @param The expected type of the expected type of the "value" attribute of the given component.
* @param component The component to obtain the expected type of the "value" attribute for.
* @return The expected type of the "value" attribute of the given component, or null
when there is no such value.
* @throws ClassCastException When T
is of wrong type.
* @since 3.8
*/
@SuppressWarnings("unchecked")
public static Class getExpectedValueType(UIComponent component) {
ValueExpression valueExpression = component.getValueExpression(VALUE_ATTRIBUTE);
if (valueExpression != null) {
return getExpectedType(valueExpression);
}
else {
Object value = component.getAttributes().get(VALUE_ATTRIBUTE);
if (value != null) {
return (Class) value.getClass();
}
return null;
}
}
/**
* Returns the expected type of the given value expression. This first inspects if the
* {@link ValueExpression#getExpectedType()} returns a specific type, i.e. not java.lang.Object
, and
* then returns it, else it inspects the actual type of the property behind the expression string.
* @param The expected type of the expected type of the given value expression.
* @param valueExpression The value expression to obtain the expected type for.
* @return The expected type of the given value expression.
* @throws ClassCastException When T
is of wrong type.
* @since 3.8
*/
@SuppressWarnings("unchecked")
public static Class getExpectedType(ValueExpression valueExpression) {
Class> expectedType = valueExpression.getExpectedType();
if (expectedType == Object.class) {
expectedType = valueExpression.getType(getELContext());
}
return (Class) expectedType;
}
/**
* Returns whether the given component has invoked the form submit. In non-ajax requests, that can only be an
* {@link UICommand} component. In ajax requests, that can also be among others an {@link UIInput} component.
* @param component The component to be checked.
* @return true
if the given component has invoked the form submit.
* @since 1.3
*/
public static boolean hasInvokedSubmit(UIComponent component) {
UIComponent source = getCurrentActionSource();
return source != null && source.equals(component);
}
/**
* Returns an unmodifiable list with all child {@link UIParameter} components (<f|o:param>
) of
* the given parent component as a list of {@link ParamHolder} instances. Those with disabled=true
and
* an empty name are skipped.
* @param The type of the param value.
* @param component The parent component to retrieve all child {@link UIParameter} components from.
* @return An unmodifiable list with all child {@link UIParameter} components having a non-empty name and not
* disabled.
* @since 2.1
*/
public static List> getParams(UIComponent component) {
if (component.getChildCount() == 0) {
return Collections.emptyList();
}
List> params = new ArrayList<>(component.getChildCount());
for (UIComponent child : component.getChildren()) {
if (child instanceof UIParameter) {
UIParameter param = (UIParameter) child;
if (!isEmpty(param.getName()) && !param.isDisable()) {
params.add(new SimpleParam<>(param));
}
}
}
return Collections.unmodifiableList(params);
}
/**
* Returns an unmodifiable map with all request query string or view parameters, appended with all child
* {@link UIParameter} components (<f|o:param>
) of the given parent component. Those with
* disabled=true
or an empty name or an empty value are skipped. The <f|o:param>
* will override any included view or request parameters on the same name.
* @param component The parent component to retrieve all child {@link UIParameter} components from.
* @param includeRequestParams Whether or not to include request query string parameters.
* When set to true
, then this overrides the includeViewParams
.
* @param includeViewParams Whether or not to include view parameters.
* @return An unmodifiable list with all request query string or view parameters, appended with all child
* {@link UIParameter} components having a non-empty name and not disabled.
* @since 2.4
*/
public static Map> getParams(UIComponent component, boolean includeRequestParams, boolean includeViewParams) {
FacesContext context = FacesContext.getCurrentInstance();
Map> params;
if (includeRequestParams) {
params = getRequestQueryStringMap(context);
}
else if (includeViewParams) {
params = getViewParameterMap(context);
}
else {
params = new LinkedHashMap<>(0);
}
for (ParamHolder