ReactiveMongo : How to write macros handler to Enumeration object? - scala-2.10

I use ReactiveMongo 0.10.0, and I have following user case class and gender Enumeration object:
case class User(
_id: Option[BSONObjectID] = None,
name: String,
gender: Option[Gender.Gender] = None)
object Gender extends Enumeration {
type Gender = Value
val MALE = Value("male")
val FEMALE = Value("female")
val BOTH = Value("both")
}
And I declare two implicit macros handler:
implicit val genderHandler = Macros.handler[Gender.Gender]
implicit val userHandler = Macros.handler[User]
but, when I run application, I get following error:
Error:(123, 48) No apply function found for reactive.userservice.Gender.Gender
implicit val genderHandler = Macros.handler[Gender.Gender]
^
Error:(125, 46) Implicit reactive.userservice.Gender.Gender for 'value gender' not found
implicit val userHandler = Macros.handler[User]
^
Anybody know how to write macros handler to Enumeration object?
Thanks in advance!

I stumbled upon your question a few times searching for the same answer. I did it this way:
import myproject.utils.EnumUtils
import play.api.libs.json.{Reads, Writes}
import reactivemongo.bson._
object DBExecutionStatus extends Enumeration {
type DBExecutionStatus = Value
val Error = Value("Error")
val Started = Value("Success")
val Created = Value("Running")
implicit val enumReads: Reads[DBExecutionStatus] = EnumUtils.enumReads(DBExecutionStatus)
implicit def enumWrites: Writes[DBExecutionStatus] = EnumUtils.enumWrites
implicit object BSONEnumHandler extends BSONHandler[BSONString, DBExecutionStatus] {
def read(doc: BSONString) = DBExecutionStatus.Value(doc.value)
def write(stats: DBExecutionStatus) = BSON.write(stats.toString)
}
}
You have to create a read/write pair by hand and populate with your values.
Hope you already solved this issue given the question age :D

Related

How to use Lucene's DistinctValuesCollector?

My objective is to collect distinct values of select fields to provided them as filter options for the frontend. DistinctValuesCollector seems to be the tool for this, however since I haven't found code sample and documentation except for the Javadocs I can't currently correctly construct this collector. Can anyone provide an example?
This is my attempt which doesn't deliver the desired distinct values of the field PROJEKTSTATUS.name.
val groupSelector = TermGroupSelector(PROJEKTSTATUS.name)
val searchGroup = SearchGroup<BytesRef>()
val valueSelector = TermGroupSelector(PROJEKTSTATUS.name)
val groups = mutableListOf(searchGroup)
val distinctValuesCollector = DistinctValuesCollector(groupSelector, groups, valueSelector)
That field is indexed as follows:
document.add(TextField(PROJEKTSTATUS.name, aggregat.projektstatus, YES))
document.add(SortedDocValuesField(PROJEKTSTATUS.name, BytesRef(aggregat.projektstatus)))
Thanks to #andrewJames's hint to a test class I could figure it out:
fun IndexSearcher.collectFilterOptions(query: Query, field: String, topNGroups: Int = 128, mapper: Function<String?, String?> = Function { it }): Set<String?> {
val firstPassGroupingCollector = FirstPassGroupingCollector(TermGroupSelector(field), Sort(), topNGroups)
search(query, firstPassGroupingCollector)
val topGroups = firstPassGroupingCollector.getTopGroups(0)
val groupSelector = firstPassGroupingCollector.groupSelector
val distinctValuesCollector = DistinctValuesCollector(groupSelector, topGroups, groupSelector)
search(query, distinctValuesCollector)
return distinctValuesCollector.groups.map { mapper.apply(it.groupValue.utf8ToString()) }.toSet()
}

How to deserialize JSON from Kafka Consumer Record

I'm looking to access some fields on a Kafka Consumer record. I'm able to receive the event data which is a Java object i.e ConsumerRecord(topic = test.topic, partition = 0, leaderEpoch = 0, offset = 0, CreateTime = 1660933724665, serialized key size = 32, serialized value size = 394, headers = RecordHeaders(headers = [], isReadOnly = false), key = db166cbf1e9e438ab4eae15093f89c34, value = {"eventInfo":...}).
I'm able to access the eventInfo values which comes back as a json string. I'm fairly new to Kotlin and using Kafka so I'm not entirely sure if this is correct but I'm looking to basically access the fields in value but I can't get rid of an error that appears when trying to use mapper.readValue which is:
None of the following functions can be called with the arguments supplied.
import com.afterpay.shop.favorites.model.Product
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.apache.avro.generic.GenericData.Record
import org.apache.kafka.clients.consumer.ConsumerRecord
import org.springframework.kafka.annotation.KafkaListener
import org.springframework.kafka.support.Acknowledgment
import org.springframework.stereotype.Component
#Component
class KafkaConsumer {
#KafkaListener(topics = ["test.topic"], groupId = "group-id")
fun consume(consumerRecord: ConsumerRecord<String, Any>, ack: Acknowledgment) {
val mapper = jacksonObjectMapper()
val value = consumerRecord.value()
val record = mapper.readValue(value, Product::class.java)
println(value)
ack.acknowledge()
}
}
Is this the correct way to accomplish this?
First, change ConsumerRecord<String, Any> to ConsumerRecord<String, Product>, then change value.deserializer in your consumer config/factory to use JSONDeserializer
Then your consumerRecord.value() will already be a Product instance, and you don't need an ObjectMapper
https://docs.spring.io/spring-kafka/docs/current/reference/html/#json-serde
Otherwise, if you use StringDeserializer, change Any to String so that the mapper.readValue argument types are correct.

kotlin with fastjson parse object error: default constructor not found

I am trying use fastjson parse object in Kotlin code. but exception happened when I use JSON.parseObject, here are detail:
My data class:
import com.alibaba.fastjson.JSONObject
data class StatesMessage #JvmOverloads constructor(val command: String =
"states", var states: States = States()) {
fun toJsonString(): String {
return JSONObject.toJSONString(this)
}
data class States(var x: Double = 0.0, var y: Double = 0.0)
}
Then I try to get object from string:
val state = JSON.parseObject(s, StatesMessage::class.java)
But exception throw from fastjson:
Caused by: com.alibaba.fastjson.JSONException: default constructor not found.
class com.example.demo.StatesMessage
at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:475)
at com.alibaba.fastjson.util.JavaBeanInfo.build(JavaBeanInfo.java:221)
at com.alibaba.fastjson.parser.ParserConfig.createJavaBeanDeserializer(ParserConfig.java:670)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:587)
at com.alibaba.fastjson.parser.ParserConfig.getDeserializer(ParserConfig.java:398)
at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:665)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:365)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:269)
at com.alibaba.fastjson.JSON.parseObject(JSON.java:488)
at com.example.demo.StartupRunner.run(StartupRunner.kt:25)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:813)
... 5 more
all code refer to https://github.com/forest-yang/koltinjson
I think it's a fastjson (till 1.2.54) bug.
When I change to gson, it's work.
/* it will throw exception
val state = JSON.parseObject(s, StatesMessage::class.java)
*/
val state = Gson().fromJson(s, StatesMessage::class.java)
logger.info(state.states.x)
logger.info(state.states.y)

Get or Insert within a Transaction on Doobie in Scala

I'm reading through the Doobie documentation and trying to do a simple get or create within a transaction. I get an option off the first query and attempt to do a getOrElse and run an insert within the else, however I keep getting a value map is not a member of Any within the getOrElse call. What's the correct way to either get an existing or create a new row in instances and return that result in a transaction?
import doobie._
import doobie.implicits._
import cats._
import cats.effect._
import cats.implicits._
import org.joda.time.DateTime
import scala.concurrent.ExecutionContext
case class Instance(id : Int, hostname : String)
case class User(id : Int, instanceId: Int, username : String, email : String, created : DateTime)
class Database(dbUrl : String, dbUser: String, dbPass: String) {
implicit val cs = IO.contextShift(ExecutionContext.global)
val xa = Transactor.fromDriverManager[IO](
"org.postgresql.Driver", dbUrl, dbUser, dbPass
)
def getOrCreateInstance(hostname: String) = for {
existingInstance <- sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option
ensuredInstance <- existingInstance.getOrElse(sql"INSERT INTO instances(hostname) VALUES(?)".update.withGeneratedKeys[Instance]("id", "hostname"))
} yield ensuredInstance
}
I got the following answer thanks to the people on the #scala/freenode chatroom. I'm posting it here for completeness and if people are interested in doing this without the for comprehension in the other answer.
def getOrCreateInstance(hostname: String): ConnectionIO[Instance] =
OptionT(sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option)
.getOrElseF(sql"INSERT INTO instances(hostname) VALUES($hostname)".update.withGeneratedKeys[Instance]("id", "hostname").compile.lastOrError)
I believe something like this should work for you,
def getOrCreateInstance(hostname: String): ConnectionIO[Instance] = for {
existingInstance <- sql"SELECT id, hostname FROM instances i WHERE i.hostname = $hostname".query[Instance].option
ensuredInstance <- existingInstance.fold(sql"INSERT INTO instances(hostname) VALUES($hostname)".update.withGeneratedKeys[Instance]("id", "hostname").take(1).compile.lastOrError)(_.pure[ConnectionIO])
} yield ensuredInstance
where you are compiling the fs2 Stream and also lifting the existing instance into a ConnectionIO in the case that it does already exist.

Fixing Some Kotlin Syntax

I just finish built a simple android music app with Java, then I convert the java files to Kotlin with Kotlin plugin for Android Studio.
There are some error, in MainActivity.kt
private fun display() {
val mySongs = findSong(Environment.getExternalStorageDirectory())
items = arrayOf(mySongs.size.toString())
for (i in mySongs.indices) {
items[i] = mySongs[i].name.toString().replace(".mp3", "").replace(".wav", "")
}
val adp = ArrayAdapter(this, android.R.layout.simple_list_item_1, items!!)
listView!!.adapter = adp
listView!!.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, position, l -> startActivity(Intent(applicationContext, PlayerActivity::class.java).putExtra("pos", position).putExtra("songs", mySongs)) }
}
this line : items[i] = mySongs[i].name.toString().replace(".mp3","").replace(".wav", "")
showing an error: Smart cast to 'Array' is a mutable property that could have been changed by this time.
and on the PlayerActivity.kt
val i = intent
val b = i.extras
mySongs = b.getParcelableArrayList<Parcelable>(mySongs.toString())
position = b.getInt("pos", 0)
val u = Uri.parse(mySongs!![position].toString())
mediaPlayer = MediaPlayer.create(applicationContext, u)
the b.getParcelableArrayList<Parcelable>(mySongs.toString()) has a problem says Type mismatch.
Anyone can help me fix this? Thank You
This line
items = arrayOf(mySongs.size.toString())
creates an array of 1 element containing a string with the size of my songs. ex: ["23"]
You could use this instead: arrayOfNulls(mySongs.size)
For the other question:
mySongs = b.getParcelableArrayList<Parcelable>(mySongs.toString())
should return the same type as (I assume that it was the same code than in main activity because they are in 2 differents files and the context of mySongs is not provided)
val mySongs = findSong(Environment.getExternalStorageDirectory())
mySongs will have be the same type as the result of findSong.
Also you should use var instead of val because val are immutable