Skip to content

Package: ManageableClassGenerator$SetterInterceptor

ManageableClassGenerator$SetterInterceptor

nameinstructionbranchcomplexitylinemethod
ManageableClassGenerator.SetterInterceptor()
M: 6 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
set(Manageable, Method)
M: 1 C: 35
97%
M: 1 C: 5
83%
M: 1 C: 3
75%
M: 1 C: 10
91%
M: 0 C: 1
100%

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.model.metamodel.gen;
19:
20: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
21: import cz.cvut.kbss.jopa.model.Manageable;
22: import cz.cvut.kbss.jopa.model.metamodel.AnnotatedAccessor;
23: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
24: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
25: import cz.cvut.kbss.jopa.sessions.UnitOfWork;
26: import cz.cvut.kbss.jopa.sessions.validator.AttributeModificationValidator;
27: import cz.cvut.kbss.jopa.utils.Configuration;
28: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
29: import cz.cvut.kbss.jopa.utils.MetamodelUtils;
30: import net.bytebuddy.ByteBuddy;
31: import net.bytebuddy.NamingStrategy;
32: import net.bytebuddy.description.modifier.FieldPersistence;
33: import net.bytebuddy.description.modifier.Visibility;
34: import net.bytebuddy.description.type.TypeDescription;
35: import net.bytebuddy.dynamic.DynamicType;
36: import net.bytebuddy.implementation.FieldAccessor;
37: import net.bytebuddy.implementation.MethodDelegation;
38: import net.bytebuddy.implementation.SuperMethodCall;
39: import net.bytebuddy.implementation.bind.annotation.Origin;
40: import net.bytebuddy.implementation.bind.annotation.This;
41: import org.slf4j.Logger;
42: import org.slf4j.LoggerFactory;
43:
44: import java.io.File;
45: import java.io.IOException;
46: import java.lang.reflect.Method;
47: import java.util.Objects;
48:
49: import static net.bytebuddy.matcher.ElementMatchers.isSetter;
50:
51: /**
52: * Generates persistence context-aware classes that implement the {@link cz.cvut.kbss.jopa.model.Manageable} interface.
53: * <p>
54: * Such classes have an additional attribute not inherited from the base entity class. This attribute's value is a
55: * reference to the persistence context to which an instance is attached. {@link cz.cvut.kbss.jopa.model.Manageable}
56: * allows establishing and accessing this connection.
57: */
58: public class ManageableClassGenerator implements PersistenceContextAwareClassGenerator {
59:
60: private static final Logger LOG = LoggerFactory.getLogger(ManageableClassGenerator.class);
61:
62: private final ByteBuddy byteBuddy = new ByteBuddy().with(new NamingStrategy.AbstractBase() {
63:
64: @Override
65: protected String name(TypeDescription typeDescription) {
66: return "JOPA_" + typeDescription.getSimpleName();
67: }
68: });
69:
70: private final Configuration config;
71:
72: public ManageableClassGenerator(Configuration config) {this.config = config;}
73:
74: @Override
75: public <T> Class<? extends T> generate(Class<T> entityClass) {
76: Objects.requireNonNull(entityClass);
77: LOG.trace("Generating dynamic type for entity class {}.", entityClass);
78: DynamicType.Unloaded<? extends T> typeDef = byteBuddy.subclass(entityClass)
79: .annotateType(entityClass.getAnnotations())
80: .annotateType(new GeneratedEntityClassImpl())
81: .defineField("persistenceContext", UnitOfWork.class, Visibility.PRIVATE, FieldPersistence.TRANSIENT)
82: .implement(Manageable.class)
83: .intercept(FieldAccessor.ofBeanProperty())
84: .method(isSetter().and(new PersistentPropertySetterMatcher<>(entityClass)))
85: .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(SetterInterceptor.class)))
86: .make();
87: LOG.debug("Generated dynamic type {} for entity class {}.", typeDef, entityClass);
88: outputGeneratedClass(typeDef);
89: return typeDef.load(getClass().getClassLoader()).getLoaded();
90: }
91:
92: private <T> void outputGeneratedClass(DynamicType.Unloaded<? extends T> typeDef) {
93: final String outputDir = config.get(JOPAPersistenceProperties.CLASS_GENERATOR_OUTPUT_DIR, "");
94: if (!outputDir.isBlank()) {
95: final File output = new File(outputDir);
96: try {
97: LOG.trace("Saving generated class '{}' to '{}'.", typeDef, output);
98: typeDef.saveIn(output);
99: } catch (IOException e) {
100: LOG.error("Unable to output generated class {} to file {}.", typeDef, output, e);
101: }
102: }
103: }
104:
105: public static class SetterInterceptor {
106:
107: private SetterInterceptor() {
108: throw new AssertionError();
109: }
110:
111: public static void set(@This Manageable instance, @Origin Method setter) {
112: final UnitOfWork pc = instance.getPersistenceContext();
113:• if (pc == null || !pc.isInTransaction()) {
114: return;
115: }
116: final String fieldName = AnnotatedAccessor.from(setter).getPropertyName();
117: final EntityType<?> et = pc.getMetamodel().entity(MetamodelUtils.getEntityClass(instance.getClass()));
118: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(fieldName);
119:• if (EntityPropertiesUtils.isFieldTransient(fieldSpec.getJavaField())) {
120: return;
121: }
122: AttributeModificationValidator.verifyCanModify(fieldSpec);
123: pc.attributeChanged(instance, fieldSpec);
124: }
125: }
126: }