Skip to content

Method: static {...}

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.oom;
19:
20: import cz.cvut.kbss.jopa.model.JOPAPersistenceProperties;
21: import cz.cvut.kbss.jopa.model.LoadState;
22: import cz.cvut.kbss.jopa.model.TypedQueryImpl;
23: import cz.cvut.kbss.jopa.model.annotations.FetchType;
24: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
25: import cz.cvut.kbss.jopa.model.metamodel.AbstractQueryAttribute;
26: import cz.cvut.kbss.jopa.model.metamodel.Attribute;
27: import cz.cvut.kbss.jopa.model.metamodel.CollectionType;
28: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
29: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
30: import cz.cvut.kbss.jopa.model.metamodel.IdentifiableEntityType;
31: import cz.cvut.kbss.jopa.model.metamodel.PluralQueryAttribute;
32: import cz.cvut.kbss.jopa.model.metamodel.PluralQueryAttributeImpl;
33: import cz.cvut.kbss.jopa.model.metamodel.QueryAttribute;
34: import cz.cvut.kbss.jopa.oom.query.PluralQueryAttributeStrategy;
35: import cz.cvut.kbss.jopa.oom.query.QueryFieldStrategy;
36: import cz.cvut.kbss.jopa.oom.query.SingularQueryAttributeStrategy;
37: import cz.cvut.kbss.jopa.query.sparql.SparqlQueryFactory;
38: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptor;
39: import cz.cvut.kbss.jopa.sessions.descriptor.LoadStateDescriptorFactory;
40: import cz.cvut.kbss.jopa.sessions.util.LoadStateDescriptorRegistry;
41: import cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator;
42: import cz.cvut.kbss.jopa.utils.CollectionFactory;
43: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
44: import cz.cvut.kbss.jopa.utils.ReflectionUtils;
45: import cz.cvut.kbss.jopa.vocabulary.RDF;
46: import cz.cvut.kbss.ontodriver.model.Axiom;
47: import org.slf4j.Logger;
48: import org.slf4j.LoggerFactory;
49:
50: import java.net.URI;
51: import java.util.Collection;
52: import java.util.HashMap;
53: import java.util.Map;
54: import java.util.Set;
55:
56: import static cz.cvut.kbss.jopa.model.metamodel.AbstractQueryAttribute.THIS_PARAMETER;
57: import static cz.cvut.kbss.jopa.sessions.validator.IntegrityConstraintsValidator.isNotLazy;
58:
59: class EntityConstructor {
60:
61: private static final Logger LOG = LoggerFactory.getLogger(EntityConstructor.class);
62:
63: private final ObjectOntologyMapperImpl mapper;
64:
65: private final LoadStateDescriptorRegistry loadStateRegistry;
66:
67: EntityConstructor(ObjectOntologyMapperImpl mapper, LoadStateDescriptorRegistry loadStateRegistry) {
68: this.mapper = mapper;
69: this.loadStateRegistry = loadStateRegistry;
70: }
71:
72: /**
73: * Creates an instance of the specified {@link EntityType} with the specified identifier and populates its
74: * attributes from the specified axioms.
75: *
76: * @param constructionParams Parameters for constructing the entity
77: * @param axioms Axioms from which the instance attribute values should be reconstructed
78: * @param <T> Entity type
79: * @return New instance with populated attributes
80: */
81: <T> T reconstructEntity(EntityConstructionParameters<T> constructionParams, Collection<Axiom<?>> axioms) {
82: assert !axioms.isEmpty();
83: final IdentifiableEntityType<T> et = constructionParams.entityType();
84:
85: if (!axiomsContainEntityClassAssertion(axioms, et)) {
86: return null;
87: }
88: final T instance = createEntityInstance(constructionParams.id(), et);
89: mapper.registerInstance(constructionParams.id(), instance);
90: final LoadStateDescriptor<T> loadStateDescriptor = LoadStateDescriptorFactory.createAllUnknown(instance, et);
91: loadStateRegistry.put(instance, loadStateDescriptor);
92: populateAttributes(instance, constructionParams, axioms, loadStateDescriptor);
93: populateQueryAttributes(instance, et, loadStateDescriptor);
94: processEmptyAttributes(instance, et, loadStateDescriptor);
95: validateIntegrityConstraints(instance, et);
96:
97: return instance;
98: }
99:
100: private static boolean axiomsContainEntityClassAssertion(Collection<Axiom<?>> axioms, EntityType<?> et) {
101: return axioms.stream().anyMatch(ax -> MappingUtils.isEntityClassAssertion(ax, et));
102: }
103:
104: /**
105: * Instantiates an entity of the specified {@link EntityType} with the specified identifier.
106: *
107: * @param identifier Entity identifier
108: * @param et Entity type
109: * @param <T> Entity type
110: * @return Newly created instance with identifier set
111: */
112: <T> T createEntityInstance(URI identifier, IdentifiableEntityType<T> et) {
113: // TODO et.getInstantiableJavaType?
114: final T instance = ReflectionUtils.instantiateUsingDefaultConstructor(et.getJavaType());
115: EntityPropertiesUtils.setIdentifier(identifier, instance, et);
116: return instance;
117: }
118:
119: private <T> void populateAttributes(final T instance, EntityConstructionParameters<T> constructionParams,
120: Collection<Axiom<?>> axioms, LoadStateDescriptor<T> loadStateDescriptor) {
121: final IdentifiableEntityType<T> et = constructionParams.entityType();
122: final Map<URI, FieldSpecification<? super T, ?>> attributes = indexEntityAttributes(et);
123: final Map<FieldSpecification<? super T, ?>, FieldStrategy<? extends FieldSpecification<? super T, ?>, T>>
124: fieldLoaders = new HashMap<>(et.getAttributes().size());
125: for (Axiom<?> ax : axioms) {
126: if (MappingUtils.isEntityClassAssertion(ax, et)) {
127: continue;
128: }
129: final FieldStrategy<? extends FieldSpecification<? super T, ?>, T> fs = getFieldLoader(
130: ax, attributes, fieldLoaders, et, constructionParams.descriptor());
131: if (fs == null) {
132: if (!MappingUtils.isClassAssertion(ax)) {
133: LOG.warn("No attribute found for property {}. Axiom {} will be skipped.", ax.getAssertion(), ax);
134: }
135: continue;
136: }
137: if (fs.attribute.getFetchType() == FetchType.LAZY && !constructionParams.forceEager()) {
138: fs.lazilyAddAxiomValue(ax);
139: } else {
140: fs.addAxiomValue(ax);
141: }
142: }
143: // We need to build the field values separately because some may be
144: // plural and we have to wait until all values are prepared
145: for (FieldStrategy<? extends FieldSpecification<?, ?>, ?> fs : fieldLoaders.values()) {
146: fs.buildInstanceFieldValue(instance);
147: if (fs.attribute.getFetchType() == FetchType.LAZY && !constructionParams.forceEager() && fs.hasValue()) {
148: loadStateDescriptor.setLoaded((FieldSpecification<? super T, ?>) fs.attribute, LoadState.NOT_LOADED);
149: } else {
150: loadStateDescriptor.setLoaded((FieldSpecification<? super T, ?>) fs.attribute, LoadState.LOADED);
151: }
152: }
153: }
154:
155: private static <T> Map<URI, FieldSpecification<? super T, ?>> indexEntityAttributes(EntityType<T> et) {
156: final Map<URI, FieldSpecification<? super T, ?>> atts = new HashMap<>(et.getAttributes().size());
157: for (Attribute<? super T, ?> at : et.getAttributes()) {
158: atts.put(at.getIRI().toURI(), at);
159: }
160: if (et.getTypes() != null) {
161: atts.put(URI.create(RDF.TYPE), et.getTypes());
162: }
163: return atts;
164: }
165:
166: private <T> FieldStrategy<? extends FieldSpecification<? super T, ?>, T> getFieldLoader(
167: Axiom<?> ax,
168: Map<URI, FieldSpecification<? super T, ?>> attributes,
169: Map<FieldSpecification<? super T, ?>, FieldStrategy<? extends FieldSpecification<? super T, ?>, T>> loaders,
170: EntityType<T> et, Descriptor desc) {
171: final URI attId = ax.getAssertion().getIdentifier();
172: FieldSpecification<? super T, ?> att = attributes.get(attId);
173: if (att == null) {
174: if (et.getProperties() != null) {
175: att = et.getProperties();
176: } else {
177: return null;
178: }
179: }
180: if (!loaders.containsKey(att)) {
181: loaders.put(att, FieldStrategy.createFieldStrategy(et, att, desc, mapper));
182: }
183: return loaders.get(att);
184: }
185:
186: /**
187: * Populate all query based attributes in the given instance.
188: *
189: * @param instance the entity, whose attributes are to be populated
190: * @param et the entity class representation in the metamodel
191: * @param <T> the entity class
192: */
193: public <T> void populateQueryAttributes(T instance, EntityType<T> et) {
194: populateQueryAttributes(instance, et, loadStateRegistry.get(instance));
195: }
196:
197: private <T> void populateQueryAttributes(T instance, EntityType<T> et, LoadStateDescriptor<T> loadStateDescriptor) {
198: final SparqlQueryFactory queryFactory = mapper.getUow().sparqlQueryFactory();
199:
200: final Set<QueryAttribute<? super T, ?>> queryAttributes = et.getQueryAttributes();
201:
202: for (QueryAttribute<? super T, ?> queryAttribute : queryAttributes) {
203: if (queryAttribute.getFetchType() != FetchType.LAZY) {
204: populateQueryAttribute(instance, queryAttribute, queryFactory, et);
205: loadStateDescriptor.setLoaded(queryAttribute, LoadState.LOADED);
206: } else {
207: loadStateDescriptor.setLoaded(queryAttribute, LoadState.NOT_LOADED);
208: }
209: }
210: }
211:
212: private <T> void populateQueryAttribute(T instance, QueryAttribute<? super T, ?> queryAttribute,
213: SparqlQueryFactory queryFactory, EntityType<T> et) {
214: TypedQueryImpl<?> typedQuery;
215: try {
216: if (queryAttribute.isCollection()) {
217: PluralQueryAttribute<? super T, ?, ?> pluralQueryAttribute =
218: (PluralQueryAttribute<? super T, ?, ?>) queryAttribute;
219: typedQuery = queryFactory.createNativeQuery(pluralQueryAttribute.getQuery(),
220: pluralQueryAttribute.getElementType().getJavaType());
221: } else {
222: typedQuery = queryFactory.createNativeQuery(queryAttribute.getQuery(), queryAttribute.getJavaType());
223: }
224: } catch (RuntimeException e) {
225: LOG.error("""
226: Could not create native query from the parameter given in annotation @Sparql:
227: {}
228: Attribute '{}' will be skipped.""", queryAttribute.getQuery(),
229: queryAttribute.getJavaMember().getName(), e);
230: return;
231: }
232:
233: setAttributeQueryParameters(instance, queryAttribute, typedQuery, et);
234:
235: QueryFieldStrategy<? extends AbstractQueryAttribute<? super T, ?>, T> qfs =
236: getQueryFieldLoader(et, queryAttribute);
237:
238: qfs.addValueFromTypedQuery(typedQuery);
239: qfs.buildInstanceFieldValue(instance);
240: }
241:
242: private static <T> void setAttributeQueryParameters(T instance, QueryAttribute<? super T, ?> queryAtt,
243: TypedQueryImpl<?> query, EntityType<?> et) {
244: try {
245: if (query.hasParameter(THIS_PARAMETER)) {
246: // set value of variable "this", if it is present in the query, with the entity instance
247: query.setParameter(THIS_PARAMETER, instance);
248: }
249: if (!queryAtt.enableReferencingAttributes()) {
250: return;
251: }
252: et.getAttributes().stream().filter(a -> query.hasParameter(a.getName())).forEach(a -> {
253: final Object value = EntityPropertiesUtils.getAttributeValue(a, instance);
254: if (value != null && ((!a.isCollection() || !((Collection) value).isEmpty()))) {
255: query.setParameter(a.getName(), value);
256: }
257: });
258: } catch (RuntimeException e) {
259: LOG.error("Unable to set query parameter ${}. Parameter will be skipped.", THIS_PARAMETER, e);
260: }
261: }
262:
263: private static <T> QueryFieldStrategy<? extends AbstractQueryAttribute<? super T, ?>, T> getQueryFieldLoader(
264: EntityType<T> et, QueryAttribute<? super T, ?> queryAttribute) {
265: if (queryAttribute == null) {
266: return null;
267: }
268:
269: if (!queryAttribute.isCollection()) {
270: return new SingularQueryAttributeStrategy<>(et, (AbstractQueryAttribute<? super T, ?>) queryAttribute);
271: } else {
272: return new PluralQueryAttributeStrategy<>(et, (PluralQueryAttributeImpl<? super T, ?, ?>) queryAttribute);
273: }
274: }
275:
276: private <T> void processEmptyAttributes(T entity, EntityType<T> et, LoadStateDescriptor<T> loadStateDescriptor) {
277: et.getFieldSpecifications().stream()
278: .filter(fs -> {
279: final Object value= EntityPropertiesUtils.getFieldValue(fs.getJavaField(), entity);
280: return value == null || (value instanceof Collection<?> && ((Collection<?>) value).isEmpty());
281: })
282: .forEach(fs -> {
283: final FetchType fetchType = fs.getFetchType();
284: final LoadState loadState = loadStateDescriptor.isLoaded(fs);
285: if (fs.isCollection() && (fetchType == FetchType.EAGER || fetchType == FetchType.LAZY && loadState == LoadState.UNKNOWN)) {
286: final CollectionType ct = CollectionFactory.resolveCollectionType(fs.getJavaType());
287: final Object emptyValue = ct == CollectionType.MAP ? CollectionFactory.createDefaultMap() : CollectionFactory.createDefaultCollection(ct);
288: EntityPropertiesUtils.setFieldValue(fs.getJavaField(), entity, emptyValue);
289: loadStateDescriptor.setLoaded(fs, LoadState.LOADED);
290: } else if (fetchType == FetchType.LAZY && loadState == LoadState.UNKNOWN) {
291: loadStateDescriptor.setLoaded(fs, LoadState.LOADED);
292: }
293: });
294: }
295:
296: private <T> void validateIntegrityConstraints(T entity, EntityType<T> et) {
297: if (shouldSkipICValidationOnLoad()) {
298: return;
299: }
300: IntegrityConstraintsValidator.getValidator().validate(entity, et, isNotLazy());
301: }
302:
303: private boolean shouldSkipICValidationOnLoad() {
304: return mapper.getConfiguration().is(JOPAPersistenceProperties.DISABLE_IC_VALIDATION_ON_LOAD);
305: }
306:
307: <T> void validateIntegrityConstraints(T entity, FieldSpecification<? super T, ?> fieldSpec,
308: EntityType<T> et) {
309: if (shouldSkipICValidationOnLoad()) {
310: return;
311: }
312: final Object id = EntityPropertiesUtils.getIdentifier(entity, et);
313: final Object value = EntityPropertiesUtils.getAttributeValue(fieldSpec, entity);
314: IntegrityConstraintsValidator.getValidator().validate(id, fieldSpec, value);
315: }
316:
317: <T> void setFieldValue(T entity, FieldSpecification<? super T, ?> fieldSpec, Collection<Axiom<?>> axioms,
318: EntityType<T> et, Descriptor entityDescriptor) {
319: final FieldStrategy<? extends FieldSpecification<? super T, ?>, T> fs = FieldStrategy
320: .createFieldStrategy(et, fieldSpec, entityDescriptor, mapper);
321: axioms.forEach(fs::addAxiomValue);
322: fs.buildInstanceFieldValue(entity);
323: validateIntegrityConstraints(entity, fieldSpec, et);
324: }
325:
326: <T> void setQueryAttributeFieldValue(T entity, QueryAttribute<? super T, ?> queryAttribute, EntityType<T> et) {
327: final SparqlQueryFactory queryFactory = mapper.getUow().sparqlQueryFactory();
328: populateQueryAttribute(entity, queryAttribute, queryFactory, et);
329: }
330:
331: /**
332: * Parameters for constructing an entity from data.
333: *
334: * @param id Entity identifier
335: * @param entityType Entity type
336: * @param descriptor Entity descriptor, specifies e.g., repository contexts
337: * @param forceEager Whether all attributes have to be loaded eagerly
338: * @param <T> Entity type
339: */
340: record EntityConstructionParameters<T>(URI id, IdentifiableEntityType<T> entityType, Descriptor descriptor,
341: boolean forceEager) {}
342: }