package fr.openwide.core.jpa.junit;
import java.beans.PropertyDescriptor;
import java.math.BigDecimal;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.MapKey;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Attribute.PersistentAttributeType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.commons.lang3.ArrayUtils;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
import org.hibernate.jpa.internal.metamodel.EmbeddableTypeImpl;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
import com.google.common.collect.Lists;
import fr.openwide.core.jpa.business.generic.model.GenericEntity;
import fr.openwide.core.jpa.business.generic.service.IGenericEntityService;
import fr.openwide.core.jpa.exception.SecurityServiceException;
import fr.openwide.core.jpa.exception.ServiceException;
import fr.openwide.core.jpa.util.EntityManagerUtils;
@TestExecutionListeners({
DependencyInjectionTestExecutionListener.class,
EntityManagerExecutionListener.class
})
public abstract class AbstractTestCase {
/**
* Use this instead of SpringJUnit4ClassRunner, so that implementors can choose their own runner
*/
@ClassRule
public static final SpringClassRule SCR = new SpringClassRule();
/**
* Use this instead of SpringJUnit4ClassRunner, so that implementors can choose their own runner
*/
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Autowired
private EntityManagerUtils entityManagerUtils;
protected abstract void cleanAll() throws ServiceException, SecurityServiceException;
protected static <E extends GenericEntity<?, ? super E>> void cleanEntities(IGenericEntityService<?, E> service) throws ServiceException, SecurityServiceException {
for (E entity : service.list()) {
service.delete(entity);
}
}
@Before
public void init() throws ServiceException, SecurityServiceException {
cleanAll();
checkEmptyDatabase();
}
@After
public void close() throws ServiceException, SecurityServiceException {
cleanAll();
checkEmptyDatabase();
}
protected final <T extends GenericEntity<?, ?>> Matcher<T> isAttachedToSession() {
return new TypeSafeMatcher<T>() {
@Override
public void describeTo(Description description) {
description.appendText("an entity already in the session");
}
@Override
protected boolean matchesSafely(T item) {
return entityManagerUtils.getCurrentEntityManager().contains(item);
}
};
}
protected <E extends GenericEntity<Long, E>> void testEntityStringFields(E entity, IGenericEntityService<Long, E> service)
throws ServiceException, SecurityServiceException {
BeanWrapper source = PropertyAccessorFactory.forBeanPropertyAccess(entity);
PropertyDescriptor[] fields = source.getPropertyDescriptors();
// creationDate est la date de création de l'objet donc indépendant entre les deux objets
String[] ignoredFields = { };
for (PropertyDescriptor field : fields) {
String fieldName = field.getName();
if (source.isWritableProperty(fieldName) && String.class.isAssignableFrom(field.getPropertyType())) {
if (!ArrayUtils.contains(ignoredFields, fieldName) && source.isReadableProperty(fieldName)) {
source.setPropertyValue(fieldName, fieldName);
}
}
}
service.create(entity);
for (PropertyDescriptor field : fields) {
String fieldName = field.getName();
if (source.isWritableProperty(fieldName) && String.class.isAssignableFrom(field.getPropertyType())) {
if (!ArrayUtils.contains(ignoredFields, fieldName) && source.isReadableProperty(fieldName)) {
Assert.assertEquals(fieldName, source.getPropertyValue(fieldName));
}
}
}
}
private void checkEmptyDatabase() {
Set<EntityType<?>> entityTypes = getEntityManager().getEntityManagerFactory().getMetamodel().getEntities();
for (EntityType<?> entityType : entityTypes) {
List<?> entities = listEntities(entityType.getBindableJavaType());
if (entities.size() > 0) {
Assert.fail(String.format("Il reste des objets de type %1$s", entities.get(0).getClass().getSimpleName()));
}
}
}
protected <E> List<E> listEntities(Class<E> clazz) {
CriteriaBuilder cb = entityManagerUtils.getEntityManager().getCriteriaBuilder();
CriteriaQuery<E> cq = cb.createQuery(clazz);
cq.from(clazz);
return entityManagerUtils.getEntityManager().createQuery(cq).getResultList();
}
protected <E extends GenericEntity<?, ?>> Long countEntities(Class<E> clazz) {
CriteriaBuilder cb = entityManagerUtils.getEntityManager().getCriteriaBuilder();
CriteriaQuery<Long> cq = cb.createQuery(Long.class);
Root<E> root = cq.from(clazz);
cq.select(cb.count(root));
return (Long) entityManagerUtils.getEntityManager().createQuery(cq).getSingleResult();
}
protected void assertDatesWithinXSeconds(Date date1, Date date2, Integer delayInSeconds) {
Assert.assertTrue(Math.abs(date1.getTime() - date2.getTime()) < delayInSeconds * 1000l);
}
protected EntityManager getEntityManager() {
return entityManagerUtils.getEntityManager();
}
protected void entityManagerOpen() {
entityManagerUtils.openEntityManager();
}
protected void entityManagerClose() {
entityManagerUtils.closeEntityManager();
}
protected void entityManagerReset() {
entityManagerClose();
entityManagerOpen();
}
protected void entityManagerClear() {
entityManagerUtils.getEntityManager().clear();
}
protected void entityManagerDetach(Object object) {
entityManagerUtils.getEntityManager().detach(object);
}
/**
* Méthode utilisée à des fins de tests.
*/
protected void testMetaModel(Attribute<?, ?> attribute, List<Class<?>> classesAutorisees) throws NoSuchFieldException, SecurityException {
testMetaModel(attribute, classesAutorisees, Collections.<Attribute<?, ?>>emptyList());
}
/**
* Méthode utilisée à des fins de tests.
*/
protected void testMetaModel(Attribute<?, ?> attribute, List<Class<?>> classesAutorisees, List<Attribute<?, ?>> ignoredAttributes) throws NoSuchFieldException, SecurityException {
for (Attribute<?, ?> ignoredAttribute : ignoredAttributes) {
if (ignoredAttribute.getJavaMember().equals(attribute.getJavaMember())) {
// champ ignoré
return;
}
}
Enumerated enumerated = attribute.getJavaMember().getDeclaringClass().getDeclaredField(attribute.getName()).getAnnotation(Enumerated.class);
MapKeyEnumerated mapKeyEnumerated = attribute.getJavaMember().getDeclaringClass().getDeclaredField(attribute.getName()).getAnnotation(MapKeyEnumerated.class);
MapKey mapKey = attribute.getJavaMember().getDeclaringClass().getDeclaredField(attribute.getName()).getAnnotation(MapKey.class);
// cas des embeddable et des collectionOfElements d'embeddable
if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.ELEMENT_COLLECTION)
&& EmbeddableTypeImpl.class.isInstance(((PluralAttribute<?, ?, ?>) attribute).getElementType())) {
PluralAttribute<?, ?, ?> pluralAttribute = (PluralAttribute<?, ?, ?>) attribute;
if (classesAutorisees.contains(pluralAttribute.getElementType().getJavaType())) {
// type autorisé de manière explicite
return;
}
for (Attribute<?, ?> embeddedAttribute : ((EmbeddableTypeImpl<?>)pluralAttribute.getElementType()).getAttributes()) {
testMetaModel(embeddedAttribute, classesAutorisees, ignoredAttributes);
}
return;
} else if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.EMBEDDED)) {
SingularAttribute<?, ?> singularAttribute = (SingularAttribute<?, ?>) attribute;
if (classesAutorisees.contains(singularAttribute.getJavaType())) {
// type autorisé de manière explicite
return;
}
if (EmbeddableTypeImpl.class.isInstance(singularAttribute.getType())) {
for (Attribute<?, ?> embeddedAttribute : ((EmbeddableTypeImpl<?>)singularAttribute.getType()).getAttributes()) {
testMetaModel(embeddedAttribute, classesAutorisees, ignoredAttributes);
}
return;
}
}
if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.BASIC)
&& !classesAutorisees.contains(attribute.getJavaType())
&& (enumerated == null || EnumType.ORDINAL.equals(enumerated.value()))) {
throw new IllegalStateException(
"Champ \"" + attribute.getName() + "\", de type " + attribute.getJavaType().getSimpleName() + " refusé");
} else if (attribute.getPersistentAttributeType().equals(PersistentAttributeType.ELEMENT_COLLECTION)
&& PluralAttribute.class.isInstance(attribute)
&& !classesAutorisees.contains(((PluralAttribute<?, ?, ?>) attribute).getElementType().getJavaType())
&& (enumerated == null || EnumType.ORDINAL.equals(enumerated.value()))) {
PluralAttribute<?, ?, ?> pluralAttribute = (PluralAttribute<?, ?, ?>) attribute;
throw new IllegalStateException(
"Collection \"" + attribute.getName() + "\" de "
+ pluralAttribute.getElementType().getJavaType().getSimpleName() + " refusée");
} else if (attribute instanceof MapAttribute) {
MapAttribute<?, ?, ?> mapAttribute = (MapAttribute<?, ?, ?>) attribute;
if (Enum.class.isAssignableFrom(mapAttribute.getKeyJavaType())
&& (mapKeyEnumerated == null || EnumType.ORDINAL.equals(mapKeyEnumerated.value()))
&& mapKey == null /* if @MapKey present, then field format is defined elsewhere and check is useless */) {
throw new IllegalStateException(
"Map \"" + attribute.getName() + "\" de clés ordinales "
+ ((PluralAttribute<?, ?, ?>) attribute).getElementType().getJavaType().getSimpleName() + " refusée");
}
if (Enum.class.isAssignableFrom(mapAttribute.getElementType().getJavaType())
&& (enumerated == null || EnumType.ORDINAL.equals(enumerated.value()))) {
throw new IllegalStateException(
"Map \"" + attribute.getName() + "\" de valeurs ordinales "
+ ((PluralAttribute<?, ?, ?>) attribute).getElementType().getJavaType().getSimpleName() + " refusée");
}
}
}
/**
* Méthode permettant de s'assurer que les attributs des classes marquées @Entity ne seront pas sérialisés en
* "bytea" lors de leur écriture en base.
*
* @param classesAutorisees : concerne uniquement des classes matérialisées. Si une enum fait péter le test, c'est
* qu'il manque l'annotation @Enumerated ou que celle-ci prend EnumType.ORDINAL en paramètre
*/
protected void testMetaModel(List<Attribute<?, ?>> ignoredAttributes, Class<?>... classesAutorisees) throws NoSuchFieldException, SecurityException {
List<Class<?>> listeAutorisee = Lists.newArrayList();
listeAutorisee.add(String.class);
listeAutorisee.add(Long.class);
listeAutorisee.add(Double.class);
listeAutorisee.add(Integer.class);
listeAutorisee.add(Float.class);
listeAutorisee.add(Date.class);
listeAutorisee.add(BigDecimal.class);
listeAutorisee.add(Boolean.class);
listeAutorisee.add(int.class);
listeAutorisee.add(long.class);
listeAutorisee.add(double.class);
listeAutorisee.add(boolean.class);
listeAutorisee.add(float.class);
for (Class<?> clazz : classesAutorisees) {
listeAutorisee.add(clazz);
}
for (EntityType<?> entityType : getEntityManager().getMetamodel().getEntities()) {
for (Attribute<?, ?> attribute : entityType.getDeclaredAttributes()) {
testMetaModel(attribute, listeAutorisee, ignoredAttributes);
}
}
}
protected void testMetaModel(Class<?>... classesAutorisees) throws NoSuchFieldException, SecurityException {
testMetaModel(Collections.<Attribute<?, ?>>emptyList(), classesAutorisees);
}
}