Quarkus Panache MongoDB: Serialize ObjectId as String - mongodb

I'm following the tutorials https://quarkus.io/guides/rest-data-panache and https://quarkus.io/guides/mongodb-panache to implement a simple MongoDB entity and resource with Quarkus Panache MongoDB.
Here's what I have so far:
#MongoEntity(collection = "guests")
class GuestEntity(
var id: ObjectId? = null,
var name: String? = null
)
#ApplicationScoped
class GuestRepository: PanacheMongoRepository<GuestEntity>
interface GuestResource: PanacheMongoRepositoryResource<GuestRepository, GuestEntity, ObjectId>
When running this, I can create a document by calling
POST localhost:8080/guest
Content-Type: application/json
{
"name": "Foo"
}
The response contains the created entity
{
"id": {
"timestamp": 1618306409,
"date": 1618306409000
},
"name": "Foo"
}
Notice, how the id field is an object whereas I would like it to be a string.

It turns out that the application was using quarkus-resteasy instead of quarkus-resteasy-jackson.
Once the proper dependency was in place, everything worked as expected

To serialize the id field as a String, apply the following annotation to the id field:
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import io.quarkus.mongodb.panache.jackson.ObjectIdSerializer
#MongoEntity(collection = "guests")
class GuestEntity(
// important: apply the annotation to the field
#field:JsonSerialize(using = ObjectIdSerializer::class)
var id: ObjectId? = null,
var name: String
)
Now the response is
{
"id": "607567590ced4472ce95be23",
"name": "Foo"
}

Related

How to get String representation of ObjectId from response body?

I am trying to create a simple API which works with MongoDB documents created from this class:
#Document()
data class Student(#Id val id: ObjectId?,
val firstName: String,
val secondName: String) {}
And I have a REST controller which returns me Student documents.
{
"id": {
"timestamp": 1657005140,
"date": "2022-07-05T07:12:20.000+00:00"
},
"firstName": "Test",
"secondName": "Test"
}
But I also need a controller which returns me documents by id. How can I put this JSON id with timestamp and date in a request param like /getByName?id= ? Maybe there is a way to get an ID in one-string representation?
The problem is that my ObjectId was serialised.
To get simple one-string value I needed just to add: #JsonSerialize(using = ToStringSerializer::class)
So my document looks like this:
#Document()
data class Student(
#Id
#JsonSerialize(using = ToStringSerializer::class)
val id: ObjectId?,
val firstName: String,
val secondName: String) {}

AppSync returns attribute values as null from AuroraDB

I am trying to implement Appsync with Aurora RDS. Get query returns response with all the attribute values as null. I think it is able to connect with the DB properly because I have seen some error when I deliberately misspelled the table name. I am not sure where the problem is.
I tried the same implementation with Dynamodb and it worked fine. Is this some problem with resolver or something related to permissions?
Response looks like this:
Response:
{
"data": {
"getTest": {
"id": null,
"name": null,
"surname": null
}
}
}
DB table description with:
id int(11)
name text
surname text
AppSync GraphQL schema is:
type Query {
getTest(id: ID!): Test
}
type Test {
id: ID
name: String
surname: String
}
schema {
query: Query
}
Request resolver:
{
"version": "2018-05-29",
"statements": [
"select * from TestTable where id = '$ctx.args.id'"
]
}
Response resolver:
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
I was able to fix my issue. The problem was with referencing the result in response resolver. It worked after adding another [0] to the result object.
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0][0])

Find entities containing a sublist of elements from an embedded array in MongoDB with Spring Data MongoDB

I have a MongoDB entity that has a list of tags:
#Document
#TypeAlias("project")
data class Project(#Id var id: String?,
val name: String,
val tags: MutableList<Tag>)
I would like to look for Project's containing a subset of the tags.
For example given these proyects:
projectService.save(Project(null, "someProject11",
mutableListOf(Tag("area","IT"), Tag("department","Architecture"))))
projectService.save(Project(null, "someProject12",
mutableListOf(Tag("area","IT"), Tag("department","HR"))))
If I define my repository as follows:
interface ProjectRepository : ReactiveCrudRepository<Project, String> {
fun findByTags(tags : List<Tag>): Flux<Project>
}
Looking for a subset doesn't work, e.g: projects with Tag("area","IT") should return two results but actually returns 0. The underlying mongo query is:
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"}]}
It only works passing the complete content of the list listOf(Tag("area","IT"), Tag("department","Architecture")):
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"},{"key": "department", "value": "Architecture"}]}
How can I query for entities containing a subset of the list?
Solved using Criteria and the $elemMatch operator:
interface CustomProjectRepository {
fun findByTags(tags: List<Tag>): Flux<Project>
}
class CustomProjectRepositoryImpl(private val mongoTemplate: ReactiveMongoTemplate) : CustomProjectRepository {
override fun findByTags(tags: List<Tag>): Flux<Project> {
val query = Query()
val criterias = mutableListOf<Criteria>()
tags.forEach {
criterias.add(Criteria.where("tags").elemMatch(Criteria.where("key").`is`(it.key).and("value").`is`(it.value)))
}
query.addCriteria(Criteria().andOperator(* criterias.toTypedArray()))
return mongoTemplate.find(query, Project::class.java)
}
}
interface ProjectRepository : ReactiveCrudRepository<Project, String>, CustomProjectRepository {
fun findByClientIdAndId(clientId: String, id: String): Mono<Project>
fun findByClientId(clientId: String): Flux<Project>
}

