kotlin: cannot get validation api's annotation - annotations

==========UPDATE==========
when i change the annotation to #get:NotNull , #get:Min and #get:Max, the hibernate-validator can read these annotations success.
But what i still want to know is:
why the validation-api's annotation, such as #NotNull , #Min and #Max cannot be used on data class's members directly, while JPA's annotations can be????
==========Bellow is the origin question===========
when i tried to use validation-api's annotation on data class, the Validator class (from hibernate-validator) cannot get the annotations, so the validation failed.
i wrote a test case, which include 3 data classes:
1st one use JPA annotation #Column and #Id, which can be read successfully by test case.
2nd one use validation-api annotation #NotEmpty, #Min,#Max on members, these annotation cannot be read by test case
3rd one use validation-api annotation #get:NotEmpty, #get:Min, #get:Max, the test case cannot read these annotation.
The target and retention of #Column , #NotNull, #Min and #Max are all:
RUNTIME and FIELD
So, what happened in the behind? how can i use the validation annotations properly?
here is the test case:
import org.hibernate.validator.constraints.NotEmpty
import org.junit.Test
import javax.persistence.Column
import javax.persistence.Id
import javax.validation.constraints.Max
import javax.validation.constraints.Min
import kotlin.reflect.KFunction
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.javaMethod
class KotlinFeatureTest2 {
#Test
fun test_get_annotation() {
// can get field's annotation for BeanUseJPAAnnotation success
println("Getting field annotations for BeanUseJPAAnnotation :")
BeanUseJPAAnnotation::class.members.forEach {
if (it is KProperty) {
val field = it.javaField
println("${field?.name}'s annotations:")
field?.annotations?.forEachIndexed { i, an ->
println(" $i is: $an")
}
}
}
println("--------------------")
println("Getting field annotations for BeanUseValidationAnnotation :")
// CANT get field's annotation for BeanUseJPAAnnotation success
BeanUseValidationAnnotation::class.members.forEach {
if (it is KProperty) {
val field = it.javaField
println("${field?.name}'s annotations:")
field?.annotations?.forEachIndexed { i, an ->
println(" $i is: $an")
}
}
}
println("--------------------")
println("Getting field annotations for BeanUseValidationAnnotationOnMethod :")
// CANT get field's annotation for BeanUseJPAAnnotation success
BeanUseValidationAnnotationOnMethod::class.members.forEach {
if (it is KFunction) {
val method = it.javaMethod
println("${method?.name}'s annotations: ")
method?.annotations?.forEachIndexed { i, an ->
println(" $i is: $an")
}
}
}
}
}
data class BeanUseJPAAnnotation(
#Column(name = "id") #Id val id: String,
#Column(name = "user_name") val name: String)
data class BeanUseValidationAnnotation(
#NotEmpty(message = "name can not be empty")
val name: String,
#Min(value = 1)
#Max(value = 100)
val age: Int
)
data class BeanUseValidationAnnotationOnMethod(
#get:NotEmpty(message = "name can not be empty")
val name: String,
#get:Min(value = 1)
#get:Max(value = 100)
val age: Int)
and here are the output of this test case:
Getting field annotations for BeanUseJPAAnnotation :
id's annotations:
0 is: #javax.persistence.Column(nullable=true, unique=false, precision=0, name=id, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
1 is: #javax.persistence.Id()
name's annotations:
0 is: #javax.persistence.Column(nullable=true, unique=false, precision=0, name=user_name, length=255, scale=0, updatable=true, columnDefinition=, table=, insertable=true)
--------------------
Getting field annotations for BeanUseValidationAnnotation :
age's annotations:
name's annotations:
--------------------
Getting field annotations for BeanUseValidationAnnotationOnMethod :
component1's annotations:
component2's annotations:
copy's annotations:
equals's annotations:
hashCode's annotations:
toString's annotations:

The following is the signature part of javax.persistence.Column:
#Target({METHOD, FIELD})
#Retention(RUNTIME)
public #interface Column {
On the contrary here's the same part of javax.validation.constraints.Min:
#Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
#Retention(RUNTIME)
#Repeatable(List.class)
#Documented
#Constraint(validatedBy = { })
public #interface Min {
As you can see the JPA Persistence annotations target METHOD and FIELD hence Kotlin emits them on a FIELD level. However the validators API annotations target more constructs, the PARAMETER in particular. Given that when generating annotations for constructor declared properties Kotlin compiler chooses to annotate parameters only. The BeanUseValidationAnnotation constructor signature equivalent in Java would look like:
public BeanUseValidationAnnotation(#NotEmpty(message = "name can not be empty") #NotNull String name, #Min(1L) #Max(100L) int age) {
This behavior is stated in the documentation:
If you don't specify a use-site target, the target is chosen according
to the #Target annotation of the annotation being used. If there are
multiple applicable targets, the first applicable target from the
following list is used:
param
property
field

Related

OSGi annotations (Activate, Reference, Component) in Scala

I am trying to write an OSGi service in Scala (most other services/bundles are written in Java) and I struggle a bit with the syntax.
Normally in Java one can use the #Activate annotation on a constructor like this:
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
#Component(configurationPid = "PID", service=AgentService.class, immediate=true)
public class AgentServiceImpl implements AgentService {
#Activate
public AgentServiceImpl(#Reference Service1 service1, #Reference Service2 service2) {
// ...
}
In Scala it should look somewhat like this:
import org.osgi.service.component.annotations.{Activate, Component, Deactivate, Reference}
#Component(
configurationPid = "PID",
service = Array(classOf[AgentService]),
immediate = true)
class AgentServiceImpl #Activate() (#Reference service1: Service1,
#Reference service2: Service2) implements AgentService {
// ...
}
When I try compiling this Scala code (with gradle) I geht the following error message:
error : In component xxx.xxxx.xx.xx.agent.AgentServiceImpl , multiple references with the same name: service1. Previous def: xxx.xxxx.xx.xx.service.Service1, this def:
error : In component xxx.xxxx.xx.xx.agent.AgentServiceImpl , multiple references with the same name: service2. Previous def: xxx.xxxx.xx.xx.service.Service2, this def:
Is this happening because my syntax concerning the annotations is wrong?
I am particularly not too sure about this #Activate() bit. In Java I don't need to use brackets here - but it does not compile without in Scala.
Does anyone know a sample project trying to do something similar?
I found the solution:
Compilation succeeds after adding val before the constructor paramters:
import org.osgi.service.component.annotations.{Activate, Component, Deactivate, Reference}
#Component(
configurationPid = "PID",
service = Array(classOf[AgentService]),
immediate = true)
class AgentServiceImpl #Activate() (#Reference val service1: Service1,
#Reference val service2: Service2) implements AgentService {
// ...
}
Probably this is because OSGi cannot properly deal with the automatically generated setter methods of service1 and service2.

Corda: InstantiationException when extending FungibleState in custom Schema

This issue is similar to the one posted here: Corda: error=org.hibernate.InstantiationException: No default constructor for entity
I am getting the same error for a custom schema extending FungibleState (as described in the API Vault Query documentation):
object CustomSchemaV1 : MappedSchema(schemaFamily = CustomSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCustomState::class.java))
{
#Entity
#Table(name = "custom_states", indexes = arrayOf(Index(name = "custom_field_idx", columnList = "custom_field")))
class PersistentCustomState(
/** Custom attributes */
#Column(name = "custom_field")
var customField: String? = null,
/** FungibleState parent attributes */
#Transient
val _participants: Set<AbstractParty>,
#Transient
val _owner: AbstractParty,
#Transient
val _quantity: Long,
#Transient
val _issuerParty: AbstractParty,
#Transient
val _issuerRef: OpaqueBytes
) : CommonSchemaV1.FungibleState(_participants?.toMutableSet(), _owner, _quantity, _issuerParty, _issuerRef.bytes)}
Example schema found here: https://github.com/corda/corda/blob/master/finance/src/test/kotlin/net/corda/finance/schemas/SampleCashSchemaV2.kt
I have the kotlin-jpa plugin installed. Making all fields nullable seems to solve the issue for schemas extending PersistentState, but is not an option here because of the FungibleState parent field data types.
Corda release version = 2.0.0
You need to add a default constructor to the body of PersistentCustomState. Something like:
constructor() : this(*first default value*, *second default value*, etc.)
The difficulty will be in passing default values for the AbstractParty parameters. You can use something like:
AnonymousParty(generateKeyPair().public)

Defining indices independently for same field in child and parent documents in different collections

