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

org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation Maven / Gradle / Ivy

There is a newer version: 3.3.1
Show newest version
/*
 * 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;
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy