org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of spring-data-jpa Show documentation
Show all versions of spring-data-jpa Show documentation
Spring Data module for JPA repositories.
/*
* Copyright 2011-2017 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
*
* 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.jpa.repository.support;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.IdClass;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.IdentifiableType;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;
import javax.persistence.metamodel.Type.PersistenceType;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
/**
* Implementation of {@link org.springframework.data.repository.core.EntityInformation} that uses JPA {@link Metamodel}
* to find the domain class' id field.
*
* @author Oliver Gierke
* @author Thomas Darimont
* @author Christoph Strobl
* @author Mark Paluch
*/
public class JpaMetamodelEntityInformation extends JpaEntityInformationSupport {
private final IdMetadata idMetadata;
private final SingularAttribute versionAttribute;
private final Metamodel metamodel;
private final String entityName;
/**
* Creates a new {@link JpaMetamodelEntityInformation} for the given domain class and {@link Metamodel}.
*
* @param domainClass must not be {@literal null}.
* @param metamodel must not be {@literal null}.
*/
public JpaMetamodelEntityInformation(Class domainClass, Metamodel metamodel) {
super(domainClass);
Assert.notNull(metamodel, "Metamodel must not be null!");
this.metamodel = metamodel;
ManagedType type = metamodel.managedType(domainClass);
if (type == null) {
throw new IllegalArgumentException("The given domain class can not be found in the given Metamodel!");
}
this.entityName = type instanceof EntityType ? ((EntityType) type).getName() : null;
if (!(type instanceof IdentifiableType)) {
throw new IllegalArgumentException("The given domain class does not contain an id attribute!");
}
IdentifiableType identifiableType = (IdentifiableType) type;
this.idMetadata = new IdMetadata(identifiableType);
this.versionAttribute = findVersionAttribute(identifiableType, metamodel);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaEntityInformationSupport#getEntityName()
*/
@Override
public String getEntityName() {
return entityName != null ? entityName : super.getEntityName();
}
/**
* Returns the version attribute of the given {@link ManagedType} or {@literal null} if none available.
*
* @param type must not be {@literal null}.
* @param metamodel must not be {@literal null}.
* @return
*/
@SuppressWarnings("unchecked")
private static SingularAttribute findVersionAttribute(IdentifiableType type,
Metamodel metamodel) {
try {
return type.getVersion(Object.class);
} catch (IllegalArgumentException o_O) {
// Needs workarounds as the method is implemented with a strict type check on e.g. Hibernate < 4.3
}
Set> attributes = type.getSingularAttributes();
for (SingularAttribute attribute : attributes) {
if (attribute.isVersion()) {
return attribute;
}
}
Class superType = type.getJavaType().getSuperclass();
try {
ManagedType managedSuperType = metamodel.managedType(superType);
if (!(managedSuperType instanceof IdentifiableType)) {
return null;
}
return (SingularAttribute) findVersionAttribute((IdentifiableType) managedSuperType, metamodel);
} catch (IllegalArgumentException o_O) {
return null;
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.EntityInformation#getId(java.lang.Object)
*/
@SuppressWarnings("unchecked")
public ID getId(T entity) {
BeanWrapper entityWrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
if (idMetadata.hasSimpleId()) {
return (ID) entityWrapper.getPropertyValue(idMetadata.getSimpleIdAttribute().getName());
}
BeanWrapper idWrapper = new IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(idMetadata.getType(), metamodel);
boolean partialIdValueFound = false;
for (SingularAttribute attribute : idMetadata) {
Object propertyValue = entityWrapper.getPropertyValue(attribute.getName());
if (propertyValue != null) {
partialIdValueFound = true;
}
idWrapper.setPropertyValue(attribute.getName(), propertyValue);
}
return (ID) (partialIdValueFound ? idWrapper.getWrappedInstance() : null);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.EntityInformation#getIdType()
*/
@SuppressWarnings("unchecked")
public Class getIdType() {
return (Class) idMetadata.getType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttribute()
*/
public SingularAttribute getIdAttribute() {
return idMetadata.getSimpleIdAttribute();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaEntityInformation#hasCompositeId()
*/
public boolean hasCompositeId() {
return !idMetadata.hasSimpleId();
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getIdAttributeNames()
*/
public Iterable getIdAttributeNames() {
List attributeNames = new ArrayList(idMetadata.attributes.size());
for (SingularAttribute attribute : idMetadata.attributes) {
attributeNames.add(attribute.getName());
}
return attributeNames;
}
/*
* (non-Javadoc)
* @see org.springframework.data.jpa.repository.support.JpaEntityInformation#getCompositeIdAttributeValue(java.io.Serializable, java.lang.String)
*/
public Object getCompositeIdAttributeValue(Serializable id, String idAttribute) {
Assert.isTrue(hasCompositeId(), "Model must have a composite Id!");
return new DirectFieldAccessFallbackBeanWrapper(id).getPropertyValue(idAttribute);
}
/*
* (non-Javadoc)
* @see org.springframework.data.repository.core.support.AbstractEntityInformation#isNew(java.lang.Object)
*/
@Override
public boolean isNew(T entity) {
if (versionAttribute == null || versionAttribute.getJavaType().isPrimitive()) {
return super.isNew(entity);
}
BeanWrapper wrapper = new DirectFieldAccessFallbackBeanWrapper(entity);
Object versionValue = wrapper.getPropertyValue(versionAttribute.getName());
return versionValue == null;
}
/**
* Simple value object to encapsulate id specific metadata.
*
* @author Oliver Gierke
* @author Thomas Darimont
*/
private static class IdMetadata implements Iterable> {
private final IdentifiableType type;
private final Set> attributes;
private Class idType;
@SuppressWarnings("unchecked")
public IdMetadata(IdentifiableType source) {
this.type = source;
this.attributes = (Set>) (source.hasSingleIdAttribute()
? Collections.singleton(source.getId(source.getIdType().getJavaType())) : source.getIdClassAttributes());
}
public boolean hasSimpleId() {
return attributes.size() == 1;
}
public Class getType() {
if (idType != null) {
return idType;
}
// lazy initialization of idType field with tolerable benign data-race
this.idType = tryExtractIdTypeWithFallbackToIdTypeLookup();
return this.idType;
}
private Class tryExtractIdTypeWithFallbackToIdTypeLookup() {
try {
Type idType2 = type.getIdType();
return idType2 == null ? fallbackIdTypeLookup(type) : idType2.getJavaType();
} catch (IllegalStateException e) {
// see https://hibernate.onjira.com/browse/HHH-6951
return fallbackIdTypeLookup(type);
}
}
private static Class fallbackIdTypeLookup(IdentifiableType type) {
IdClass annotation = AnnotationUtils.findAnnotation(type.getJavaType(), IdClass.class);
return annotation == null ? null : annotation.value();
}
public SingularAttribute getSimpleIdAttribute() {
return attributes.iterator().next();
}
/*
* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
public Iterator> iterator() {
return attributes.iterator();
}
}
/**
* Custom extension of {@link DirectFieldAccessFallbackBeanWrapper} that allows to derive the identifier if composite
* keys with complex key attribute types (e.g. types that are annotated with {@code @Entity} themselves) are used.
*
* @author Thomas Darimont
*/
private static class IdentifierDerivingDirectFieldAccessFallbackBeanWrapper
extends DirectFieldAccessFallbackBeanWrapper {
private final Metamodel metamodel;
public IdentifierDerivingDirectFieldAccessFallbackBeanWrapper(Class type, Metamodel metamodel) {
super(type);
this.metamodel = metamodel;
}
/**
* In addition to the functionality described in {@link BeanWrapperImpl} it is checked whether we have a nested
* entity that is part of the id key. If this is the case, we need to derive the identifier of the nested entity.
*
* @see org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.DirectFieldAccessFallbackBeanWrapper#setPropertyValue(java.lang.String,
* java.lang.Object)
*/
@Override
public void setPropertyValue(String propertyName, Object value) {
if (!isIdentifierDerivationNecessary(value)) {
super.setPropertyValue(propertyName, value);
return;
}
// Derive the identifier from the nested entity that is part of the composite key.
@SuppressWarnings({ "rawtypes", "unchecked" })
JpaMetamodelEntityInformation nestedEntityInformation = new JpaMetamodelEntityInformation(value.getClass(),
this.metamodel);
if (!nestedEntityInformation.getJavaType().isAnnotationPresent(IdClass.class)) {
Object nestedIdPropertyValue = new DirectFieldAccessFallbackBeanWrapper(value)
.getPropertyValue(nestedEntityInformation.getIdAttribute().getName());
super.setPropertyValue(propertyName, nestedIdPropertyValue);
return;
}
// We have an IdClass property, we need to inspect the current value in order to map potentially multiple id
// properties correctly.
BeanWrapper sourceIdValueWrapper = new DirectFieldAccessFallbackBeanWrapper(value);
BeanWrapper targetIdClassTypeWrapper = new BeanWrapperImpl(nestedEntityInformation.getIdType());
for (String idAttributeName : (Iterable) nestedEntityInformation.getIdAttributeNames()) {
targetIdClassTypeWrapper.setPropertyValue(idAttributeName,
extractActualIdPropertyValue(sourceIdValueWrapper, idAttributeName));
}
super.setPropertyValue(propertyName, targetIdClassTypeWrapper.getWrappedInstance());
}
private Object extractActualIdPropertyValue(BeanWrapper sourceIdValueWrapper, String idAttributeName) {
Object idPropertyValue = sourceIdValueWrapper.getPropertyValue(idAttributeName);
if (idPropertyValue != null) {
Class idPropertyValueType = idPropertyValue.getClass();
if (ClassUtils.isPrimitiveOrWrapper(idPropertyValueType)) {
return idPropertyValue;
}
return new DirectFieldAccessFallbackBeanWrapper(idPropertyValue)
.getPropertyValue(tryFindSingularIdAttributeNameOrUseFallback(idPropertyValueType, idAttributeName));
}
return null;
}
private String tryFindSingularIdAttributeNameOrUseFallback(Class idPropertyValueType,
String fallbackIdTypePropertyName) {
ManagedType idPropertyType = metamodel.managedType(idPropertyValueType);
for (SingularAttribute sa : idPropertyType.getSingularAttributes()) {
if (sa.isId()) {
return sa.getName();
}
}
return fallbackIdTypePropertyName;
}
/**
* @param value
* @return {@literal true} if the given value is not {@literal null} and a mapped persistable entity otherwise
* {@literal false}
*/
private boolean isIdentifierDerivationNecessary(Object value) {
if (value == null) {
return false;
}
try {
ManagedType managedType = this.metamodel.managedType(value.getClass());
return managedType != null && managedType.getPersistenceType() == PersistenceType.ENTITY;
} catch (IllegalArgumentException iae) {
// no mapped type
return false;
}
}
}
}