I have two classes that look like this:
#Document(collection = 'rule')
class Rule {
#Indexed(unique = true)
String name
}
#Document(collection = 'archived_rule')
class ArchivedRule extends Rule {
#Indexed(unique = false)
String name
}
Rules are the primary domain class that my application works with. Only the latest version of each Rule is stored in the 'rule' collection. When a Rule is updated, a copy of it is made and is saved in the 'archived_rule' collection.
The name field should be unique in the 'rule' collection. It should be able to have duplicates in the 'archived_rule' collection.
Defining my classes as I have above does not seem to work. When I start my application, I get an exception like this:
Caused by: org.springframework.data.mapping.model.MappingException: Ambiguous field mapping detected! Both #org.springframework.data.mongodb.core.index.Indexed(expireAfterSeconds=-1, dropDups=false, sparse=false, useGeneratedName=false, background=false, unique=true, name=, collection=, direction=ASCENDING) private java.lang.String ...Rule.name and #org.springframework.data.mongodb.core.index.Indexed(expireAfterSeconds=-1, dropDups=false, sparse=false, useGeneratedName=false, background=false, unique=false, name=, collection=, direction=ASCENDING) private java.lang.String ...ArchivedRule.name map to the same field name name! Disambiguate using #Field annotation!
I have also tried not specifying the name field at all in the ArchivedRule class, but in that case it creates a unique index on the 'name' field in the 'archived_rule' collection.
I have thought that I could make Rule and ArchivedRule unrelated by inheritance and then explicitly re-define all the fields I need to save from Rule in ArchivedRule. I would like to avoid having to do that, though.
Is there some other way that I can specify my classes so that Rule.name has a unique index and ArchivedRule.name does not have a unique index?
I was able to solve this by adding an abstract base class with the shared fields other than name that both Rule and ArchivedRule extend from. They each then define their own version of name with the appropriate index configuration.
class RuleBase {
String sharedField
}
#Document(collection = 'rule')
class Rule extends RuleBase {
#Indexed(unique = true)
String name
}
#Document(collection = 'archived_rule')
class ArchivedRule extends RuleBase {
#Indexed(unique = false)
String name
}

Usage of #field annotations

I was perusing the spray-swagger library and came across this piece of code:
#ApiModel(description = "A pet object")
case class Pet(
#(ApiModelProperty #field)(value = "unique identifier for the pet")
val id: Int,
#(ApiModelProperty #field)(value = "The name of the pet")
val name: String)
I went to my trusty copy of Programming In Scala 3ed to find out what syntax of using the #field annotation within another annotation (re: #(ApiModelProperty #field)) but I came up short. I cracked open the #ApiModelProperty code and found:
/**
* Adds and manipulates data of a model property.
*/
#Target({ElementType.METHOD, ElementType.FIELD})
#Retention(RetentionPolicy.RUNTIME)
#Inherited
public #interface ApiModelProperty {
Question: Are we providing the compiler clues as to what context the #ApiModelProperty annotation applies in order to satisfy its #Target annotation?
You can find the answer in the documentation for scala.annotation.target package.
The #field meta-annotation is used to tell the compiler that the actual field named id should be considered the target of the annotation, as opposed to the automatically generated accesssor/mutator methods:
id():Int and id_=(i: Int):().

kotlin data class + bean validation jsr 303

I'm trying to get Kotlin working with jsr 303 validation on a spring-data-rest project.
Given the following data class declarartion :
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#Size(min=5, max=15)
val name: String
)
The #Size annotation has no effect here, making me able to save a user with a name of 1 character.
It works well when executing the very same example but in a Java class instead of Kotlin.
This makes me think of a Kotlin problem.
Thanks in advance for you help !
You need to use Annotation use-site targets since the default for a property declared in the constructor is to target the annotation on the constructor parameter instead of the getter (which will be seen by JavaBeans compliant hosts) when there are multiple options available. Also using a data class might be inappropriate here (see note at end).
#Entity data class User(
#Id
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO)
var id: Long? = null,
#get:Size(min=5, max=15) // added annotation use-site target here
val name: String
)
The property target from the Kotlin docs may look tempting, but it can only be seen from Kotlin and not Java. Usually get does the trick, and it is not needed on the bean set.
The docs describe the process as:
If you don’t specify a use-site target, the target is chosen according to the #Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used:
param
property
field
And the #Size annotation is:
#Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER})
Therefore since PARAMETER is a valid target, and multiple targets are available (parameter, field, method [get/set]) it choses PARAMETER which is not what you want. Therefore for a JavaBean host to see the property it will look for the getter (properties are defined by the getter/setter and not the backing field).
In one of the Java samples, it shows:
public class Book {
private String title;
private String description;
// ...
#NotEmpty(groups={FirstLevelCheck.class, Default.class})
#Size(max=30)
public String getTitle() {
return title;
}
// ...
}
Which matches our usage of having it on the getter. If it were to be on the field like some of the validation annotations show, see the field use-site target. Or if the field must also be publicly accessible, see the #JvmField annotation in Kotlin.
NOTE: As mentioned in notes from others, you should likely consider NOT using a data class for entities if they use an auto-generated ID since it will not exist for new objects the same as for retrieved objects; and a data class will generate equals and hashCode to include all fields including the ones it should not. You can read guidance about this from the Hibernate docs.
Use the #get or #field targets for validation annotations. Annotations with the target #param(first default) and #property are not supported.
e.g:
From #NotEmpty To #field:NotEmpty
data class Student(
#field:NotEmpty #field:Size(min= 2, message = "Invalid field") var name: String? = ""
)
GL
Jayson Minard
Annotation use site targets