Property named "type" on embedded records with RESTSerializer in Ember Data - Error no model was found

I use Ember Data 1.13.4.
I have a model with some embedded records, let's say they look like this:
var Partner = DS.Model.extend({
name: DS.attr(),
addresses: DS.hasMany('address', { async: false } ),
});
var Address = DS.Model.extend({
type: DS.attr(),
zip: DS.attr(),
city: DS.attr(),
street: DS.attr()
});
The API sends back the Address records embedded in the Partner records. Example response:
{
"partners": [
{
"id": 47,
"name": "Johnny",
"addresses": [
{
"id": 7,
"type": "MAIN",
"zip": "1234",
"city": "City-X",
"street": "6. Nowhere"
}
],
},
]
}
The problem is that type on the Address model is just a normal property, but Ember Data wants it to be the type of the embedded model, and I get this assertion message:
Error: No model was found for 'MAIN'
Where 'MAIN' is the content of the type property.
I can't change how the API sends back data, I need to adapt to it.
How do I do this?
Edit: Important detail which I forgot to include the first time.
The API has a /partners/search endpoint, which I access with a direct ajax request, then I (supposedly) need to call this.store.pushMany('partner', this.store.normalize('partner', response.partners)); And this is when the No model was found for 'MAIN' is raised.
Your { partners: { addresses: [ ... ] } } is returning an embedded data, but because you are providing an object with id and type ember-data is understanding that the api returns an polymorphic association. And trying to find a model called MAIN, but since it does't exist Error: No model was found for 'MAIN' is raised.
In order to tell ember-data that your relationship is embedded you need to override the PartnerSerializer and include the DS.EmbeddedRecordsMixin.
App.ApplicationAdapter= DS.RESTAdapter;
App.ApplicationSerializer = DS.RESTSerializer;
App.PartnerSerializer = DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
addresses: { embedded: 'always' }
}
})
A live demo of this sample running http://emberjs.jsbin.com/nufofehota/1/edit?html,js,output

GORM get/find resource by ID using MongoDB in Grails

Grails makes it easy to get a domain object by ID (handy for building a REST API).
A controller to retrieve a resource can be as simple as:
MetricController.groovy
import grails.converters.JSON
class MetricController {
def index() {
def resource = Metric.get(params.id)
render resource as JSON
}
}
When using the Grails plugin for MongoDB GORM (compile ":mongodb:1.2.0"), the default id type of Long needs to be changed to type String or ObjectId.
Metric.groovy
import org.bson.types.ObjectId
class Metric {
static mapWith = "mongo"
ObjectId id
String title
}
However, doing a .get(1) will now result in:
Error 500: Internal Server Error
URI
/bow/rest/metric/1
Class
java.lang.IllegalArgumentException
Message
invalid ObjectId [1]
I took a guess and changed the controller to use findById:
def resource = Metric.findById(new ObjectId(new Date(), params.id.toInteger()))
That fixed the error, but it fails to find the object (always returns null).
For example, using the id of "-1387348672" does not find this test object:
{ "class" : "Metric",
"id" : { "class" : "org.bson.types.ObjectId",
"inc" : -1387348672,
"machine" : 805582250,
"new" : false,
"time" : 1371329632000,
"timeSecond" : 1371329632
},
"title" : "Test"
}
The ObjectId.inc field may not even be the correct field to use for the resource ID.
So, what is the simplest way to retrieve a domain object by ID when using MongoDB?
When a domain object is persisted in MongoDB, it is stored as a document with an ObjectId as a unique 12 bytes BSON primary key. For example, if you have a domain object Product like
import org.bson.types.ObjectId
class Product {
ObjectId id
String name
static mapWith = "mongo"
}
then the persisted entity in MongoDB would look like below if you save with a name as "TestProduct".
//db.product.find() in mongodb
{
"_id" : ObjectId("51bd047c892c8bf0b3a58b21"),
"name" : "TestProduct",
"version" : 0
}
The _id becomes your primary key for that document. To get this document you need the primary key ObjectId. Speaking from a RESTful context, you atleast need the 12 bytes hexcode 51bd047c892c8bf0b3a58b21 as part of the request.
So in the above case, you can fetch that particular document by doing something like
Product.get(new ObjectId("51bd047c892c8bf0b3a58b21"))
Product.findById(new ObjectId("51bd047c892c8bf0b3a58b21"))
Have a look at the API for ObjectId which would make clear how to retrieve a document.
When you retrieve the document as JSON, then it just shows the ObjectId class with its elements.
{
"class": "com.example.Product",
"id": {
"class": "org.bson.types.ObjectId",
"inc": -1280996575,
"machine": -1993569296,
"new": false,
"time": 1371341948000,
"timeSecond": 1371341948
},
"name": "TestProduct"
}
For completeness, here's the domain with a controller to get a resource by ID string (instead of ObjectID).
Example:
Metric.get("51bf8ccc30040460f5a05579")
Domain
import org.bson.types.ObjectId
class Metric {
ObjectId id
String title
static mapWith = "mongo"
def out() {
return [
id: id as String, //convert Mongo ObjectId to 12-byte hex BSON
title: title
]
}
}
The out() method is used to render the resource showing its ID string (not its ObjectID).
Controller
import grails.converters.JSON
class MetricController {
def index() {
def resource = Metric.read(params.id)
render resource.out() as JSON
}
}
Example JSON response for /rest/metric/51bf8ccc30040460f5a05579
{ "id": "51bf8ccc30040460f5a05579", "title": "Accounts" }