Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,18 @@ private static void evaluateResourceClass(ClassResourceInfo cri, boolean enableS
}
for (Method m : serviceClass.getMethods()) {
if (m.isBridge() || m.isSynthetic()) {
//if a bridge/synthetic method isn't already mapped to something, go ahead and do it
Method annotatedMethod = AnnotationUtils.getAnnotatedMethod(serviceClass, m);
if (annotatedMethod == m && m.isBridge()) {
//no annotated method was found for the bridge other than itself, i.e. the JAX-RS
//annotations were copied onto the bridge by the compiler but with the erased parameter
//types, so if the method it bridges to is already mapped then reuse its operation
Method bridged = findBridgedMethod(serviceClass, m);
if (bridged != null && md.getOperationResourceInfo(bridged) != null) {
bindResourceMethod(md, m, bridged);
continue;
}
}
//if a bridge/synthetic method isn't already mapped to something, go ahead and do it
if (!annotatedMethods.containsKey(annotatedMethod)) {
evaluateResourceMethod(cri, enableStatic, md, m, annotatedMethod);
annotatedMethods.put(annotatedMethod, m);
Expand All @@ -352,6 +362,33 @@ private static void evaluateResourceClass(ClassResourceInfo cri, boolean enableS
cri.setMethodDispatcher(md);
}

private static Method findBridgedMethod(Class<?> serviceClass, Method bridge) {
Class<?>[] bridgeParamTypes = bridge.getParameterTypes();
Method bridged = null;
for (Method m : serviceClass.getMethods()) {
if (m.isBridge() || m.isSynthetic()
|| !m.getName().equals(bridge.getName())
|| m.getDeclaringClass() != bridge.getDeclaringClass()
|| m.getParameterCount() != bridgeParamTypes.length
|| !bridge.getReturnType().isAssignableFrom(m.getReturnType())) {
continue;
}
Class<?>[] paramTypes = m.getParameterTypes();
boolean matches = true;
for (int i = 0; i < paramTypes.length && matches; i++) {
matches = bridgeParamTypes[i].isAssignableFrom(paramTypes[i]);
}
if (matches) {
if (bridged != null) {
// ambiguous, more than one candidate
return null;
}
bridged = m;
}
}
return bridged;
}

private static void bindResourceMethod(MethodDispatcher md, Method m, Method bound) {
final OperationResourceInfo ori = md.getOperationResourceInfo(bound);
if (ori != null && !ori.getMethodToInvoke().equals(m)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@

import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.BeanParam;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PATCH;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.core.Application;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.UriInfo;
import jakarta.xml.bind.annotation.XmlRootElement;
import org.apache.cxf.BusFactory;
Expand Down Expand Up @@ -170,6 +175,48 @@ public void testClassResourceInfoWithOverride() throws Exception {
assertEquals("GET", ori.getHttpMethod());
}

@Test
public void testInterfaceBridgeMethodsWithBeanParam() throws Exception {
ClassResourceInfo cri = ResourceUtils.createClassResourceInfo(VersionedUserResource.class,
VersionedUserResource.class, true, true);
assertNotNull(cri);
Set<OperationResourceInfo> oris = cri.getMethodDispatcher().getOperationResourceInfos();
assertEquals(2, oris.size());
for (OperationResourceInfo ori : oris) {
Method m = ori.getMethodToInvoke();
assertFalse(m.toString(), m.isBridge());
assertEquals(UserIdParam.class, m.getParameterTypes()[0]);
}

for (Method m : VersionedUserResource.class.getMethods()) {
if (m.isBridge()) {
OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(m);
assertNotNull(m.toString(), ori);
assertFalse(ori.getMethodToInvoke().isBridge());
assertEquals(m.getName(), ori.getMethodToInvoke().getName());
}
}
}

@Test
public void testBridgeMethodToAnnotatedSuperInterfaceMethod() throws Exception {
ClassResourceInfo cri = ResourceUtils.createClassResourceInfo(OverridingAnnotatedResource.class,
OverridingAnnotatedResource.class, true, true);
assertNotNull(cri);
Set<OperationResourceInfo> oris = cri.getMethodDispatcher().getOperationResourceInfos();
assertEquals(2, oris.size());

OperationResourceInfo ori = cri.getMethodDispatcher().getOperationResourceInfo(
OverridingAnnotatedResource.class.getMethod("update", String.class));
assertEquals("PUT", ori.getHttpMethod());
assertEquals("/own", ori.getURITemplate().getValue());

ori = cri.getMethodDispatcher().getOperationResourceInfo(
OverridingAnnotatedResource.class.getMethod("update", Object.class));
assertEquals("POST", ori.getHttpMethod());
assertEquals("/base", ori.getURITemplate().getValue());
}

@Test
public void testClassResourceInfoWithBridgeMethod() throws Exception {
ClassResourceInfo cri =
Expand Down Expand Up @@ -413,6 +460,62 @@ public SuperBook get() {
}
}

public abstract static class RequestParam<T> {
@PathParam("id")
private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}

public static class UserIdParam extends RequestParam<String> {

}

public interface VersionedResource<U, I extends RequestParam<String>> {
Response update(I idParam, U updateDto);

default Response updateByPost(I idParam, U updateDto) {
return update(idParam, updateDto);
}
}

@Path("/users")
public interface VersionedUserResource extends VersionedResource<Book, UserIdParam> {
@PATCH
@Path("{id}")
@Override
Response update(@BeanParam UserIdParam idParam, Book updateDto);

@POST
@Path("{id}")
@Override
default Response updateByPost(@BeanParam UserIdParam idParam, Book updateDto) {
return VersionedResource.super.updateByPost(idParam, updateDto);
}
}

public interface AnnotatedGenericResource<T> {
@POST
@Path("base")
String update(T dto);
}

@Path("/overriding")
public static class OverridingAnnotatedResource implements AnnotatedGenericResource<String> {
@PUT
@Path("own")
@Override
public String update(String dto) {
return dto;
}
}

@XmlRootElement
public static class OrderItem {

Expand Down
Loading