-
Notifications
You must be signed in to change notification settings - Fork 238
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CB-16465 Lazy load safe toString() methods
Added tests for Entity classes to make sure they implement toString() correctly.
- Loading branch information
Showing
95 changed files
with
1,169 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
autoscale/src/test/java/com/sequenceiq/periscope/PeriscopeEntityToStringTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.sequenceiq.periscope; | ||
|
||
import com.sequenceiq.cloudbreak.AbstractEntityToStringTest; | ||
|
||
class PeriscopeEntityToStringTest extends AbstractEntityToStringTest { | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
common/src/test/java/com/sequenceiq/cloudbreak/AbstractEntityToStringTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package com.sequenceiq.cloudbreak; | ||
|
||
import static org.assertj.core.api.SoftAssertions.assertSoftly; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.when; | ||
import static org.mockito.Mockito.withSettings; | ||
|
||
import java.lang.reflect.Field; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Modifier; | ||
import java.util.Map; | ||
import java.util.Set; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import javax.persistence.Entity; | ||
import javax.persistence.FetchType; | ||
import javax.persistence.ManyToOne; | ||
import javax.persistence.OneToMany; | ||
import javax.persistence.OneToOne; | ||
|
||
import org.hibernate.proxy.HibernateProxy; | ||
import org.hibernate.proxy.LazyInitializer; | ||
import org.junit.jupiter.api.Test; | ||
import org.reflections.Reflections; | ||
import org.reflections.scanners.SubTypesScanner; | ||
import org.reflections.scanners.TypeAnnotationsScanner; | ||
import org.springframework.util.ReflectionUtils; | ||
|
||
public abstract class AbstractEntityToStringTest { | ||
|
||
private final Set<Class<?>> entityClasses = getEntityClasses(); | ||
|
||
Set<Class<?>> getEntityClasses() { | ||
return new Reflections(getClass().getPackageName(), new TypeAnnotationsScanner(), new SubTypesScanner()) | ||
.getTypesAnnotatedWith(Entity.class, true) | ||
.stream() | ||
.filter(clazz -> !Modifier.isAbstract(clazz.getModifiers())) | ||
.collect(Collectors.toSet()); | ||
} | ||
|
||
@Test | ||
void testImplementedToString() { | ||
assertSoftly(softly -> | ||
entityClasses.forEach(entityClass -> { | ||
Method toString = ReflectionUtils.findMethod(entityClass, "toString"); | ||
if (Object.class.equals(toString.getDeclaringClass())) { | ||
softly.fail("Class %s does not implement toString() %s", entityClass.getName(), toString.getDeclaringClass()); | ||
} | ||
})); | ||
} | ||
|
||
@Test | ||
void testLazyLoadSafeToString() { | ||
assertSoftly(softly -> | ||
entityClasses.forEach(entityClass -> { | ||
Object entityInstance = createEntityInstance(entityClass); | ||
setLazyLoadedFields(entityClass, entityInstance); | ||
try { | ||
entityInstance.toString(); | ||
} catch (UninitializedToString e) { | ||
softly.fail("Lazy loaded field %s may throw UninitializedException, please wrap in DatabaseUtil.lazyLoadSafeToString()", e.getField()); | ||
} | ||
})); | ||
} | ||
|
||
/** | ||
* Set circular entity references, so if they are referenced in each other's toString() methods, the test will fail with a {@link StackOverflowError} | ||
*/ | ||
@Test | ||
void testCircularEntityReferencesInToString() { | ||
Map<? extends Class<?>, Object> entityInstancesByClass = entityClasses.stream() | ||
.map(this::createEntityInstance) | ||
.collect(Collectors.toMap(Object::getClass, Function.identity())); | ||
|
||
entityInstancesByClass.values().forEach(entity -> Stream.of(entity.getClass().getDeclaredFields()) | ||
.filter(field -> entityInstancesByClass.containsKey(field.getType())) | ||
.forEach(field -> { | ||
ReflectionUtils.makeAccessible(field); | ||
ReflectionUtils.setField(field, entity, entityInstancesByClass.get(field.getType())); | ||
})); | ||
|
||
assertSoftly(softly -> entityInstancesByClass.values() | ||
.forEach(entityInstance -> softly.assertThatCode(entityInstance::toString).doesNotThrowAnyException())); | ||
} | ||
|
||
private Object createEntityInstance(Class<?> entityClass) { | ||
try { | ||
return entityClass.getDeclaredConstructor().newInstance(); | ||
} catch (Exception e) { | ||
throw new RuntimeException("Failed to verify toString() implementation of entity " + entityClass.getName(), e); | ||
} | ||
} | ||
|
||
private void setLazyLoadedFields(Class<?> entityClass, Object newInstance) { | ||
Set<Field> lazyLoadedFields = Stream.of(entityClass.getDeclaredFields()) | ||
.filter(this::isLazyLoaded) | ||
.collect(Collectors.toSet()); | ||
lazyLoadedFields.forEach(field -> { | ||
ReflectionUtils.makeAccessible(field); | ||
ReflectionUtils.setField(field, newInstance, createProxyObject(field.getType(), field)); | ||
}); | ||
} | ||
|
||
private boolean isLazyLoaded(Field field) { | ||
return (field.isAnnotationPresent(OneToMany.class) && FetchType.LAZY.equals(field.getAnnotation(OneToMany.class).fetch())) | ||
|| (field.isAnnotationPresent(OneToOne.class) && FetchType.LAZY.equals(field.getAnnotation(OneToOne.class).fetch())) | ||
|| (field.isAnnotationPresent(ManyToOne.class) && FetchType.LAZY.equals(field.getAnnotation(ManyToOne.class).fetch())); | ||
} | ||
|
||
private <T> T createProxyObject(Class<T> type, Field field) { | ||
LazyInitializer lazyInitializer = mock(LazyInitializer.class); | ||
when(lazyInitializer.isUninitialized()).thenReturn(true); | ||
T hibernateProxy = mock(type, withSettings().extraInterfaces(HibernateProxy.class)); | ||
when(((HibernateProxy) hibernateProxy).getHibernateLazyInitializer()).thenReturn(lazyInitializer); | ||
when(hibernateProxy.toString()).thenThrow(new UninitializedToString(field)); | ||
return hibernateProxy; | ||
} | ||
|
||
static class UninitializedToString extends RuntimeException { | ||
private final Field field; | ||
|
||
UninitializedToString(Field field) { | ||
this.field = field; | ||
} | ||
|
||
public Field getField() { | ||
return field; | ||
} | ||
} | ||
} |
Oops, something went wrong.