Skip to contentMethod: LazyLoadingEntityProxyGenerator.ProxyMethodsInterceptor()
1: package cz.cvut.kbss.jopa.proxy.lazy.gen;
2:
3: import cz.cvut.kbss.jopa.exception.LazyLoadingException;
4: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
5: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator;
6: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertySetterMatcher;
7: import cz.cvut.kbss.jopa.sessions.UnitOfWork;
8: import net.bytebuddy.ByteBuddy;
9: import net.bytebuddy.NamingStrategy;
10: import net.bytebuddy.description.modifier.FieldPersistence;
11: import net.bytebuddy.description.modifier.Visibility;
12: import net.bytebuddy.description.type.TypeDescription;
13: import net.bytebuddy.dynamic.DynamicType;
14: import net.bytebuddy.implementation.FieldAccessor;
15: import net.bytebuddy.implementation.MethodDelegation;
16: import net.bytebuddy.implementation.bind.annotation.AllArguments;
17: import net.bytebuddy.implementation.bind.annotation.FieldValue;
18: import net.bytebuddy.implementation.bind.annotation.Origin;
19: import net.bytebuddy.implementation.bind.annotation.RuntimeType;
20: import net.bytebuddy.implementation.bind.annotation.This;
21: import org.slf4j.Logger;
22: import org.slf4j.LoggerFactory;
23:
24: import java.lang.reflect.InvocationTargetException;
25: import java.lang.reflect.Method;
26: import java.util.Objects;
27:
28: import static net.bytebuddy.matcher.ElementMatchers.isGetter;
29: import static net.bytebuddy.matcher.ElementMatchers.isSetter;
30: import static net.bytebuddy.matcher.ElementMatchers.isToString;
31: import static net.bytebuddy.matcher.ElementMatchers.named;
32:
33: public class LazyLoadingEntityProxyGenerator implements PersistenceContextAwareClassGenerator {
34:
35: private static final Logger LOG = LoggerFactory.getLogger(LazyLoadingEntityProxyGenerator.class);
36:
37: private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
38: @Override
39: protected String name(TypeDescription typeDescription) {
40: return typeDescription.getSimpleName() + "_LazyLoadingProxy";
41: }
42: });
43:
44: @Override
45: public <T> Class<? extends T> generate(Class<T> entityClass) {
46: Objects.requireNonNull(entityClass);
47: LOG.trace("Generating lazy loading proxy for entity class {}.", entityClass);
48: DynamicType.Unloaded<? extends T> typeDef = byteBuddy.subclass(entityClass)
49: .annotateType(new GeneratedLazyLoadingProxyImpl())
50: .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
51: .defineField("owner", Object.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
52: .defineField("fieldSpec", FieldSpecification.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
53: // Have to use Object, because otherwise it won't generate a setter for us
54: .defineField("value", entityClass, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
55: .implement(TypeDescription.Generic.Builder.parameterizedType(LazyLoadingProxyPropertyAccessor.class, entityClass).build())
56: .intercept(FieldAccessor.ofBeanProperty())
57: .implement(LazyLoadingEntityProxy.class)
58: .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass)))
59: .intercept(MethodDelegation.to(SetterInterceptor.class))
60: .method(isGetter().and(new PersistentPropertyGetterMatcher<>(entityClass)))
61: .intercept(MethodDelegation.to(GetterInterceptor.class))
62: .method(isToString())
63: .intercept(MethodDelegation.toMethodReturnOf("stringify"))
64: .method(named("isLoaded"))
65: .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class))
66: .method(named("getLoadedValue"))
67: .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class))
68: .make();
69: LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass);
70: return typeDef.load(getClass().getClassLoader()).getLoaded();
71: }
72:
73: public static class GetterInterceptor {
74:
75: private GetterInterceptor() {
76: throw new AssertionError();
77: }
78:
79: @RuntimeType
80: public static <T> Object get(@This LazyLoadingEntityProxy<T> proxy, @Origin Method getter) {
81: final Object loaded = proxy.triggerLazyLoading();
82: try {
83: return getter.invoke(loaded);
84: } catch (InvocationTargetException | IllegalAccessException e) {
85: throw new LazyLoadingException("Unable to invoke getter after lazily loading object.", e);
86: }
87: }
88: }
89:
90: public static class SetterInterceptor {
91:
92: private SetterInterceptor() {
93: throw new AssertionError();
94: }
95:
96: public static <T> void set(@This LazyLoadingEntityProxy<T> proxy, @Origin Method setter,
97: @AllArguments Object[] args) {
98: final Object loaded = proxy.triggerLazyLoading();
99: try {
100: setter.invoke(loaded, args);
101: } catch (InvocationTargetException | IllegalAccessException e) {
102: throw new LazyLoadingException("Unable to invoke setter after lazily loading object.", e);
103: }
104: }
105: }
106:
107: public static class ProxyMethodsInterceptor {
108:
109: private ProxyMethodsInterceptor() {
110: throw new AssertionError();
111: }
112:
113: public static <T> boolean isLoaded(@This LazyLoadingEntityProxy<T> proxy, @FieldValue("value") T value) {
114: return value != null;
115: }
116:
117: public static <T> T getLoadedValue(@This LazyLoadingEntityProxy<T> proxy, @FieldValue("value") T value) {
118: if (value == null) {
119: throw new IllegalStateException("Proxy has not been loaded, yet.");
120: }
121: return value;
122: }
123: }
124: }