Scala Generics - Overloaded method - spring

Considering the given code:
val repository =
context.getBean(
Introspector.decapitalize(t.getClass.getSimpleName).replace("C", "E").concat("Repository"))
and that my repositories have a String as Serializable.
I'm trying to do the following:
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t))
This one works fine:
repository.asInstanceOf[ElasticsearchRepository[_, String]].findAll()
But I don't know how to put that above to work.
Assuming the method getObject(t) is retuning the correct object to be persisted and since it's a Spring Data Repository, there are 2 save method. One that accept a single entity and another for a list of entities and it says overloaded method value save.
What I have tried so far:
I saw in another thread to force the method with a type, something like this:
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t) : TYPE)
This is ok if I knew the type and also my method getObject should return that same type.
Here is my getObject method which I return the object itself without any specific type:
#throws[IOException]
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t))
}
So I was trying to get the type like this:
val m = Manifest.classType(getClazz(t))
type TYPE = m.type
Looks good if I force my object to this type using getObject(t) : TYPE but I don't know how to use this same type in my getObject method to be returned.
Anyway, I don't even know if this is the best approach to do this, invoking a generic repository and save a generic object.
Just to understand what I'm trying to do, I'm using a aspect to intercept a Cassandra entity to be persisted, then get it and turn into a ElasticSearch entity to save a json(thats why the getObject(t)) and replicate into ElasticSearch.
Here is the full aspect class:
#Component
#Aspect
class ElasticAop {
#Autowired val context : ApplicationContext = null
val objectMapper : ObjectMapper = new ObjectMapper()
#Pointcut("execution(* com.test.service.cassandra.*.post(..)) && args(t)")
def getPointcutPost[T](t : T) : Unit = {}
#throws[Throwable]
#Before("getPointcutPost(t)")
def elasticSaveAspect[T](joinPoint: JoinPoint, t: T) = {
val m = Manifest.classType(getClazz(t))
type TYPE = m.type
val repository =
context.getBean(
Introspector.decapitalize(t.getClass.getSimpleName).replace("C", "E").concat("Repository"))
repository.asInstanceOf[ElasticsearchRepository[_, String]].findAll()
repository.asInstanceOf[ElasticsearchRepository[_, String]].save(getObject(t))
}
#throws[ClassNotFoundException]
def getClazz[T](t : T) = {
val className = t.getClass.getName.replace("cassandra", "elastic").replace("C", "E")
Class.forName(className)
}
#throws[IOException]
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t))
}
}
EDITED
Even setting up a type return in my getObject to Address and then setting the save method as follow save(getObject(t) : Address) give me the same overloaded error.
EDITED
I just figured out it's a limitation and a possible work around is to create a factory or something like this.
Then I created a service with a saveOrUpdate method:
trait ElasticGenericService[T <: ElasticGenericKey, R <: ElasticsearchRepository[T, String]] {
var r : R = _
def saveOrUpdate(t: T) = r.save(t)
}
and now I'm getting a cast exception:
java.lang.ClassCastException: Address cannot be cast to scala.runtime.Nothing$

What i can see here:
getObject[T](t : T) returns existential type _1 and actually kills all type checks, as you choosing the class in runtime
ElasticsearchRepository[_, String].save require existential type _2 to be passed to the save method, so _1 doesn't fit
Possible solution:
repository.asInstanceOf[ElasticsearchRepository[Any, String]].save(getObject(t).asInstanceOf[Any]) //getClass will work with runtime class instead of Any, so should be fine
Another solution (saving existential type):
def getObject[T](t : T) = {
objectMapper.readValue(objectMapper.writeValueAsString(t), getClazz(t)).asInstanceOf[T]
} //assuming T is an existential - it will return same existential as you passed

Related

Moshi: PolymorphicJsonAdapterFactory is it possible to get the type in withDefaultValue?

I have a moshi PolymorphicJsonAdapterFactory and it works great.
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
.withDefaultValue(//how to grab the label)
The method withDefaultValue is a great catch all, but my BE team wants me to log the actual label that comes down in order to help catch a bug that's going on on their end. As far as I can tell... in the withDefaultValue I can't grab a reference to the label which in this case the backend is sending back "Medium".
I feel like there must be a way to grab this label (but I'm missing something simple?) so I can log it and possibly propagate it in the withDefaultValue method.
I stumbled on the issue a while ago. I found it impossible to achieve with just using .withDefaultValue method. So far I did not find better solution other than .withFallbackJsonAdapter (I am using moshi version 1.12), which lets you parse the json manually in case the label is unknown to your PolymorphicJsonAdapterFactory adapter. The documentation says:
/**
* Returns a new factory that with default to {#code fallbackJsonAdapter.fromJson(reader)} upon
* decoding of unrecognized labels.
*
* <p>The {#link JsonReader} instance will not be automatically consumed, so make sure to consume
* it within your implementation of {#link JsonAdapter#fromJson(JsonReader)}
*/
public PolymorphicJsonAdapterFactory<T> withFallbackJsonAdapter(
#Nullable JsonAdapter<Object> fallbackJsonAdapter) {
return ...
}
I assume your code is somewhat like this (simplified):
interface Weather {
val type: String
}
#JsonClass(generateAdapter = true)
class ColdWeather( #Json(name = "type") override val type: String) : Weather
#JsonClass(generateAdapter = true)
class HotWeather( #Json(name = "type") override val type: String) : Weather
val weatherAdapter = PolymorphicJsonAdapterFactory.of(Weather::class.java, "type")
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
and you receive a json similar to this:
{
"weather" : {
"type" : "Cold"
}
}
To receive an unknown label, I would do something like this:
class UnknownWeather(override val type: String) : Weather
val weatherAdapter = PolymorphicJsonAdapterFactory.of(Weather::class.java, "type")
.withSubtype(ColdWeather::class.java, "Cold")
.withSubtype(HotWeather::class.java, "Hot")
.withFallbackJsonAdapter((object : JsonAdapter<Any>() {
override fun fromJson(reader: JsonReader): UnknownWeather {
var type = ... // parse it from the reader
return UnknownWeather(type)
}
override fun toJson(writer: JsonWriter, value: Any?) {
// nothing to do
}
}))
Of course that means that you will have to dig a bit into JsonReader, but it has a fairly understandable interface, you basically iterate through the properties of the json object and extract what you need, in our case just the "type" property.
FYI, seems like more people had problem with this: https://github.com/square/moshi/issues/784

Spring cache for specific values #Cacheable annotation

I want to cache a result of a method only when the attribute of the result contains specific values. For example
Class APIOutput(code: Int, message: String)
sealed class Response<out T : Any> : Serializable {
data class Success<out T : Any>(val data: T) : Response<T>()
data class Error(val errorText: String, val errorCode: Int) : Response<Nothing>()
}
#Cacheable(
key = "api-key",
unless = "do something here"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
In the above method, I want to cache the response only when Response.Success.data.code == (long list of codes).
Please note, in the previous line data is nothing but APIOutput object. How could I achieve it using unless or any other approach. I was thinking of writing a function that takes a doApicall method result as input and would return true or false and call that method it as unless="call a method". But I'm not sure how to do it. Any help is highly appreciated.
You can specify an expression to be evaluated in unless using SpEL. The returned value is available as result so you can do something like -
#Cacheable(
key = "api-key",
unless = "#result!=null or #result.success.data.code!=200"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}
You can even use Regex in SpEL and can create custom Expression parsers if the existing functionality is not enough for your usecase.
Thanks Yatharth and John! Below is the condition that worked for me. resultcodes in the below expression is a list
#Cacheable(
key = "api-key",
unless = "!(#result instanceof T(com.abc.Response\$Success))
or (#result instanceof T(com.abc.Response\$Success)
and !(T(com.abc.APIStatus).resultCodes.contains(#result.data.code)))"
)
fun doApicall(uniqueId: Long): Response<APIOutput> {
//make API call
val output = callAPI(uniqueId)
return Response.Success(output)
}

Kotlin: Specify input-constraints in interface

Lets say I have the following interface:
interface MathThing {
fun mathFunction(x : Int)
}
Let's say the constraint I want to put onto this function is that x cannot be negative.
How can I make sure that every time this (or any other arbitrary) condition isn't met on a object of type MathThing, a (custom) exception is thrown?
One way is to use a wrapper class for your function parameters. You can make an extension function so it's a little easier to pass values to the function.
data class NonNegative(val value: Int) {
init{ if (value < 0) throw IllegalArgumentException("Input must not be negative.") }
}
fun Int.nonNegative() = NonNegative(this)
interface MathThing {
fun mathFunction(x : NonNegative)
}

Create instance of SpringĀ“s ParameterizedTypeReference in Kotlin

I am trying to learn Kotlin, and test how it works with spring boot. My application is using a mongo database to store data and I have a Jersey resource for retrieving data. I am testing it using spring-boot-test and RestTestTemplate.
The RestTestTemplate has an exchange method which takes a ParameterizedTypeReference. This class has a protected constructor. So the only way I managed to use it from Kotlin was like this:
class ListOfPeople : ParameterizedTypeReference<List<Person>>()
Here is my test-method:
#Test
fun `get list of people`() {
// create testdata
datastore.save(Person(firstname = "test1", lastname = "lastname1"))
datastore.save(Person(firstname = "test2", lastname = "lastname2"))
datastore.save(Person(firstname = "test3", lastname = "lastname2"))
datastore.save(Person(firstname = "test4", lastname = "lastname2"))
val requestEntity = RequestEntity<Any>(HttpMethod.GET, URI.create("/person"))
// create typereference for response de-serialization
class ListOfPeople : ParameterizedTypeReference<List<Person>>() // can this be done inline in the exchange method?
val responseEntity : ResponseEntity<List<Person>> = restTemplate.exchange(requestEntity, ListOfPeople())
assertNotNull(responseEntity)
assertEquals(200, responseEntity.statusCodeValue)
assertTrue( responseEntity.body.size >= 4 )
responseEntity.body.forEach { person ->
println("Found person: [${person.firstname} ${person.lastname}] " +
", born [${person.birthdate}]")
}
}
Is this the correct (or only) way to do this, or is there a better way?
If it helps, here is a link for the whole test: testclass on github
While the answer using object expression is correct and the direct equivalent of the way you do it in Java, reified type parameters allow you to simplify it if you need many ParameterizedTypeReferences:
inline fun <reified T> typeReference() = object : ParameterizedTypeReference<T>() {}
// called as
restTemplate.exchange(requestEntity, typeReference<List<Person>>())
When the compiler sees a typeReference<SomeType> call, it's replaced by the definition, so the result is the same as if you wrote object : ParameterizedTypeReference<SomeType>() {}.
Thanks to JB Nizet who pointed me to the correct documentation.
val responseEntity : ResponseEntity<List<Person>> =
restTemplate.exchange(requestEntity,
object: ParameterizedTypeReference<List<Person>> () {})
If I read correctly this is called an Object expression.

Returning object of type specified in method arguments instead of AnyRef

I have the following method:
#org.springframework.stereotype.Service
class EntityCacheManager {
def get(cacheId: String, entityClass: Class[_]): AnyRef = { ... }
//...
}
So to use it, i have to write this:
val cachedEntity = entityCacheManager.get(cacheId, classOf[SomeEntity]).asInstanceOf[SomeEntity]
Is there some way to make EntityCacheManager.get() returning instance of type entityClass which is specified in method params? I'd like to avoid casting asInstanceOf every time i use this method. I know it would be nice to use generic definition of type EntityCacheManager, but it's also a spring-managed bean, so i think using generics will cause troubles.
You can use a more idiomatic scala approach by using the ClassTag typeclass
class EntityCacheManager {
def get[T: ClassTag](cacheId: String): T = {
val entityClass = implicitly[ClassTag[T]].runtimeClass
val myObject: T = ??? // you retrieve your object somehow using entityClass
myObject
}
}
you can now use it like this:
val myEntityClassInstance = get[MyEntityClass]("key")

Resources