Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
30deb9b
CARE-no-cglib: checkpoint
Dec 22, 2022
68611cd
CARE-no-cglib: checkpoint
Dec 27, 2022
fb2d58f
CARE-no-cglib: checkpoint
Dec 28, 2022
7f3c17c
CARE-no-cglib: checkpoint
Dec 28, 2022
a1686e1
CARE-no-cglib: most things working now
Dec 28, 2022
9f51c3f
CARE-no-cglib: ??
Dec 28, 2022
22fb57d
CARE-no-cglib: bump failsafe plugin to rm deps that don't work on lat…
Dec 28, 2022
f17e450
CARE-no-cglib: yep
Dec 28, 2022
1dba8c6
CARE-no-cglib: checkpoint
Dec 28, 2022
6891a4c
CARE-no-cglib: checkpoint
Dec 28, 2022
27519c1
CARE-no-cglib: use instance creation to create a stateful delegate w/…
Dec 28, 2022
3759ba4
CARE-no-cglib: put this back
Dec 28, 2022
9401bf0
CARE-no-cglib: make it so we can know how actually bad we are
Dec 29, 2022
ab23d30
CARE-no-cglib: more info in timing tests
Dec 29, 2022
121f407
CARE-no-cglib: working some more ideas
Dec 29, 2022
83731b1
CARE-no-cglib: few more
Dec 29, 2022
1644bad
CARE-no-cglib: try this
Dec 29, 2022
260b0e6
CARE-no-cglib: try this
Dec 29, 2022
0e829a8
CARE-no-cglib: get more info on this assertion
Jan 3, 2023
ba40416
CARE-no-cglib: get more info on this assertion
Jan 3, 2023
51658f8
CARE-no-cglib: the timing on these seems trickier now
Jan 3, 2023
a93293d
CARE-no-cglib: cleanup
Jan 3, 2023
6a448a9
CARE-no-cglib: the timing on these seems trickier now
Jan 3, 2023
88135e7
CARE-no-cglib: the timing on these seems trickier now
Jan 3, 2023
5bef6be
CARE-no-cglib: correct some of these assertions (expected vs actual),…
Jan 3, 2023
77f35ce
CARE-no-cglib: put this back
Jan 3, 2023
2a77e0a
CARE-no-cglib: some cleanup
Jan 3, 2023
ab5984a
CARE-no-cglib: works on java8?
Jan 3, 2023
11b4fdc
CARE-no-cglib: some nits
Jan 3, 2023
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
2 changes: 1 addition & 1 deletion Jenkinsfile-continuous
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pipeline {

stage('Build') {
steps {
sh 'mvn clean verify -P code-coverage'
sh 'mvn clean verify'
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ The main cleanup we made with Jedis is, if the jedis client came from a pool, it

## Now onto Lua and Coolness:

Jedis provides a basic way of loading a Lua script into Redis and eval the script by its sha1 hash. rDBI provides this functionality via fluent queries, based off of [jDBI's fluent queries](http://jdbi.org/fluent_queries/). The application developer does not have to think about preloading the scripts on startup of the app or creating enums and storing sha1 in hashmaps. rDBI will cache the lua scripts internally and load them on demand while keeping it all thread-safe.
Jedis provides a basic way of loading a Lua script into Redis and eval the script by its sha1 hash. rDBI provides this functionality via fluent queries, based off of [jDBI's fluent queries](http://jdbi.org/fluent_queries/).
The application developer does not have to think about preloading the scripts on startup of the app or creating enums and storing sha1 in hashmaps. rDBI will cache the lua scripts internally and load them on demand while keeping it all thread-safe.

private static interface TestDAO {
@RedisQuery(
Expand Down
36 changes: 2 additions & 34 deletions rdbi-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,42 +13,10 @@
<artifactId>rdbi-core</artifactId>
<name>rDBI-core</name>

<build>
<plugins>
<!-- shading dangerous libs cglib and asm -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<executions>
<execution>
<id>shade-proxying-implementation</id>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>cglib:cglib-nodep</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>net.sf.cglib</pattern>
<shadedPattern>com.lithium.dbi.rdbi.shaded.net.sf.cglib</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<groupId>net.bytebuddy</groupId>
Copy link
Copy Markdown
Contributor

@phutwo phutwo Jan 4, 2023

Choose a reason for hiding this comment

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

neat, where did you find this library?
it's like a mostly drop in replacement of cglib.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

from the cglib homepage - https://github.com/cglib/cglib

IMPORTANT NOTE: cglib is unmaintained and does not work well (or possibly at all?) in newer JDKs, particularly JDK17+. If you need to support newer JDKs, we will accept well-tested well-thought-out patches... but you'll probably have better luck migrating to something like ByteBuddy.

<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
Expand Down
2 changes: 1 addition & 1 deletion rdbi-core/src/main/java/com/lithium/dbi/rdbi/Handle.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class Handle implements Closeable {

private final Jedis jedis;
private final Tracer tracer;
private JedisWrapperDoNotUse jedisWrapper;
private Jedis jedisWrapper;

private final ProxyFactory proxyFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

abstract class JedisWrapperDoNotUse extends Jedis {

JedisWrapperDoNotUse() {
public JedisWrapperDoNotUse() {
super();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,55 +5,85 @@
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.Factory;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.matcher.ElementMatchers;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisException;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

class JedisWrapperMethodInterceptor implements MethodInterceptor {
public class JedisWrapperMethodInterceptor {

private final Jedis jedis;
private final Tracer tracer;
private final Attributes commonAttributes;
private static final Class<? extends JedisWrapperDoNotUse> clazz = newLoadedClass();

static Factory newFactory() {
Enhancer e = new Enhancer();
e.setClassLoader(Jedis.class.getClassLoader());
e.setSuperclass(JedisWrapperDoNotUse.class);
e.setCallback(new MethodNoOpInterceptor());
return (Factory) e.create();
static Jedis newInstance(final Jedis realJedis, final Tracer tracer) {
try {
Object proxy = clazz.getDeclaredConstructor()
.newInstance();
final Field field = proxy.getClass().getDeclaredField("handler");
field.set(proxy, new JedisWrapperMethodInterceptor(realJedis, tracer));
return (Jedis) proxy;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException |
NoSuchFieldException e) {
throw new RuntimeException(e);
}
}

static JedisWrapperDoNotUse newInstance(final Factory factory, final Jedis realJedis, final Tracer tracer) {
return (JedisWrapperDoNotUse) factory.newInstance(new JedisWrapperMethodInterceptor(realJedis, tracer));
private static Class<? extends JedisWrapperDoNotUse> newLoadedClass() {
return new ByteBuddy()
.subclass(JedisWrapperDoNotUse.class)
.defineField("handler", JedisWrapperMethodInterceptor.class, Visibility.PUBLIC)
.method(ElementMatchers.isMethod())
.intercept(MethodDelegation.toField("handler"))
.make()
.load(Jedis.class.getClassLoader(), ClassLoadingStrategy.UsingLookup.withFallback(MethodHandles::lookup))
.getLoaded();
}

private JedisWrapperMethodInterceptor(Jedis jedis, Tracer tracer) {
public JedisWrapperMethodInterceptor(Jedis jedis, Tracer tracer) {
this.jedis = jedis;
this.tracer = tracer;
commonAttributes = Attributes.of(
AttributeKey.stringKey("db.type"), "redis",
AttributeKey.stringKey("component"), "rdbi"
);
);
}

@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
@RuntimeType
public Object intercept(
@AllArguments Object[] args,
@Origin Method method) {
Span s = tracer.spanBuilder(method.getName())
.setAllAttributes(commonAttributes)
.startSpan();
.setAllAttributes(commonAttributes)
.startSpan();
if (args.length > 0 && args[0] instanceof String) {
s.setAttribute("redis.key", (String) args[0]);
}
try (Scope scope = s.makeCurrent()) {
return methodProxy.invoke(jedis, args);
try (Scope ignored = s.makeCurrent()) {
return method.invoke(jedis, args);
} catch (JedisException e) {
s.recordException(e);
throw e;
} catch (InvocationTargetException e) {
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} finally {
s.end();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.lithium.dbi.rdbi;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;

Expand All @@ -10,43 +11,68 @@
import java.util.List;
import java.util.Map;

class MethodContextInterceptor implements MethodInterceptor {
public class MethodContextInterceptor {

private final Jedis jedis;
private final Map<Method, MethodContext> contexts;


public MethodContextInterceptor(Jedis jedis, Map<Method, MethodContext> contexts) {
this.jedis = jedis;
this.contexts = contexts;
}

@Override
@SuppressWarnings("unchecked")
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
@RuntimeType
public Object intercept(@AllArguments Object[] args,
@Origin Method method) {

MethodContext context = contexts.get(method);

Object ret = context.hasDynamicLists() ? callEvalDynamicList(context, objects)
: callEval(context, objects);

Object ret = context.hasDynamicLists() ? callEvalDynamicList(context, args)
: callEval(context, args);
if (ret == null) {
return null;
}

if (contexts.get(method).getMapper() != null) {
return contexts.get(method).getMapper().map(ret);

if (context.getMapper() != null) {
// here we need to adjust for the expected input to the mapper
Class<?> parameterType;
try {
parameterType = context.getMapper().getClass().getMethod("map", Integer.class).getParameterTypes()[0];
} catch (NoSuchMethodException e) {
parameterType = null;
}
return context.getMapper().map(adjust(parameterType, ret));
} else {
return adjust(method.getReturnType(), ret);
}
}

private Object adjust(Class<?> c, Object ret) {
// problem here is we are getting a LONG but expected an int
// did this work on older jdks? doesn't seem to be a cglib vs bytebuddy thing
// java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer (java.lang.Long and java.lang.Integer are in module java.base of loader 'bootstrap')
// i am not sure why this wasn't required before, and there's probably a utility out there
// to do it better the primary thing to handle is explicit cast to int where jedis returns a number
// as a long
if (c == null) {
return ret;
} else if ((c.equals(Integer.TYPE) || c.isAssignableFrom(Integer.class)) && ret instanceof Long) {
return ((Long) ret).intValue();
} else {
return ret;
}
}


@SuppressWarnings("unchecked")
private Object callEval(MethodContext context, Object[] objects) {

List<String> keys = objects.length > 0 ? (List<String>) objects[0] : null;
List<String> argv = objects.length > 1 ? (List<String>) objects[1] : null;
List<String> keys = objects.length > 0 ? (List<String>) objects[0] : new ArrayList<>();
List<String> argv = objects.length > 1 ? (List<String>) objects[1] : new ArrayList<>();

return evalShaHandleReloadScript(context, keys, argv);
return evalShaHandleReloadScript(context, keys, argv);
}

private Object callEvalDynamicList(MethodContext context, Object[] objects) {
Expand All @@ -62,7 +88,7 @@ private Object callEvalDynamicList(MethodContext context, Object[] objects) {
}
}

return evalShaHandleReloadScript(context, keys, argv);
return evalShaHandleReloadScript(context, keys, argv);
}

private Object evalShaHandleReloadScript(MethodContext context, List<String> keys, List<String> argv) {
Expand Down

This file was deleted.

Loading