Skip to content

Commit

Permalink
Fix #5415: move retargeted client ID calculation from AjaxHandler to
Browse files Browse the repository at this point in the history
AjaxBehaviorHandler so it doesn't fail when cc.clientId happens to be
referenced in f:ajax execute
  • Loading branch information
BalusC committed Mar 16, 2024
1 parent 7cd3d2a commit 51a688b
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.sun.faces.facelets.tag.composite;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import jakarta.el.ValueExpression;
Expand All @@ -32,7 +33,7 @@

/**
* Basically represents {@code <f:ajax>} which is retargeted by {@code <cc:clientBehavior>} in {@code AjaxHandler} and checked in {@code AjaxBehaviorRenderer}.
*
*
* We should probably introduce {@code AjaxBehaviorWrapper} in Faces.next to reduce boilerplate like this.
*
* - https://github.com/jakartaee/faces/issues/1567
Expand All @@ -41,9 +42,15 @@
public class RetargetedAjaxBehavior extends AjaxBehavior {

private AjaxBehavior retargeted;
private List<String> targetClientIds;

public RetargetedAjaxBehavior(AjaxBehavior retargeted) {
public RetargetedAjaxBehavior(AjaxBehavior retargeted, List<String> targetClientIds) {
this.retargeted = retargeted;
this.targetClientIds = targetClientIds;
}

public List<String> getTargetClientIds() {
return targetClientIds;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,17 @@

package com.sun.faces.facelets.tag.faces.core;

import static jakarta.faces.component.UINamingContainer.getSeparatorChar;
import static java.util.Arrays.stream;
import static java.util.Arrays.asList;

import java.beans.BeanDescriptor;
import java.beans.BeanInfo;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import com.sun.faces.component.behavior.AjaxBehaviors;
import com.sun.faces.facelets.tag.TagHandlerImpl;
import com.sun.faces.facelets.tag.composite.BehaviorHolderWrapper;
import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior;
import com.sun.faces.facelets.tag.faces.CompositeComponentTagHandler;
import com.sun.faces.renderkit.RenderKitUtils;

import jakarta.el.ELContext;
import jakarta.el.MethodExpression;
import jakarta.el.MethodNotFoundException;
Expand All @@ -60,14 +51,21 @@
import jakarta.faces.view.facelets.TagException;
import jakarta.faces.view.facelets.TagHandler;

import com.sun.faces.component.behavior.AjaxBehaviors;
import com.sun.faces.facelets.tag.TagHandlerImpl;
import com.sun.faces.facelets.tag.composite.BehaviorHolderWrapper;
import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior;
import com.sun.faces.facelets.tag.faces.CompositeComponentTagHandler;
import com.sun.faces.renderkit.RenderKitUtils;

/**
* <p class="changed_added_2_0">
* <span class="changed_modified_2_2">Enable</span> one or more components in the view to perform Ajax operations. This
* tag handler must create an instance of {@link jakarta.faces.component.behavior.AjaxBehavior} using the tag attribute
* values. <div class="changed_modified_2_2">The <code>events</code> attribute for this tag that can be a
* <code>ValueExpression</code> must be evaluated at tag execution time since the event name is used in the process of
* <code>Behavior</code> creation.</div> If this tag is nested within a single {@link ClientBehaviorHolder} component:
*
*
* <ul>
* <li>If the <code>events</code> attribute value is not specified, obtain the default event name by calling
* {@link ClientBehaviorHolder#getDefaultEventName}. If that returns <code>null</code> throw an
Expand Down Expand Up @@ -302,20 +300,12 @@ private AjaxBehavior createAjaxBehavior(FaceletContext ctx, UIComponent parent,

if (parent instanceof BehaviorHolderWrapper) {
ValueExpression targets = ((BehaviorHolderWrapper) parent).getTargets();

if (targets != null) {
String targetClientIds = (String) targets.getValue(ctx);

if (targetClientIds != null) {
Collection<String> executeClientIds = new ArrayList<>(behavior.getExecute());

if (executeClientIds.isEmpty() || executeClientIds.contains("@this")) {
String separatorChar = String.valueOf(getSeparatorChar(ctx.getFacesContext()));
executeClientIds.remove("@this");
stream(targetClientIds.trim().split(" +")).map(id -> "@this" + separatorChar + id).forEach(executeClientIds::add);
behavior.setExecute(executeClientIds);
behavior = new RetargetedAjaxBehavior(behavior);
}
behavior = new RetargetedAjaxBehavior(behavior, asList(targetClientIds.trim().split(" +")));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,16 @@

import static jakarta.faces.component.UINamingContainer.getSeparatorChar;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.util.FacesLogger;

import jakarta.faces.component.ActionSource;
import jakarta.faces.component.EditableValueHolder;
import jakarta.faces.component.UIComponent;
Expand All @@ -47,6 +45,10 @@
import jakarta.faces.event.PhaseId;
import jakarta.faces.render.ClientBehaviorRenderer;

import com.sun.faces.facelets.tag.composite.RetargetedAjaxBehavior;
import com.sun.faces.renderkit.RenderKitUtils;
import com.sun.faces.util.FacesLogger;

/*
*<b>AjaxBehaviorRenderer</b> renders Ajax behavior for a component.
* It also
Expand Down Expand Up @@ -280,9 +282,24 @@ private static String buildAjaxCommand(ClientBehaviorContext behaviorContext, Aj
SearchExpressionHint.RESOLVE_SINGLE_COMPONENT);

// Appends an ids argument to the ajax command
private static void appendIds(FacesContext facesContext, UIComponent component, AjaxBehavior ajaxBehavior, StringBuilder builder, Collection<String> ids) {
private static void appendIds(FacesContext facesContext, UIComponent component, AjaxBehavior ajaxBehavior, StringBuilder builder, Collection<String> idsOrNull) {

if (idsOrNull == null) {
builder.append('0');
return;
}

Collection<String> ids = new ArrayList<>(idsOrNull);
UIComponent composite = UIComponent.getCompositeComponentParent(component);
String separatorChar = String.valueOf(getSeparatorChar(facesContext));

if (null == ids || ids.isEmpty()) {
if (composite != null && (ajaxBehavior instanceof RetargetedAjaxBehavior) && (ids.isEmpty() || ids.contains("@this"))) {
List<String> targetClientIds = ((RetargetedAjaxBehavior) ajaxBehavior).getTargetClientIds();
ids.remove("@this");
targetClientIds.stream().map(id -> "@this" + separatorChar + id).forEach(ids::add);
}

if (ids.isEmpty()) {
builder.append('0');
return;
}
Expand All @@ -294,9 +311,6 @@ private static void appendIds(FacesContext facesContext, UIComponent component,

boolean first = true;

UIComponent composite = UIComponent.getCompositeComponentParent(component);
String separatorChar = String.valueOf(getSeparatorChar(facesContext));

for (String id : ids) {
String expression = id.trim();

Expand Down

0 comments on commit 51a688b

Please sign in to comment.