All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.data.mapping.model.AnnotationBasedPersistentProperty Maven / Gradle / Ivy

There is a newer version: 0.0.41
Show newest version
/*
 * Copyright 2011-2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.data.mapping.model;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.data.annotation.AccessType;
import org.springframework.data.annotation.AccessType.Type;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Reference;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.Version;
import org.springframework.data.mapping.Association;
import org.springframework.data.mapping.MappingException;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.mapping.PersistentProperty;
import org.springframework.data.util.Lazy;
import org.springframework.data.util.Optionals;
import org.springframework.data.util.ReflectionUtils;
import org.springframework.data.util.StreamUtils;
import org.springframework.data.util.TypeInformation;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * Special {@link PersistentProperty} that takes annotations at a property into account.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @author Mark Paluch
 */
public abstract class AnnotationBasedPersistentProperty

> extends AbstractPersistentProperty

{ private static final String SPRING_DATA_PACKAGE = "org.springframework.data"; private static final Class IDENTITY_TYPE = loadIdentityType(); private final @Nullable String value; private final Map, Optional> annotationCache = new ConcurrentHashMap<>(); private final Lazy usePropertyAccess = Lazy.of(() -> { AccessType accessType = findPropertyOrOwnerAnnotation(AccessType.class); return accessType != null && Type.PROPERTY.equals(accessType.value()) || super.usePropertyAccess(); }); private final Lazy isTransient = Lazy.of(() -> super.isTransient() || isAnnotationPresent(Transient.class) || isAnnotationPresent(Value.class) || isAnnotationPresent(Autowired.class) || isAnnotationPresent(jakarta.persistence.Transient.class)); private final Lazy isWritable = Lazy .of(() -> !isTransient() && !isAnnotationPresent(ReadOnlyProperty.class)); private final Lazy isReference = Lazy.of(() -> !isTransient() // && (isAnnotationPresent(Reference.class) || super.isAssociation())); private final Lazy isId = Lazy .of(() -> isAnnotationPresent(Id.class) || (IDENTITY_TYPE != null && isAnnotationPresent(IDENTITY_TYPE)) || isAnnotationPresent(jakarta.persistence.Id.class)); private final Lazy isVersion = Lazy.of(() -> isAnnotationPresent(Version.class) || isAnnotationPresent(jakarta.persistence.Version.class)); private final Lazy> associationTargetType = Lazy.of(() -> { if (!isAssociation()) { return null; } return Optional.of(Reference.class) // .map(this::findAnnotation) // .map(Reference::to) // .map(it -> !Class.class.equals(it) ? TypeInformation.of(it) : getActualTypeInformation()) // .orElseGet(() -> super.getAssociationTargetTypeInformation()); }); /** * Creates a new {@link AnnotationBasedPersistentProperty}. * * @param property must not be {@literal null}. * @param owner must not be {@literal null}. */ public AnnotationBasedPersistentProperty(Property property, PersistentEntity owner, SimpleTypeHolder simpleTypeHolder) { super(property, owner, simpleTypeHolder); populateAnnotationCache(property); Value value = findAnnotation(Value.class); this.value = value == null ? null : value.value(); } /** * Populates the annotation cache by eagerly accessing the annotations directly annotated to the accessors (if * available) and the backing field. Annotations override annotations found on field. * * @param property * @throws MappingException in case we find an ambiguous mapping on the accessor methods */ private void populateAnnotationCache(Property property) { Optionals.toStream(property.getGetter(), property.getSetter()).forEach(it -> { for (Annotation annotation : it.getAnnotations()) { Class annotationType = annotation.annotationType(); Annotation mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(it, annotationType); validateAnnotation(mergedAnnotation, "Ambiguous mapping; Annotation %s configured " + "multiple times on accessor methods of property %s in class %s", annotationType.getSimpleName(), getName(), getOwner().getType().getSimpleName()); annotationCache.put(annotationType, Optional.of(mergedAnnotation)); } }); property.getField().ifPresent(it -> { for (Annotation annotation : it.getAnnotations()) { Class annotationType = annotation.annotationType(); Annotation mergedAnnotation = AnnotatedElementUtils.getMergedAnnotation(it, annotationType); validateAnnotation(mergedAnnotation, "Ambiguous mapping; Annotation %s configured " + "on field %s and one of its accessor methods in class %s", annotationType.getSimpleName(), it.getName(), getOwner().getType().getSimpleName()); annotationCache.put(annotationType, Optional.of(mergedAnnotation)); } }); } /** * Verifies the given annotation candidate detected. Will be rejected if it's a Spring Data annotation and we already * found another one with a different configuration setup (i.e. other attribute values). * * @param candidate must not be {@literal null}. * @param message must not be {@literal null}. * @param arguments must not be {@literal null}. */ private void validateAnnotation(Annotation candidate, String message, Object... arguments) { Class annotationType = candidate.annotationType(); if (!annotationType.getName().startsWith(SPRING_DATA_PACKAGE)) { return; } if (annotationCache.containsKey(annotationType) && !annotationCache.get(annotationType).equals(Optional.of(candidate))) { throw new MappingException(String.format(message, arguments)); } } /** * Inspects a potentially available {@link Value} annotation at the property and returns the {@link String} value of * it. * * @see org.springframework.data.mapping.model.AbstractPersistentProperty#getSpelExpression() */ @Nullable @Override public String getSpelExpression() { return value; } /** * Considers plain transient fields, fields annotated with {@link Transient}, {@link Value} or {@link Autowired} as * transient. * * @see org.springframework.data.mapping.PersistentProperty#isTransient() */ @Override public boolean isTransient() { return isTransient.get(); } @Override public boolean isIdProperty() { return isId.get(); } @Override public boolean isVersionProperty() { return isVersion.get(); } /** * Considers the property an {@link Association} if it is annotated with {@link Reference}. */ @Override public boolean isAssociation() { return isReference.get(); } @Override public boolean isWritable() { return isWritable.get(); } /** * Returns the annotation found for the current {@link AnnotationBasedPersistentProperty}. Will prefer getters or * setters annotations over ones found at the backing field as the former can be used to reconfigure the metadata in * subclasses. * * @param annotationType must not be {@literal null}. * @return {@literal null} if annotation type not found on property. */ @Override @Nullable public A findAnnotation(Class annotationType) { Assert.notNull(annotationType, "Annotation type must not be null"); return doFindAnnotation(annotationType).orElse(null); } @SuppressWarnings("unchecked") private Optional doFindAnnotation(Class annotationType) { Optional annotation = annotationCache.get(annotationType); if (annotation != null) { return (Optional) annotation; } return (Optional) annotationCache.computeIfAbsent(annotationType, type -> { return getAccessors() // .map(it -> AnnotatedElementUtils.findMergedAnnotation(it, type)) // .flatMap(StreamUtils::fromNullable) // .findFirst(); }); } @Nullable @Override public A findPropertyOrOwnerAnnotation(Class annotationType) { A annotation = findAnnotation(annotationType); return annotation != null ? annotation : getOwner().findAnnotation(annotationType); } /** * Returns whether the property carries the an annotation of the given type. * * @param annotationType the annotation type to look up. * @return */ @Override public boolean isAnnotationPresent(Class annotationType) { return doFindAnnotation(annotationType).isPresent(); } @Override public boolean usePropertyAccess() { return usePropertyAccess.get(); } @Nullable @Override public TypeInformation getAssociationTargetTypeInformation() { return associationTargetType.getNullable(); } @Override public String toString() { if (annotationCache.isEmpty()) { populateAnnotationCache(getProperty()); } String builder = annotationCache.values().stream() // .flatMap(Optionals::toStream) // .map(Object::toString) // .collect(Collectors.joining(" ")); return builder + super.toString(); } private Stream getAccessors() { return Optionals.toStream(Optional.ofNullable(getGetter()), Optional.ofNullable(getSetter()), Optional.ofNullable(getField())); } /** * Load jMolecules' {@code @Identity} type if present on the classpath. Dedicated method instead of a simple static * initializer to be able to suppress the compiler warning. * * @return can be {@literal null}. */ @Nullable @SuppressWarnings("unchecked") private static Class loadIdentityType() { return (Class) ReflectionUtils.loadIfPresent("org.jmolecules.ddd.annotation.Identity", AbstractPersistentProperty.class.getClassLoader()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy