xtend method implementation with Annotations for the parameter - xtend

How can I implement this method with Xtend?
private void myListenerMethod(#Observes(notifyObserver=IF_EXISTS) #MyAnnotation Boolean value)
{
... Do somehting ...
}
I have here an example for a normal method implementation:
var method_myListenerMethod = toMethod("myListenerMethod", newTypeRef(void), [
'''
... Do something ...
'''
])
method_myListenerMethod.parameters += toParameter("value",newTypeRef('''javax.enterprise.event.Observes'''))
members += method_doSaveOperation

I'm not sure what your methods toMethod and toParameter mean, but I would do generation of your method declaration as follows:
private def addMethod(MutableFieldDeclaration field, extension TransformationContext context) {
field.declaringType.addMethod("myListenerMethod") [ methodDecl |
methodDecl.returnType = newTypeReference(typeof(void))
val param = methodDecl.addParameter("value", newTypeReference(typeof(Boolean)))
val observes = param.addAnnotation(context.findTypeGlobally("javax.enterprise.event.Observes"))
val reception = context.findTypeGlobally("javax.enterprise.event.Reception") as EnumerationTypeDeclaration
observes.setEnumValue("notifyObserver", reception.findDeclaredValue("IF_EXISTS"))
param.addAnnotation(context.findTypeGlobally("MyAnnotation"))
]
}
This requires at least Xtend 2.5.0 (see bug #403789)

Related

How do you register the KotlinModule to the AWS lambda Jackson Object Mapper?

I'm using Kotlin to write an AWS Lambda. I have a Kotlin data class
class MessageObject(
val id: String,
val name: String,
val otherId: String
)
This data class is used as the input to the required interface implementation
class Handler : RequestHandler<MessageObject, Output> {
...
override fun handleRequest(msg: MessageObject, ctx: Context) {
...
}
}
When I test this lambda in the aws console, and pass it a proper JSON message, I get this:
An error occurred during JSON parsing: java.lang.RuntimeException
java.lang.RuntimeException: An error occurred during JSON parsing
Caused by: java.io.UncheckedIOException:
com.amazonaws.lambda.thirdparty.com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Cannot construct instance of 'com.mycode.MessageObject'(no Creators, like default construct, exist):
cannot deserialize from Object value (no delegate- or property-based Creator)
I'm almost certain this is fixed by saying:
ObjectMapper().registerModule(KotlinModule())
but in the world of AWS Lambda how do I edit the object mapper provided by AWS?
If you haven't gotten it to work with KotlinModule, since the problem you're having is that Jackson requires a default empty constructor and you currently don't have one. You could just change your MessageObject as follows and it should work:
data class MessageObject(
var id: String = "",
var name: String = "",
var otherId: String = ""
)
I created this repo with a fully functional kotlin lambda template using the Serverless Framework. Have a look for some other tidbits you might need: https://github.com/crafton/sls-aws-lambda-kotlin-gradlekt
You cannot use data class with provided RequestHandler<I, O> unfortunately, because you need register the kotlin module for your jackson mapper in order to work with data classes. But you can write you own RequestHandler, which will like this one.
Here's the code:
interface MyRequestStreamHandler<I : Any, O : Any?> : RequestStreamHandler {
val inputType: Class<I>
fun handleRequest(input: I, context: Context): O?
override fun handleRequest(inputStream: InputStream, outputStream: OutputStream, context: Context) {
handleRequest(inputStream.readJson(inputType), context).writeJsonNullable(outputStream)
}
interface MessageObjectRequestHandler : MyRequestStreamHandler< MessageObject, Output> {
override val inputType: Class<MessageObject >
get() = MessageObject::class.java
}
}
And jackson util:
private val objectMapper = jacksonObjectMapper()
.configure(JsonParser.Feature.ALLOW_COMMENTS, true)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.registerKotlinModule()
private val writer: ObjectWriter = objectMapper.writer()
fun <T : Any> readJson(clazz: Class<T>, stream: InputStream): T =
objectMapper.readValue(stream, clazz)
fun <T : Any> InputStream.readJson(clazz: Class<T>): T =
readJson(clazz, this)
fun Any?.writeJsonNullable(outputStream: OutputStream) {
if (this != null) writer.writeValue(outputStream, this)
}
Now, you can keep your MessageObject class to be data class, and your handler will look something like:
class LambdaMain : MessageObjectRequestHandler {
override fun handleRequest(input: MessageObject, context: Context): Output {
//...
}
}

Distinguish null from missing properties in Jackson using Kotlin data classes

I want to use Jackson to deserialize and later serialize jsons using Kotlin's data classes. It's important that I maintain the distinction between an explicitly null property, and a property which was omitted in the original json.
I have a large domain model (50+ classes) built almost entirely out of Kotlin data classes. Kotlin's data classes provide a lot of useful functionalities that I need to use elsewhere in my program, and for that reason I'd like to keep them instead of converting my models.
I've currently got this code working, but only for Java classes or using Kotlin properties declared in the body of the Kotlin class, and not working for properties declared in the constructor. For Kotlin's data class utility functions to work, all properties must be declared in the constructor.
Here's my object mapper setup code:
val objectMapper = ObjectMapper()
objectMapper.registerModule(KotlinModule())
objectMapper.registerModule(Jdk8Module())
objectMapper.setSerializationInclusion(JsonInclude.Include.ALWAYS)
objectMapper.configOverride(Optional::class.java).includeAsProperty =
JsonInclude.Value.construct(JsonInclude.Include.NON_NULL, null)
objectMapper.configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, true)
objectMapper.configure(JsonGenerator.Feature.WRITE_BIGDECIMAL_AS_PLAIN, true)
objectMapper.configure(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS, true)
objectMapper.nodeFactory = JsonNodeFactory.withExactBigDecimals(true)
Here are my test classes:
TestClass1.java
public class TestClass1 {
public TestClass1() {}
public TestClass1(int intVal, Optional<Double> optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
public Integer intVal;
public Optional<Double> optDblVal;
}
TestClasses.kt
data class TestClass2(val intVal: Int?, val optDblVal: Optional<Double>?)
class TestClass3(val intVal: Int?, val optDblVal: Optional<Double>?)
class TestClass4 {
val intVal: Int? = null
val optDblVal: Optional<Double>? = null
}
and here are my tests:
JsonReserializationTests.kt
#Test
fun `Test 1 - Explicit null Double reserialized as explicit null`() {
val inputJson = """
{
"intVal" : 7,
"optDblVal" : null
}
""".trimIndent()
val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
val actualJson = handler.objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(intermediateObject)
.replace("\r", "")
assertEquals(inputJson, actualJson)
}
#Test
fun `Test 2 - Missing Double not reserialized`() {
val inputJson = """
{
"intVal" : 7
}
""".trimIndent()
val intermediateObject = handler.objectMapper.readValue(inputJson, TestClassN::class.java)
val actualJson = handler.objectMapper
.writerWithDefaultPrettyPrinter()
.writeValueAsString(intermediateObject)
.replace("\r", "")
assertEquals(inputJson, actualJson)
}
Test Results for each class
Let's talk about TestClass2.
If you convert Kotlin code to Java Code, you can find the reason.
Intellij offers a converting tool for Kotlin. You can find it from the menu Tools -> Kotlin -> Show Kotlin Bytecode.
Here is a Java code from the TestClass2 Kotlin code.
public final class TestClass2 {
#Nullable
private final Integer intVal;
#Nullable
private final Optional optDblVal;
#Nullable
public final Integer getIntVal() {
return this.intVal;
}
#Nullable
public final Optional getOptDblVal() {
return this.optDblVal;
}
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
#Nullable
public final Integer component1() {
return this.intVal;
}
#Nullable
public final Optional component2() {
return this.optDblVal;
}
#NotNull
public final TestClass2 copy(#Nullable Integer intVal, #Nullable Optional optDblVal) {
return new TestClass2(intVal, optDblVal);
}
// $FF: synthetic method
public static TestClass2 copy$default(TestClass2 var0, Integer var1, Optional var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.intVal;
}
if ((var3 & 2) != 0) {
var2 = var0.optDblVal;
}
return var0.copy(var1, var2);
}
#NotNull
public String toString() {
return "TestClass2(intVal=" + this.intVal + ", optDblVal=" + this.optDblVal + ")";
}
public int hashCode() {
Integer var10000 = this.intVal;
int var1 = (var10000 != null ? var10000.hashCode() : 0) * 31;
Optional var10001 = this.optDblVal;
return var1 + (var10001 != null ? var10001.hashCode() : 0);
}
public boolean equals(#Nullable Object var1) {
if (this != var1) {
if (var1 instanceof TestClass2) {
TestClass2 var2 = (TestClass2)var1;
if (Intrinsics.areEqual(this.intVal, var2.intVal) && Intrinsics.areEqual(this.optDblVal, var2.optDblVal)) {
return true;
}
}
return false;
} else {
return true;
}
}
The original code is too long, so here is the constructor only.
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
Since Jackson library cannot create an instance without parameters because there is no non-parameter constructor, it will try to create a new instance with some parameters. For test case 2, JSON has only one parameter so that it will look for a one-parameter constructor, but there is no so that it will throw an exception. This is also why test case 1 is passed.
Therefore, what you have to do is that you have to give all default values to all the parameters of data class to make a non-parameter constructor like the below code.
data class TestClass2(val intVal: Int? = null, val optDblVal: Optional<Double>? = null)
Then, if you see in Java code, the class will have a non-parameter constructor.
public TestClass2(#Nullable Integer intVal, #Nullable Optional optDblVal) {
this.intVal = intVal;
this.optDblVal = optDblVal;
}
// $FF: synthetic method
public TestClass2(Integer var1, Optional var2, int var3, DefaultConstructorMarker var4)
{
if ((var3 & 1) != 0) {
var1 = (Integer)null;
}
if ((var3 & 2) != 0) {
var2 = (Optional)null;
}
this(var1, var2);
}
public TestClass2() {
this((Integer)null, (Optional)null, 3, (DefaultConstructorMarker)null);
}
So, if you still want to use Kotlin data class, you have to give default values to all the variables.
data class Example(
val greeting: Optional<String>? = null
)
This allows you to distinguish all three cases in the JSON:
non-null value ({"greeting":"hello"} → greeting.isPresent() == true)
null value ({"greeting":null} → greeting.isPresent() == false)
not present ({ } → greeting == null)
This is just a concise summary of what #Pemassi offered, with the key insight being to make a default null assignment to the nullable Optional<T> member.
Note that the semantics of .isPresent() is potentially confusing to a casual observer, since it does not refer to the presence of a value in the JSON.
A full unit test demonstration is here.
I just found the Kotiln Plugin that makes no-argument constructor for data class automatically. This should help you without much editing. However, this is not a good design pattern, so I still recommend giving default value to all members.
Here is a link for the Kotlin NoArg Plugin
https://kotlinlang.org/docs/reference/compiler-plugins.html#no-arg-compiler-plugin

(de)serializing kotlin delegate properties with jackson

How can I (de)serialize kotlin delegate properties with jackson.
I have a class like this
class MyClass {
var a: Int = 42
set(value) {
val changed = field != value
field = value
if (changed) notifyListeners()
}
... and a dozen other properties that all follow this pattern ...
}
I wanted to simplify that by using
class MyClass {
var a: Int by NotifyUiOnChange(42)
...
private inner class NotifyUiOnChange<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
notifyUiListeners()
}
}
}
but then Jackson will ignore that property.
How can I tell Jackson to serialize and deserialize that property anyway?
And how do I then apply #JsonIgnore annotations (or something comparable)?
You must use outdated version on Jackson (or maybe a version for Java, not Kotlin?). I've checked this using "com.fasterxml.jackson.module:jackson-module-kotlin:2.10.+" (resolved to 2.10.1).
I've declared two classes:
class MyClass {
var a: Int = 42
set(value) {
val changed = field != value
field = value
if (changed) notifyListener(field)
}
private fun notifyListener(field: Any?) {
println("changed: $field")
}
}
class MyDelegatedClass {
var a: Int by NotifyUi(42)
private inner class NotifyUi<T>(initialValue: T) : ObservableProperty<T>(initialValue) {
override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
notifyListener(newValue)
}
}
private fun notifyListener(field: Any?) {
println("changed: $field")
}
}
My main function:
fun main() {
val noDelegate = MyClass()
val delegated = MyDelegatedClass()
val mapper = ObjectMapper().registerKotlinModule()
// Deserialization
val noDelegateValue = mapper.writeValueAsString(noDelegate)
val delegatedValue = mapper.writeValueAsString(delegated)
println("No delegate:\t$noDelegateValue")
println("With delegate\t$delegatedValue")
// Serialization
val noDelegateObject = mapper.readValue<MyClass>("{\"a\":42}".trimIndent())
val delegateObject = mapper.readValue<MyDelegatedClass>("{\"a\":42}".trimIndent())
}
Output:
No delegate: {"a":42}
With delegate {"a":42}
changed: 42
We even can see output on delegate when we use delegate property :) (I believe it's a side-effect that should be consider as bug actually)
So, handling delegates is out of the box feature in jackson (I am not sure since when, but I used lazy delegate with jackson in older project I used to participate and there was no problems with delegates).
How to ignore delegated property?
So, you cannot apply JsonIgnore annotation to delegated field, because you will get This annotation is not applicable to target 'member property with delegate'. But, you can define the scope that annotation should be applied. Example below:
class MyDelegateClass {
#get:JsonIgnore // or set:
val a: Int by NotifyUi(42)
}
Unfortunately, seems that it's kind of broken, because you can use get: or set: and it's not apply to getter or setter only, but for both.

Adding no arg constructor to Scala enumerations

I have the following Scala enum:
object RunMode extends Enumeration {
val CLIENT_MODE = Value("CLIENT")
val SERVER_MODE = Value("SERVER")
}
I have some JSON that my app takes in as input for example:
{
"version" : "0.1",
"runMode" : "CLIENT"
}
Here the JSON field "runMode" is really my RunMode enum, and its values will always be either "CLIENT" or "SERVER". I am trying to use GSON to deserialize this JSON into an AppConfig instance:
class AppConfig(version : String, runMode : RunMode) {
def version() : String = { this.version }
def runMode() : RunMode.Value = { this.runMode }
}
I have the following GSON code:
val gson = new Gson()
val text = Source.fromFile(jsonConfigFile).mkString
gson.fromJson(text, classOf[AppConfig])
When this runs:
java.lang.RuntimeException: Unable to invoke no-args constructor for class scala.Enumeration$Value. Register an InstanceCreator with Gson for this type may fix this problem.
> Buildiat com.google.gson.internal.ConstructorConstructor$14.construct(ConstructorConstructor.java:226)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:210)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:129)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:220)
at com.google.gson.Gson.fromJson(Gson.java:887)
<rest of stacktrace omitted for brevity>
So clearly, GSON expects RunMode to have a no-arg constructor, and it doesn't, and so its unable to deserialize my JSON file at runtime.
I've tried a million different combos but can't quite seem to find the magical constructor definition. So I ask: How can I add a no-arg constructor to RunMode so that GSON can deserialize it into an AppConfig instance?
This doesn't directly answer why using Gson fails, but offers an alternative. Here is an example using argonaut:
RunMode enum definition:
object RunMode extends Enumeration {
type RunMode = Value
val CLIENT_MODE = Value("CLIENT")
val SERVER_MODE = Value("SERVER")
implicit def runModeCodec: CodecJson[RunMode.RunMode] = CodecJson({
case CLIENT_MODE => "CLIENT".asJson
case SERVER_MODE => "SERVER".asJson
}, c => c.focus.string match {
case Some("CLIENT") => DecodeResult.ok(CLIENT_MODE)
case Some("SERVER") => DecodeResult.ok(SERVER_MODE)
case _ => DecodeResult.fail("Could not decode RunMode", c.history)
})
}
Definition of Foo (matching the object you want to create):
case class Foo(version: String, runMode: RunMode)
object Foo {
implicit def codec: CodecJson[Foo] =
casecodec2(Foo.apply, Foo.unapply)("version", "runMode")
}
And now the decoding/encoding example:
object ArgonautEnumCodec {
def main(args: Array[String]): Unit = {
val res: String = Foo("0.1", RunMode.CLIENT_MODE).asJson.toString
println(res)
val foo: Foo = res.decodeOption[Foo].get
println(foo)
}
}
Yields:
{"version":"0.1","runMode":"CLIENT"}
Foo(0.1,CLIENT)
Since I'm not a Scala guy, but have some Gson background, peeking some insights into how Scala works was fun to me. The reason of why you're getting the exception is that Gson cannot instantiate an abstract class scala.Enumeration.Value. The AutoConfig class content is pretty much like the following class in vanilla Java:
final class AppConfig {
final String version;
// This is where ig gets failed
final scala.Enumeration.Value runMode;
AppConfig(final String version, final scala.Enumeration.Value runMode) {
this.version = version;
this.runMode = runMode;
}
}
As far as I understand how Scala enumerations are implemented, unlike Java enumerations, they do not have their type per se, and every Scala enumeration value seem to be an instance of scala.Enumeration$Val giving not enough "host" enumeration type information from its type (however instances seem to have their outer class references). That's why custom implementing custom type adapter is not that simple and requires some inspection on the real enum type (not sure how it can be implemented, though).
Gson provides a special annotation #JsonAdapter that can annotate a certain field including a type adapter to be applied. So the AppConfig.runMode from the class above can be annotated like:
#JsonAdapter(RunModeEnumTypeAdapter.class)
final scala.Enumeration.Value runMode;
Note that it has some hint on the target type in its name. This is because there's probably no other way to specify the target enumeration type. Now, how a generic scala.Enumeration type adapter can be implemented.
// E - a special generic type bound to associate a Scala enumeration with
// So any Scala enumeration can be processed with this type adapter
abstract class AbstractScalaEnumTypeAdapter<E extends scala.Enumeration>
extends TypeAdapter<scala.Enumeration.Value> {
private final E enumeration;
protected AbstractScalaEnumTypeAdapter(final E enumeration) {
this.enumeration = enumeration;
}
#Override
#SuppressWarnings("resource")
public final void write(final JsonWriter out, final scala.Enumeration.Value value)
throws IOException {
// If the given value is null, null must be written to the writer (however it depends on a particular Gson instance configuration)
if ( value == null ) {
out.nullValue();
} else {
// Does Scala provide something like java.lang.Enumeration#name?
out.value(value.toString());
}
}
#Override
public final scala.Enumeration.Value read(final JsonReader in)
throws IOException {
final JsonToken token = in.peek();
switch ( token ) {
case NULL:
// Consume the `null` JSON token
in.nextNull();
return null;
case STRING:
// Consume a JSON string value and lookup an appropriate Scala enumeration value by its name
final String rawValue = in.nextString();
return enumeration.withName(rawValue);
// These case labels are matter of style and cover the rest of possible Gson JSON tokens, and are not really necessary
case BEGIN_ARRAY:
case END_ARRAY:
case BEGIN_OBJECT:
case END_OBJECT:
case NAME:
case NUMBER:
case BOOLEAN:
case END_DOCUMENT:
throw new MalformedJsonException("Unexpected token: " + token);
// Something else? Must never happen
default:
throw new AssertionError(token);
}
}
}
Now, RunMode can be bound to the type adapter above:
final class RunModeEnumTypeAdapter
extends AbstractScalaEnumTypeAdapter<RunMode$> {
// Gson can instantiate this itself
private RunModeEnumTypeAdapter() {
// This is how it looks like from the Java perspective
// And this is the "hint" I was talking about above
super(RunMode$.MODULE$);
}
}
Example of use:
final Gson gson = new Gson();
final AppConfig appConfig = gson.fromJson("{\"version\":\"0.1\",\"runMode\":\"CLIENT\"}", AppConfig.class);
System.out.println(appConfig.version);
System.out.println(appConfig.runMode);
System.out.println(gson.toJson(appConfig));
Output:
0.1
CLIENT
{"version":"0.1","runMode":"CLIENT"}
Probably not that nice and compact as Scala can do, but I hope the code above can be translated to Scala with no issues.

How to exclude null properties in json using groovy

Using JSONOutput.toJSON to convert objects to json representation in groovy, is there a way to exclude null values to compact the JSON ?
Understand it can be done using jackson, but currently using groovy JSonOutput.
Current Implementation :
Java Domain
class User {
String firstName;
String lastName;
}
Groovy Script
def user = new User()
user.firstName="abc"
def mapUser = ["userkey",user]
JsonOutput.toJson(mapUser)
Actual Output
{"userkey":{"firstName":abc,"lastName":null}}
Expected Output
{"userkey":{"firstName":abc}}
You also could use JsonGenerator, but it appeared since 2.5.0:
def user = new User()
user.firstName="abc"
def mapUser = ["userkey",user]
def generator = new JsonGenerator.Options()
.excludeNulls()
.build()
generator.toJson(mapUser)
and you'll get wat you want
You could manually create a map from the objects' properties and serialize that:
def "exclude null values"() {
given:
def user = new User(firstName: 'abc')
def mapUser = [ "userkey":
user.properties
.findAll { it.key != 'class' && it.value }
]
expect:
JsonOutput.toJson(mapUser) == /{"userkey":{"firstName":"abc"}}/
}
(spock test)