Skip to contentMethod: LazyLoadingEntityProxyGenerator.GetterInterceptor()
1: /*
2: * JOPA
3: * Copyright (C) 2024 Czech Technical University in Prague
4: *
5: * This library is free software; you can redistribute it and/or
6: * modify it under the terms of the GNU Lesser General Public
7: * License as published by the Free Software Foundation; either
8: * version 3.0 of the License, or (at your option) any later version.
9: *
10: * This library is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13: * Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public
16: * License along with this library.
17: */
18: package cz.cvut.kbss.jopa.proxy.lazy.gen;
19:
20: import cz.cvut.kbss.jopa.exception.LazyLoadingException;
21: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
22: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator;
23: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertySetterMatcher;
24: import cz.cvut.kbss.jopa.sessions.UnitOfWork;
25: import net.bytebuddy.ByteBuddy;
26: import net.bytebuddy.NamingStrategy;
27: import net.bytebuddy.description.modifier.FieldPersistence;
28: import net.bytebuddy.description.modifier.Visibility;
29: import net.bytebuddy.description.type.TypeDescription;
30: import net.bytebuddy.dynamic.DynamicType;
31: import net.bytebuddy.implementation.FieldAccessor;
32: import net.bytebuddy.implementation.MethodDelegation;
33: import net.bytebuddy.implementation.bind.annotation.AllArguments;
34: import net.bytebuddy.implementation.bind.annotation.FieldValue;
35: import net.bytebuddy.implementation.bind.annotation.Origin;
36: import net.bytebuddy.implementation.bind.annotation.RuntimeType;
37: import net.bytebuddy.implementation.bind.annotation.This;
38: import org.slf4j.Logger;
39: import org.slf4j.LoggerFactory;
40:
41: import java.lang.reflect.InvocationTargetException;
42: import java.lang.reflect.Method;
43: import java.util.Objects;
44:
45: import static net.bytebuddy.matcher.ElementMatchers.isGetter;
46: import static net.bytebuddy.matcher.ElementMatchers.isSetter;
47: import static net.bytebuddy.matcher.ElementMatchers.isToString;
48: import static net.bytebuddy.matcher.ElementMatchers.named;
49:
50: public class LazyLoadingEntityProxyGenerator implements PersistenceContextAwareClassGenerator {
51:
52: private static final Logger LOG = LoggerFactory.getLogger(LazyLoadingEntityProxyGenerator.class);
53:
54: private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
55: @Override
56: protected String name(TypeDescription typeDescription) {
57: return typeDescription.getSimpleName() + "_LazyLoadingProxy";
58: }
59: });
60:
61: @Override
62: public <T> Class<? extends T> generate(Class<T> entityClass) {
63: Objects.requireNonNull(entityClass);
64: LOG.trace("Generating lazy loading proxy for entity class {}.", entityClass);
65: DynamicType.Unloaded<? extends T> typeDef = byteBuddy.subclass(entityClass)
66: .annotateType(new GeneratedLazyLoadingProxyImpl())
67: .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
68: .defineField("owner", Object.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
69: .defineField("fieldSpec", FieldSpecification.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
70: // Have to use Object, because otherwise it won't generate a setter for us
71: .defineField("value", entityClass, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
72: .implement(TypeDescription.Generic.Builder.parameterizedType(LazyLoadingProxyPropertyAccessor.class, entityClass).build())
73: .intercept(FieldAccessor.ofBeanProperty())
74: .implement(LazyLoadingEntityProxy.class)
75: .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass)))
76: .intercept(MethodDelegation.to(SetterInterceptor.class))
77: .method(isGetter().and(new PersistentPropertyGetterMatcher<>(entityClass)))
78: .intercept(MethodDelegation.to(GetterInterceptor.class))
79: .method(isToString())
80: .intercept(MethodDelegation.toMethodReturnOf("stringify"))
81: .method(named("isLoaded"))
82: .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class))
83: .method(named("getLoadedValue"))
84: .intercept(MethodDelegation.to(ProxyMethodsInterceptor.class))
85: .make();
86: LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass);
87: return typeDef.load(getClass().getClassLoader()).getLoaded();
88: }
89:
90: public static class GetterInterceptor {
91:
92: private GetterInterceptor() {
93: throw new AssertionError();
94: }
95:
96: @RuntimeType
97: public static <T> Object get(@This LazyLoadingEntityProxy<T> proxy, @Origin Method getter) {
98: final Object loaded = proxy.triggerLazyLoading();
99: try {
100: return getter.invoke(loaded);
101: } catch (InvocationTargetException | IllegalAccessException e) {
102: throw new LazyLoadingException("Unable to invoke getter after lazily loading object.", e);
103: }
104: }
105: }
106:
107: public static class SetterInterceptor {
108:
109: private SetterInterceptor() {
110: throw new AssertionError();
111: }
112:
113: public static <T> void set(@This LazyLoadingEntityProxy<T> proxy, @Origin Method setter,
114: @AllArguments Object[] args) {
115: final Object loaded = proxy.triggerLazyLoading();
116: try {
117: setter.invoke(loaded, args);
118: } catch (InvocationTargetException | IllegalAccessException e) {
119: throw new LazyLoadingException("Unable to invoke setter after lazily loading object.", e);
120: }
121: }
122: }
123:
124: public static class ProxyMethodsInterceptor {
125:
126: private ProxyMethodsInterceptor() {
127: throw new AssertionError();
128: }
129:
130: public static <T> boolean isLoaded(@This LazyLoadingEntityProxy<T> proxy, @FieldValue("value") T value) {
131: return value != null;
132: }
133:
134: public static <T> T getLoadedValue(@This LazyLoadingEntityProxy<T> proxy, @FieldValue("value") T value) {
135: if (value == null) {
136: throw new IllegalStateException("Proxy has not been loaded, yet.");
137: }
138: return value;
139: }
140: }
141: }