Permalink
Browse files

Add Components#getCurrentActionSource/ActionExpressionsAndListeners()

(useful for logging faces actions)
  • Loading branch information...
1 parent f773bb6 commit 0b190e4b38a9b0eba94f249e4fa9c9a08cc2672c @BalusC BalusC committed May 3, 2016
Showing with 110 additions and 46 deletions.
  1. +110 −46 src/main/java/org/omnifaces/util/Components.java
@@ -13,12 +13,14 @@
package org.omnifaces.util;
import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
import static java.util.regex.Pattern.quote;
import static javax.faces.component.visit.VisitContext.createVisitContext;
import static javax.faces.component.visit.VisitResult.ACCEPT;
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.setContext;
import static org.omnifaces.util.FacesLocal.getRenderKit;
@@ -29,6 +31,7 @@
import java.io.IOException;
import java.io.StringWriter;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -41,6 +44,7 @@
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.application.ViewHandler;
+import javax.faces.component.ActionSource2;
import javax.faces.component.EditableValueHolder;
import javax.faces.component.NamingContainer;
import javax.faces.component.UICommand;
@@ -53,7 +57,9 @@
import javax.faces.component.UIParameter;
import javax.faces.component.UIViewRoot;
import javax.faces.component.behavior.AjaxBehavior;
+import javax.faces.component.behavior.BehaviorBase;
import javax.faces.component.behavior.ClientBehavior;
+import javax.faces.component.behavior.ClientBehaviorHolder;
import javax.faces.component.visit.VisitCallback;
import javax.faces.component.visit.VisitContext;
import javax.faces.component.visit.VisitHint;
@@ -63,8 +69,10 @@
import javax.faces.context.ResponseWriter;
import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
+import javax.faces.event.ActionListener;
import javax.faces.event.AjaxBehaviorEvent;
import javax.faces.event.AjaxBehaviorListener;
+import javax.faces.event.BehaviorListener;
import javax.faces.event.MethodExpressionActionListener;
import javax.faces.render.RenderKit;
import javax.faces.view.ViewDeclarationLanguage;
@@ -828,6 +836,18 @@ else if (component != null) {
* @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 <code>null</code> 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 source of the currently invoked action.
+ * @since 2.4
+ */
+ public static UIComponent getCurrentActionSource() {
FacesContext context = FacesContext.getCurrentInstance();
if (!context.isPostback()) {
@@ -836,32 +856,31 @@ public static UICommand getCurrentCommand() {
UIViewRoot viewRoot = context.getViewRoot();
Map<String, String> params = context.getExternalContext().getRequestParameterMap();
+ UIComponent actionSource = null;
if (context.getPartialViewContext().isAjaxRequest()) {
- String source = params.get("javax.faces.source");
-
- if (source != null) {
- UIComponent component = findComponentIgnoringIAE(viewRoot, stripIterationIndexFromClientId(source));
+ String sourceClientId = params.get("javax.faces.source");
- if (component instanceof UICommand) {
- return (UICommand) component;
- }
+ if (sourceClientId != null) {
+ actionSource = findComponentIgnoringIAE(viewRoot, stripIterationIndexFromClientId(sourceClientId));
}
}
- for (String name : params.keySet()) {
- if (name.startsWith("javax.faces.")) {
- continue; // Quick skip.
- }
+ if (actionSource == null) {
+ for (String name : params.keySet()) {
+ if (name.startsWith("javax.faces.")) {
+ continue; // Quick skip.
+ }
- UIComponent component = findComponentIgnoringIAE(viewRoot, stripIterationIndexFromClientId(name));
+ actionSource = findComponentIgnoringIAE(viewRoot, stripIterationIndexFromClientId(name));
- if (component instanceof UICommand) {
- return (UICommand) component;
+ if (actionSource instanceof UICommand) {
+ break;
+ }
}
}
- return null;
+ return actionSource;
}
/**
@@ -961,36 +980,8 @@ public static boolean hasSubmittedValue(EditableValueHolder component) {
* @since 1.3
*/
public static boolean hasInvokedSubmit(UIComponent component) {
- FacesContext context = FacesContext.getCurrentInstance();
-
- if (!context.isPostback()) {
- return false;
- }
-
- String clientId = stripIterationIndexFromClientId(component.getClientId(context));
- Map<String, String> params = context.getExternalContext().getRequestParameterMap();
-
- if (context.getPartialViewContext().isAjaxRequest()) {
- String source = params.get("javax.faces.source");
-
- if (source != null) {
- return clientId.equals(stripIterationIndexFromClientId(source));
- }
- }
-
- if (component instanceof UICommand) {
- for (String name : params.keySet()) {
- if (name.startsWith("javax.faces.")) {
- continue; // Quick skip.
- }
-
- if (clientId.equals(stripIterationIndexFromClientId(name))) {
- return true;
- }
- }
- }
-
- return false;
+ UIComponent source = getCurrentActionSource();
+ return source != null && source.equals(component);
}
/**
@@ -1146,6 +1137,48 @@ public void processAjaxBehavior(AjaxBehaviorEvent event) throws AbortProcessingE
return behavior;
}
+ /**
+ * Returns a list of all action expressions and listeners associated with given component. This covers expressions
+ * in <code>action</code> attribute of command components and <code>listener</code> attribute of ajax components.
+ * Any method expressions are in format <code>#{bean.method}</code> and any action listeners are added as fully
+ * qualified class names. This list is primarily useful for logging postback actions in a phase listener. You can
+ * use {@link #getCurrentActionSource()} to obtain the current action source.
+ * @param component The component to retrieve all action expressions and listeners from.
+ * @return A list of all action expressions and listeners associated with given component.
+ * @since 2.4
+ */
+ @SuppressWarnings("unchecked")
+ public static List<String> getActionExpressionsAndListeners(UIComponent component) {
+ List<String> actions = new ArrayList<>();
+
+ if (component instanceof ActionSource2) {
+ ActionSource2 source = (ActionSource2) component;
+ addExpressionStringIfNotNull(source.getActionExpression(), actions);
+
+ for (ActionListener actionListener : source.getActionListeners()) {
+ actions.add(actionListener.getClass().getName());
+ }
+ }
+
+ if (component instanceof ClientBehaviorHolder) {
+ String behaviorEvent = getRequestParameter("javax.faces.behavior.event");
+
+ if (behaviorEvent != null) {
+ for (ClientBehavior behavior : ((ClientBehaviorHolder) component).getClientBehaviors().get(behaviorEvent)) {
+ List<BehaviorListener> listeners = getField(BehaviorBase.class, List.class, behavior);
+
+ if (listeners != null) {
+ for (BehaviorListener listener : listeners) {
+ addExpressionStringIfNotNull(getField(listener.getClass(), MethodExpression.class, listener), actions);
+ }
+ }
+ }
+ }
+ }
+
+ return unmodifiableList(actions);
+ }
+
// Validation -----------------------------------------------------------------------------------------------------
/**
@@ -1226,6 +1259,37 @@ private static UIComponent findComponentIgnoringIAE(UIViewRoot viewRoot, String
}
}
+ /**
+ * If given method expression is not null, extract expression string from it and add to given list.
+ */
+ private static void addExpressionStringIfNotNull(MethodExpression expression, List<String> list) {
+ if (expression != null) {
+ list.add(expression.getExpressionString());
+ }
+ }
+
+ /**
+ * Get first matching field of given field type from the given class type and get the value from the given instance.
+ * (this is a rather specific helper for getActionExpressionsAndListeners() and may not work in other cases).
+ */
+ @SuppressWarnings("unchecked")
+ private static <C, F> F getField(Class<? extends C> classType, Class<F> fieldType, C instance) {
+ for (Field field : classType.getDeclaredFields()) {
+ if (fieldType.isAssignableFrom(field.getType())) {
+ field.setAccessible(true);
+
+ try {
+ return (F) field.get(instance);
+ }
+ catch (IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ return null;
+ }
+
// Inner classes --------------------------------------------------------------------------------------------------
/**
@@ -1264,4 +1328,4 @@ public FacesContext getWrapped() {
}
-}
+}

0 comments on commit 0b190e4

Please sign in to comment.