Package: EntityReferenceProxyGenerator$SetterInterceptor
EntityReferenceProxyGenerator$SetterInterceptor
name | instruction | branch | complexity | line | method | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
EntityReferenceProxyGenerator.SetterInterceptor() |
|
|
|
|
|
||||||||||||||||||||
set(EntityReferenceProxy, Method, Object[]) |
|
|
|
|
|
Coverage
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.reference;
19:
20: import cz.cvut.kbss.jopa.exception.LazyLoadingException;
21: import cz.cvut.kbss.jopa.exceptions.AttributeModificationForbiddenException;
22: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
23: import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor;
24: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistenceContextAwareClassGenerator;
25: import cz.cvut.kbss.jopa.model.metamodel.gen.PersistentPropertySetterMatcher;
26: import cz.cvut.kbss.jopa.proxy.lazy.gen.PersistentPropertyGetterMatcher;
27: import cz.cvut.kbss.jopa.sessions.UnitOfWork;
28: import net.bytebuddy.ByteBuddy;
29: import net.bytebuddy.NamingStrategy;
30: import net.bytebuddy.description.modifier.FieldPersistence;
31: import net.bytebuddy.description.modifier.Visibility;
32: import net.bytebuddy.description.type.TypeDescription;
33: import net.bytebuddy.dynamic.DynamicType;
34: import net.bytebuddy.implementation.FieldAccessor;
35: import net.bytebuddy.implementation.MethodDelegation;
36: import net.bytebuddy.implementation.bind.annotation.AllArguments;
37: import net.bytebuddy.implementation.bind.annotation.Origin;
38: import net.bytebuddy.implementation.bind.annotation.RuntimeType;
39: import net.bytebuddy.implementation.bind.annotation.This;
40: import org.slf4j.Logger;
41: import org.slf4j.LoggerFactory;
42:
43: import java.lang.reflect.InvocationTargetException;
44: import java.lang.reflect.Method;
45: import java.net.URI;
46: import java.util.Objects;
47:
48: import static net.bytebuddy.matcher.ElementMatchers.isGetter;
49: import static net.bytebuddy.matcher.ElementMatchers.isSetter;
50:
51: public class EntityReferenceProxyGenerator implements PersistenceContextAwareClassGenerator {
52:
53: private static final Logger LOG = LoggerFactory.getLogger(EntityReferenceProxyGenerator.class);
54:
55: private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
56: @Override
57: protected String name(TypeDescription typeDescription) {
58: return typeDescription.getSimpleName() + "_ReferenceProxy";
59: }
60: });
61:
62: @Override
63: public <T> Class<? extends T> generate(Class<T> entityClass) {
64: Objects.requireNonNull(entityClass);
65: LOG.trace("Generating reference proxy for entity class {}.", entityClass);
66: DynamicType.Unloaded<? extends T> typeDef = byteBuddy.subclass(entityClass)
67: .annotateType(new GeneratedEntityReferenceProxyImpl())
68: .defineField("identifier", URI.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
69: .defineField("type", Class.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
70: .defineField("descriptor", Descriptor.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
71: .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
72: // Have to use Object, because otherwise it won't generate a setter for us
73: .defineField("value", entityClass, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
74: .implement(TypeDescription.Generic.Builder.parameterizedType(EntityReferenceProxyPropertyAccessor.class, entityClass)
75: .build())
76: .intercept(FieldAccessor.ofBeanProperty())
77: .implement(EntityReferenceProxy.class)
78: .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass)))
79: .intercept(MethodDelegation.to(SetterInterceptor.class))
80: .method(isGetter().and(new PersistentPropertyGetterMatcher<>(entityClass)))
81: .intercept(MethodDelegation.to(GetterInterceptor.class))
82: .make();
83: LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass);
84: return typeDef.load(getClass().getClassLoader()).getLoaded();
85: }
86:
87: public static class GetterInterceptor {
88:
89: private GetterInterceptor() {
90: throw new AssertionError();
91: }
92:
93: @RuntimeType
94: public static <T> Object get(@This EntityReferenceProxy<T> proxy, @Origin Method getter) {
95: if (isIdentifierField(proxy, getter)) {
96: return proxy.getIdentifier();
97: }
98: final Object loaded = proxy.triggerLoading();
99: try {
100: return getter.invoke(loaded);
101: } catch (InvocationTargetException | IllegalAccessException e) {
102: throw new LazyLoadingException("Unable to invoke getter after loading referenced object.", e);
103: }
104: }
105: }
106:
107: private static <T> boolean isIdentifierField(EntityReferenceProxy<T> proxy, Method accessor) {
108: final String fieldName = AnnotatedAccessor.from(accessor).getPropertyName();
109: return proxy.getPersistenceContext().getMetamodel().entity(proxy.getType()).getIdentifier().getName()
110: .equals(fieldName);
111: }
112:
113: public static class SetterInterceptor {
114:
115: private SetterInterceptor() {
116: throw new AssertionError();
117: }
118:
119: public static <T> void set(@This EntityReferenceProxy<T> proxy, @Origin Method setter,
120: @AllArguments Object[] args) {
121:• if (isIdentifierField(proxy, setter)) {
122: throw new AttributeModificationForbiddenException("Cannot change entity reference identifier.");
123: }
124: final Object loaded = proxy.triggerLoading();
125: try {
126: setter.invoke(loaded, args);
127: } catch (InvocationTargetException | IllegalAccessException e) {
128: throw new LazyLoadingException("Unable to invoke setter after loading referenced object.", e);
129: }
130: }
131: }
132: }