Created
November 1, 2019 10:09
-
-
Save jdmwood/9d97b4a2252e3b2596c67a87d56e7d19 to your computer and use it in GitHub Desktop.
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
package com.yourpackage | |
import java.lang.reflect.Field | |
import java.lang.reflect.Modifier | |
import javax.persistence.* | |
import kotlin.jvm.internal.Reflection | |
import kotlin.reflect.KCallable | |
import org.hibernate.annotations.Subselect | |
import org.junit.Assert.assertFalse | |
import org.junit.Test | |
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider | |
import org.springframework.core.type.filter.AnnotationTypeFilter | |
const val PACKAGE = "com.yourpackage" | |
class CheckKotlinJPAClasses { | |
@Test | |
@Throws(ClassNotFoundException::class, NoSuchFieldException::class) | |
fun testAllKotlinJPAFields() { | |
val sb = StringBuilder() | |
// Find all Entity classes | |
val scanner = ClassPathScanningCandidateComponentProvider(false) | |
scanner.addIncludeFilter(AnnotationTypeFilter(Entity::class.java)) | |
val fail = scanner.findCandidateComponents(PACKAGE) | |
.map { Class.forName(it.beanClassName) } | |
.filter { it.getDeclaredAnnotation(Metadata::class.java) != null } // Ignore non kotlin classes | |
.map { checkForNullableMismatch(sb, it) } | |
.any { it } | |
assertFalse(sb.toString(), fail) | |
} | |
/** | |
* Check that the Kotlin nullness matches the JPA nullness of fields | |
*/ | |
private fun checkForNullableMismatch(sb: StringBuilder, clazz: Class<*>): Boolean { | |
// Ignore views (with @Subselect) | |
if (clazz.getDeclaredAnnotation(Subselect::class.java) != null) { | |
return false | |
} | |
var fail: Boolean = false | |
val kotlinClass = Reflection.createKotlinClass(clazz) | |
val members = kotlinClass.members as Collection<KCallable<*>> | |
for (member in members) { | |
val expectedNullable = member.returnType.isMarkedNullable | |
// For some reason the KClass getAnnotations() doesn't work so use the Java one | |
var declaredField: Field? = null | |
try { | |
declaredField = clazz.getDeclaredField(member.name) | |
} catch (e: NoSuchFieldException) { | |
// It's not a Kotlin property | |
continue | |
} | |
// Ignore special cases where we have transient loaded fields | |
if (Modifier.isTransient(declaredField!!.modifiers) || declaredField.getDeclaredAnnotation(Transient::class.java) != null) { | |
continue | |
} | |
// Ignore embedded | |
if (declaredField.getDeclaredAnnotation(Embedded::class.java) != null) { | |
continue | |
} | |
val declaredAnnotations = declaredField.declaredAnnotations | |
var nullables = 0 | |
var nonNullables = 0 | |
for (annotation in declaredAnnotations) { | |
if (annotation is Basic) { | |
if (annotation.optional) { | |
nullables++ | |
} else { | |
nonNullables++ | |
} | |
} | |
if (annotation is Column) { | |
if (annotation.nullable) { | |
nullables++ | |
} else { | |
nonNullables++ | |
} | |
} | |
if (annotation is ManyToOne) { | |
if (annotation.optional) { | |
nullables++ | |
} else { | |
nonNullables++ | |
} | |
} | |
if (annotation is OneToOne) { | |
if (annotation.optional) { | |
nullables++ | |
} else { | |
nonNullables++ | |
} | |
} | |
if (annotation is Id) { | |
nonNullables++ | |
} | |
} | |
if (declaredAnnotations.isEmpty()) { | |
nullables++ | |
} | |
if (nullables > 0 && nonNullables > 0) { | |
sb.append(String.format("Found conflicting nullables on @Column/@Basic for %s\n", member)) | |
fail = true | |
} | |
val wasNullable = nonNullables == 0 | |
if (expectedNullable != wasNullable) { | |
sb.append( | |
String.format( | |
"Found JPA field [%s] with nullable mismatch. The field was nullable=%b but the JPA annotations were nullable=%b. Check your @Basic/@Column annotations.\n", | |
member, | |
expectedNullable, | |
wasNullable | |
) | |
) | |
fail = true | |
} | |
} | |
return fail | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment