Skip to content

Commit

Permalink
better proxy unwrap
Browse files Browse the repository at this point in the history
  • Loading branch information
SylvainJuge committed Nov 13, 2024
1 parent 54a9f00 commit a7f57cf
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.Field;
import java.util.List;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -67,20 +67,8 @@ public static void afterRefresh(
return;
}
Object bean = springCtx.getBean("otelAutoDispatcherFilter");
OpenTelemetryHandlerMappingFilter filter;
if (bean instanceof OpenTelemetryHandlerMappingFilter) {
// TODO: remove this branch once advices are not inlined anymore as it's no longer relevant
// inline advice: no proxy class is used
filter = (OpenTelemetryHandlerMappingFilter) bean;
} else {
// non-inlined advice: proxy class is used
try {
Field delegate = bean.getClass().getField("delegate");
filter = (OpenTelemetryHandlerMappingFilter) delegate.get(bean);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
OpenTelemetryHandlerMappingFilter filter =
IndyProxy.unwrapIfNeeded(bean, OpenTelemetryHandlerMappingFilter.class);
filter.setHandlerMappings(handlerMappings);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@

import io.opentelemetry.context.Context;
import io.opentelemetry.context.Scope;
import io.opentelemetry.javaagent.bootstrap.IndyProxy;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import java.lang.reflect.Field;
import java.util.List;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
Expand Down Expand Up @@ -68,19 +68,8 @@ public static void afterRefresh(
}

Object bean = springCtx.getBean("otelAutoDispatcherFilter");
OpenTelemetryHandlerMappingFilter filter;
if (bean instanceof OpenTelemetryHandlerMappingFilter) {
// inline advice: no proxy class is used
filter = (OpenTelemetryHandlerMappingFilter) bean;
} else {
// non-inlined advice: proxy class is used
try {
Field delegate = bean.getClass().getField("delegate");
filter = (OpenTelemetryHandlerMappingFilter) delegate.get(bean);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
OpenTelemetryHandlerMappingFilter filter =
IndyProxy.unwrapIfNeeded(bean, OpenTelemetryHandlerMappingFilter.class);
filter.setHandlerMappings(handlerMappings);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.javaagent.bootstrap;

import java.lang.reflect.Field;

/** Interface added to proxy classes injected for indy instrumentation */
@SuppressWarnings("InterfaceWithOnlyStatics") // TODO: remove this once we have a method
public interface IndyProxy {

/**
* Unwraps the proxied object
*
* @return unwrapped object
*/
default Object unwrap() {
try {
// current implementation based on introspection + public delegate field
Field delegate = this.getClass().getField("delegate");
return delegate.get(this);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}

/**
* Transparently unwraps an object that might have been proxied for indy instrumentation. When
* unwrapping is not needed, for example for inlined advices, the original object is returned
* effectively making this equivalent to a type cast.
*
* @param o object that might need unwrapping
* @param type expected unwrapped object type
* @param <T> type of the proxied object
* @return unwrapped object
* @throws IllegalArgumentException when object can't be cast or unwrapped to the desired type
*/
static <T> T unwrapIfNeeded(Object o, Class<T> type) {
if (type.isAssignableFrom(o.getClass())) {
return type.cast(o);
}
if (o instanceof IndyProxy) {
Object unwrapped = ((IndyProxy) o).unwrap();
if (type.isAssignableFrom(unwrapped.getClass())) {
return type.cast(unwrapped);
}
}
throw new IllegalArgumentException("unexpected object unwrap");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

package io.opentelemetry.javaagent.tooling.instrumentation.indy;

import io.opentelemetry.javaagent.bootstrap.IndyProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.method.ParameterDescription;
import net.bytebuddy.description.type.TypeDefinition;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
Expand Down Expand Up @@ -87,10 +90,12 @@ public IndyProxyFactory(Method bootstrapMethod, BootstrapArgsProvider bootstrapA
public DynamicType.Unloaded<?> generateProxy(
TypeDescription classToProxy, String proxyClassName) {
TypeDescription.Generic superClass = classToProxy.getSuperClass();
List<TypeDefinition> interfaces = new ArrayList<>(classToProxy.getInterfaces());
interfaces.add(TypeDescription.ForLoadedType.of(IndyProxy.class));
DynamicType.Builder<?> builder =
new ByteBuddy()
.subclass(superClass, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.implement(classToProxy.getInterfaces())
.implement(interfaces)
.name(proxyClassName)
.annotateType(classToProxy.getDeclaredAnnotations())
// field must be public to enable resolving the proxy target using introspection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import static org.assertj.core.api.Assertions.assertThat;

import io.opentelemetry.javaagent.bootstrap.IndyProxy;
import io.opentelemetry.javaagent.tooling.instrumentation.indy.dummies.DummyAnnotation;
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
Expand Down Expand Up @@ -319,6 +320,28 @@ void verifyNonPublicMembersIgnored() throws Exception {
.anySatisfy(method -> assertThat(method.getName()).isEqualTo("publicStaticMethod"));
}

@Test
void verifyUnwrap() throws Exception {
Class<?> proxy = generateProxy(WrappingTest.class);
Object proxyInstance = proxy.getConstructor().newInstance();
assertThat(proxyInstance)
.describedAs("proxies should implement IndyProxy")
.isNotInstanceOf(WrappingTest.class)
.isInstanceOf(IndyProxy.class);

WrappingTest unwrapped = IndyProxy.unwrapIfNeeded(proxyInstance, WrappingTest.class);
assertThat(unwrapped).isInstanceOf(WrappingTest.class).isNotInstanceOf(IndyProxy.class);

assertThat(IndyProxy.unwrapIfNeeded(unwrapped, WrappingTest.class))
.describedAs("unwrap an already unwrapped is a no-op")
.isSameAs(unwrapped);
}

public static class WrappingTest {

public WrappingTest() {}
}

private static Class<?> generateProxy(Class<?> clazz) {
DynamicType.Unloaded<?> unloaded =
proxyFactory.generateProxy(
Expand Down

0 comments on commit a7f57cf

Please sign in to comment.