/* * Copyright 2011-2017 by the original author(s). * * 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 * * http://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 lombok.NonNull; import lombok.RequiredArgsConstructor; import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.mapping.Alias; import org.springframework.data.mapping.Association; import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.IdentifierAccessor; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PreferredConstructor; import org.springframework.data.mapping.PropertyHandler; import org.springframework.data.mapping.SimpleAssociationHandler; import org.springframework.data.mapping.SimplePropertyHandler; import org.springframework.data.mapping.TargetAwareIdentifierAccessor; import org.springframework.data.util.Lazy; import org.springframework.data.util.TypeInformation; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * Simple value object to capture information of {@link PersistentEntity}s. * * @author Oliver Gierke * @author Jon Brisbin * @author Patryk Wasik * @author Thomas Darimont * @author Christoph Strobl * @author Mark Paluch */ public class BasicPersistentEntity<T, P extends PersistentProperty<P>> implements MutablePersistentEntity<T, P> { private static final Logger LOGGER = LoggerFactory.getLogger(BasicPersistentEntity.class); private static final String TYPE_MISMATCH = "Target bean of type %s is not of type of the persistent entity (%s)!"; private static final String NULL_ASSOCIATION = "%s.addAssociation(…) was called with a null association! Usually indicates a problem in a Spring Data MappingContext implementation. Be sure to file a bug at https://jira.spring.io!"; private final Optional<PreferredConstructor<T, P>> constructor; private final TypeInformation<T> information; private final List<P> properties; private final Optional<Comparator<P>> comparator; private final Set<Association<P>> associations; private final Map<String, P> propertyCache; private final Map<Class<? extends Annotation>, Optional<Annotation>> annotationCache; private Optional<P> idProperty = Optional.empty(); private Optional<P> versionProperty = Optional.empty(); private PersistentPropertyAccessorFactory propertyAccessorFactory; private final Lazy<Alias> typeAlias; /** * Creates a new {@link BasicPersistentEntity} from the given {@link TypeInformation}. * * @param information must not be {@literal null}. */ public BasicPersistentEntity(TypeInformation<T> information) { this(information, Optional.empty()); } /** * Creates a new {@link BasicPersistentEntity} for the given {@link TypeInformation} and {@link Comparator}. The given * {@link Comparator} will be used to define the order of the {@link PersistentProperty} instances added to the * entity. * * @param information must not be {@literal null}. * @param comparator can be {@literal null}. */ public BasicPersistentEntity(TypeInformation<T> information, Optional<Comparator<P>> comparator) { Assert.notNull(information, "Information must not be null!"); this.information = information; this.properties = new ArrayList<>(); this.comparator = comparator; this.constructor = new PreferredConstructorDiscoverer<>(this).getConstructor(); this.associations = comparator.<Set<Association<P>>> map(it -> new TreeSet<>(new AssociationComparator<>(it))) .orElseGet(HashSet::new); this.propertyCache = new HashMap<>(); this.annotationCache = new HashMap<>(); this.propertyAccessorFactory = BeanWrapperPropertyAccessorFactory.INSTANCE; this.typeAlias = Lazy.of(() -> Alias .ofOptional(Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), TypeAlias.class))// .map(TypeAlias::value)// .filter(StringUtils::hasText))); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPersistenceConstructor() */ public Optional<PreferredConstructor<T, P>> getPersistenceConstructor() { return constructor; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isConstructorArgument(org.springframework.data.mapping.PersistentProperty) */ public boolean isConstructorArgument(PersistentProperty<?> property) { return constructor.map(it -> it.isConstructorParameter(property)).orElse(false); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isIdProperty(org.springframework.data.mapping.PersistentProperty) */ public boolean isIdProperty(PersistentProperty<?> property) { return this.idProperty.map(it -> it.equals(property)).orElse(false); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#isVersionProperty(org.springframework.data.mapping.PersistentProperty) */ public boolean isVersionProperty(PersistentProperty<?> property) { return this.versionProperty.map(it -> it.equals(property)).orElse(false); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getName() */ public String getName() { return getType().getName(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getIdProperty() */ public Optional<P> getIdProperty() { return idProperty; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getVersionProperty() */ public Optional<P> getVersionProperty() { return versionProperty; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#hasIdProperty() */ public boolean hasIdProperty() { return idProperty.isPresent(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#hasVersionProperty() */ public boolean hasVersionProperty() { return versionProperty.isPresent(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.MutablePersistentEntity#addPersistentProperty(P) */ public void addPersistentProperty(P property) { Assert.notNull(property, "Property must not be null!"); if (properties.contains(property)) { return; } properties.add(property); if (!propertyCache.containsKey(property.getName())) { propertyCache.put(property.getName(), property); } P candidate = returnPropertyIfBetterIdPropertyCandidateOrNull(property); if (candidate != null) { this.idProperty = Optional.of(candidate); } if (property.isVersionProperty()) { this.versionProperty.ifPresent(it -> { throw new MappingException( String.format("Attempt to add version property %s but already have property %s registered " + "as version. Check your mapping configuration!", property.getField(), it.getField())); }); this.versionProperty = Optional.of(property); } } /** * Returns the given property if it is a better candidate for the id property than the current id property. * * @param property the new id property candidate, will never be {@literal null}. * @return the given id property or {@literal null} if the given property is not an id property. */ protected P returnPropertyIfBetterIdPropertyCandidateOrNull(P property) { if (!property.isIdProperty()) { return null; } this.idProperty.ifPresent(it -> { throw new MappingException(String.format("Attempt to add id property %s but already have property %s registered " + "as id. Check your mapping configuration!", property.getField(), it.getField())); }); return property; } /* * (non-Javadoc) * @see org.springframework.data.mapping.MutablePersistentEntity#addAssociation(org.springframework.data.mapping.model.Association) */ public void addAssociation(Association<P> association) { Assert.notNull(association, "Association must not be null!"); if (!associations.contains(association)) { associations.add(association); } } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.String) */ public Optional<P> getPersistentProperty(String name) { return Optional.ofNullable(propertyCache.get(name)); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperty(java.lang.Class) */ @Override public Optional<P> getPersistentProperty(Class<? extends Annotation> annotationType) { Assert.notNull(annotationType, "Annotation type must not be null!"); Optional<P> property = properties.stream()// .filter(it -> it.isAnnotationPresent(annotationType))// .findAny(); if (property.isPresent()) { return property; } return associations.stream().map(Association::getInverse)// .filter(it -> it.isAnnotationPresent(annotationType)).findAny(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getType() */ public Class<T> getType() { return information.getType(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getTypeAlias() */ public Alias getTypeAlias() { return typeAlias.get(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getTypeInformation() */ public TypeInformation<T> getTypeInformation() { return information; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler) */ public void doWithProperties(PropertyHandler<P> handler) { Assert.notNull(handler, "PropertyHandler must not be null!"); getPersistentProperties().forEach(handler::doWithPersistentProperty); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#doWithProperties(org.springframework.data.mapping.PropertyHandler.Simple) */ @Override public void doWithProperties(SimplePropertyHandler handler) { Assert.notNull(handler, "Handler must not be null!"); getPersistentProperties().forEach(handler::doWithPersistentProperty); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPersistentProperties() */ @Override public Stream<P> getPersistentProperties() { return properties.stream()// .filter(it -> !it.isTransient() && !it.isAssociation()); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.AssociationHandler) */ public void doWithAssociations(AssociationHandler<P> handler) { Assert.notNull(handler, "Handler must not be null!"); for (Association<P> association : associations) { handler.doWithAssociation(association); } } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#doWithAssociations(org.springframework.data.mapping.SimpleAssociationHandler) */ public void doWithAssociations(SimpleAssociationHandler handler) { Assert.notNull(handler, "Handler must not be null!"); for (Association<? extends PersistentProperty<?>> association : associations) { handler.doWithAssociation(association); } } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getAssociations() */ @Override public Stream<Association<P>> getAssociations() { return associations.stream(); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#findAnnotation(java.lang.Class) */ @Override @SuppressWarnings("unchecked") public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType) { return (Optional<A>) annotationCache.computeIfAbsent(annotationType, it -> Optional.ofNullable(AnnotatedElementUtils.findMergedAnnotation(getType(), it))); } /* * (non-Javadoc) * @see org.springframework.data.mapping.MutablePersistentEntity#verify() */ public void verify() { comparator.ifPresent(it -> properties.sort(it)); } /* * (non-Javadoc) * @see org.springframework.data.mapping.model.MutablePersistentEntity#setPersistentPropertyAccessorFactory(org.springframework.data.mapping.model.PersistentPropertyAccessorFactory) */ @Override public void setPersistentPropertyAccessorFactory(PersistentPropertyAccessorFactory factory) { this.propertyAccessorFactory = factory; } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getPropertyAccessor(java.lang.Object) */ @Override public PersistentPropertyAccessor getPropertyAccessor(Object bean) { Assert.notNull(bean, "Target bean must not be null!"); Assert.isTrue(getType().isInstance(bean), () -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName())); return propertyAccessorFactory.getPropertyAccessor(this, bean); } /* * (non-Javadoc) * @see org.springframework.data.mapping.PersistentEntity#getIdentifierAccessor(java.lang.Object) */ @Override public IdentifierAccessor getIdentifierAccessor(Object bean) { Assert.notNull(bean, "Target bean must not be null!"); Assert.isTrue(getType().isInstance(bean), () -> String.format(TYPE_MISMATCH, bean.getClass().getName(), getType().getName())); return hasIdProperty() ? new IdPropertyIdentifierAccessor(this, bean) : new AbsentIdentifierAccessor(bean); } /** * A null-object implementation of {@link IdentifierAccessor} to be able to return an accessor for entities that do * not have an identifier property. * * @author Oliver Gierke */ private static class AbsentIdentifierAccessor extends TargetAwareIdentifierAccessor { public AbsentIdentifierAccessor(Object target) { super(() -> target); } /* * (non-Javadoc) * @see org.springframework.data.mapping.IdentifierAccessor#getIdentifier() */ @Override public Optional<Object> getIdentifier() { return Optional.empty(); } } /** * Simple {@link Comparator} adaptor to delegate ordering to the inverse properties of the association. * * @author Oliver Gierke */ @RequiredArgsConstructor private static final class AssociationComparator<P extends PersistentProperty<P>> implements Comparator<Association<P>>, Serializable { private static final long serialVersionUID = 4508054194886854513L; private final @NonNull Comparator<P> delegate; /* * (non-Javadoc) * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) */ public int compare(Association<P> left, Association<P> right) { return delegate.compare(left.getInverse(), right.getInverse()); } } }