Skip to content
Closed
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
29 changes: 29 additions & 0 deletions core/src/main/java/org/apache/cxf/common/util/ReflectionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.cxf.common.classloader.ClassLoaderUtils;

Expand Down Expand Up @@ -286,4 +290,29 @@ private static <T extends Annotation> T getAnnotationForInterface(Class<?> intf,
}
return null;
}

/**
* Find all bridge methods corresponding to the real method {@param m}.
* @param m A real method.
* @return The bridge methods as a Collection.
*/
public static Collection<Method> findBridges(Method m) {
Set<Method> bridges = new HashSet<>();

if (m.isBridge() || m.isSynthetic()) {
return bridges;
}

Class<?> c = m.getDeclaringClass();
for (Method candidate : c.getMethods()) {
if (candidate.isBridge()
&& candidate.getName().equals(m.getName())
&& Arrays.equals(candidate.getParameterTypes(), m.getParameterTypes())) {
bridges.add(candidate);
}
}

return bridges;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* http://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.apache.cxf.common.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.junit.Test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

public class ReflectionUtilTest {

private static class MultiBridgeExample {
public MultiBridgeExample returnThis() {
return this;
}
}

private static class MultiBridgeExample2 extends MultiBridgeExample {
public MultiBridgeExample2 returnThis() {
return this;
}
}

private static class MultiBridgeExample3 extends MultiBridgeExample2 {
public MultiBridgeExample3 returnThis() {
return this;
}

// Also has two bridge methods.
}

@Test
public void testFindBridges() {
MultiBridgeExample3 example = new MultiBridgeExample3();

Method real = null;
List<Method> bridges = new ArrayList<>(2);

for (Method m : example.getClass().getDeclaredMethods()) {
if (m.isBridge()) {
bridges.add(m);
} else {
assertNull(real);
real = m;
}
}

assertEquals(2, bridges.size());
assertNotNull(real);

Collection<Method> bridgesOfReal = ReflectionUtil.findBridges(real);
Collection<Method> bridgesOfBridge = ReflectionUtil.findBridges(bridges.get(0));

assertEquals(bridges, new ArrayList<>(bridgesOfReal));
assertTrue(bridgesOfBridge.isEmpty());

}
}
2 changes: 2 additions & 0 deletions distribution/src/main/appended-resources/META-INF/NOTICE
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ This product includes code from the Nimbus JOSE + JWT project, under the Apache
license 2.0 (https://github.com/felx/nimbus-jose-jwt). Copyright 2012-2017,
Connect2id Ltd.

Portions of this software were developed at
Cloudera, Inc (https://www.cloudera.com/).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thanks so much for the contribution!

But I'm totally not sure if we should/can add this into NOTICE. Let's wait other comments from the community.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure (and curious) about this part as well. Thank you very much for pointing this out!

Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.apache.cxf.common.classloader.ClassLoaderUtils;
import org.apache.cxf.common.i18n.BundleUtils;
import org.apache.cxf.common.logging.LogUtils;
import org.apache.cxf.common.util.ReflectionUtil;
import org.apache.cxf.common.util.StringUtils;
import org.apache.cxf.feature.Feature;
import org.apache.cxf.helpers.CastUtils;
Expand Down Expand Up @@ -357,7 +358,14 @@ private static void evaluateResourceMethod(ClassResourceInfo cri, boolean enable
return;
}

md.bind(createOperationInfo(m, annotatedMethod, cri, path, httpMethod), m);
// Binding the bridge methods here as secondary methods allows them to work in client proxies
// without reintroducing CXF-7670.
List<Method> boundMethods = new ArrayList<>();
boundMethods.add(m);
boundMethods.addAll(ReflectionUtil.findBridges(m));
md.bind(
createOperationInfo(m, annotatedMethod, cri, path, httpMethod),
boundMethods.toArray(new Method[]{}));
if (httpMethod == null) {
// subresource locator
Class<?> subClass = m.getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
import org.apache.cxf.jaxrs.Customer;
import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
import org.apache.cxf.jaxrs.model.ClassResourceInfo;
import org.apache.cxf.jaxrs.model.MethodDispatcher;
import org.apache.cxf.jaxrs.model.OperationResourceInfo;
import org.apache.cxf.jaxrs.model.Parameter;
import org.apache.cxf.jaxrs.model.ParameterType;
Expand Down Expand Up @@ -210,6 +211,57 @@ public void testClassResourceInfoWithSyntheticMethod() throws Exception {
cri.getMethodDispatcher().getOperationResourceInfos().size());
}

@Path("/bridge-example")
private interface BridgeExample<T> {
@GET
@Path("/foo")
T foo();
}

private static class BridgeExampleImpl implements BridgeExample<String> {
public String foo() {
return "Hello World!";
}
}

@Test
public void testClassResourceInfoSecondaryBinds() {
ClassResourceInfo cri = ResourceUtils.createClassResourceInfo(
BridgeExampleImpl.class,
BridgeExampleImpl.class,
true,
false);

MethodDispatcher md = cri.getMethodDispatcher();

Method bridge = null;
Method real = null;

for (Method candidate : BridgeExampleImpl.class.getDeclaredMethods()) {
if (candidate.isBridge()) {
bridge = candidate;
} else {
real = candidate;
}
if (bridge != null && real != null) {
break;
}
}

assertNotNull(bridge);
assertNotNull(real);

// Only one ori-to-method mapping should exist, to the real foo()
Set<OperationResourceInfo> oris = md.getOperationResourceInfos();
assertEquals(1, oris.size());
OperationResourceInfo ori = oris.iterator().next();
assertEquals(real, md.getMethod(ori));

// Both the bridge and real method should have a method-to-ori mapping
assertEquals(ori, md.getOperationResourceInfo(bridge));
assertEquals(ori, md.getOperationResourceInfo(real));
}

protected interface OverriddenInterface<T> {
@GET
@Path("/{key}")
Expand Down Expand Up @@ -343,4 +395,4 @@ private static class SuperApplication extends BaseApplication {
private static class CustomApplication extends BaseApplication {

}
}
}