I have a JPA-enabled, mongoDB-backed Spring Boot REST service in which I'm having trouble querying a list in the entity. They look like this:
{
"_id": {
"$oid": "639df2e83f61353766023bf9"
},
"body": "body1",
"name": "name1",
"tags": [
"tag1"
],
"_class": "ca.footeware.rest.recipes.model.Recipe"
}
Note that "tags" is an array of Strings.
My repository extends MongoRepository<Recipe, String>. I have two methods, the first works but the second doesn't:
Page<Recipe> findByNameOrBodyContainingIgnoreCaseOrderByNameAsc(String name, String body, Pageable pageable);
Page<Recipe> findByNameOrBodyOrTagsContainingIgnoreCaseOrderByNameAsc(String name, String body, String tags, Pageable pageable);
The addition of "OrTags" and the accompanying "tags" parameter causes a search for "tag" to fail. Further it seems everything after "OrTags" is ignored, i.e. the "Containing", "IgnoreCase" and "OrderBy" are lost.
What am I doing wrong?
OK, for those playing along at home, I made some changes I hesitate to call progress.
I saw somewhere that the problem might be the type of the list, i.e. String. So I created a record:
public record Tag(String value) {
public Tag(String value) {
this.value = value;
}
}
And changed my entity class to:
public class Recipe {
private String body;n
#Id
private String id;
private List<String> images;
private String name;
private List<Tag> tags;
...
In mongodb's Compass app it shows its JSON as:
{
"_id": {
"$oid": "639e3f4b0f60a06354564152"
},
"body": "body1",
"name": "name1",
"tags": [
{
"value": "tag1"
}
],
"_class": "ca.footeware.rest.recipes.model.Recipe"
}
My repository method is now:
Page<Recipe> findByNameOrBodyOrTagsValueContainingIgnoreCaseOrderByNameAsc(String name, String body, String tags, Pageable pageable);
And it works! Well kinda...if I search for the fields' exact value it works. The ContainingIgnoreCaseOrderByNameAsc is being ignored. Not good in a search function.
Anyone have any ideas? Any help would be greatly appreciated.
Oh I should've mentioned I'm on Spring Boot 3 and Java 19 if that matters.
The correct sequence of elements in the repository method is:
findByNameContainingIgnoreCaseOrBodyContainingIgnoreCaseOrTagsValueContainingIgnoreCaseOrderByNameAsc
Not sure if my modification from List of String to List of Tag made any difference.
Related
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"
}
I'm trying out JSON views, not on top of domain class using #Resource, but by creating a RestfulController and trying to render that using JSON views. I've added all the relevant dependencies in build config.
I have a domain Post class like this (which I didn't want to directly expose)
class Post implements Serializable {
Map comments
User user
Venue venue
String description
Rating rating //should this be an enum?
LocalDateTime dateCreated
LocalDateTime lastUpdated
static belongsTo = [user:User]
static hasOne = [rating:Rating]
static constraints = {
venue nullable:true
comments nullable:true
description nullable:true
rating nullable:true, lazy:false
}
static mapping = {
//set the sort order for Posts - default using newest post first
sort dateCreated :"desc"
}
}
So I then created a default RestfulController like this:
class PostRestController extends RestfulController {
static responseFormats = ["json", "xml"]
//constructor - tells rest controller which domain class to scaffold
PostRestController() {
super (Post)
}
}
I'm not overriding any of the default scaffolding methods here.
When I used a rest client to access the default (I've mapped /api/posts (resources: postRest in the UrlMappings). When I access the URL with my REST client I got the full dump of the Post (including comments field persisted in a map) - this looks like this in my rest client - all OK:
[
{
"id": 1,
"comments": {
"view": "lovely"
},
"dateCreated": {
"class": "java.time.LocalDateTime",
"dayOfMonth": 7,
"dayOfWeek": {
"enumType": "java.time.DayOfWeek",
"name": "TUESDAY"
},
"dayOfYear": 66,
"hour": 19,
"minute": 15,
"month": {
"enumType": "java.time.Month",
"name": "MARCH"
},
"monthValue": 3,
"nano": 263000000,
"second": 10,
"year": 2017,
"chronology": {
"calendarType": "iso8601",
"class": "java.time.chrono.IsoChronology",
"id": "ISO"
}
},
"description": null,
"lastUpdated": {
"class": "java.time.LocalDateTime",
"dayOfMonth": 7,
"dayOfWeek": {
"enumType": "java.time.DayOfWeek",
"name": "TUESDAY"
},
"dayOfYear": 66,
"hour": 19,
"minute": 15,
"month": {
"enumType": "java.time.Month",
"name": "MARCH"
},
"monthValue": 3,
"nano": 263000000,
"second": 10,
"year": 2017,
"chronology": {
"calendarType": "iso8601",
"class": "java.time.chrono.IsoChronology",
"id": "ISO"
}
},
"rating": null,
"user": {
"id": 1
},
"venue": null
}
],
I then tried to add JSON views on top of this in the grails-app/views/postRest folder.
I did a really simple template _post.gson like this:
model {
Post post
}
json {
comments post.comments
description post.description
//rating post.rating
userWhoPosted "${post?.user}"
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MMM-dd")
def when = post.dateCreated.format(formatter)
created when
}
I then added an index.gson to render the template:
model {
List<Post> postList
}
//call the template to iterate over the postList to produce the output
json g.render(postList)
This breaks the server with this stacktrace and a 500 error to the REST client. If I comment out the line in_post.gson relating to user it all works. Leave it in and it fails:
Caused by: grails.views.ViewRenderException: Error rendering view: null
at grails.views.AbstractWritableScript.writeTo(AbstractWritableScript.groovy:43)
at grails.views.mvc.GenericGroovyTemplateView.renderMergedOutputModel(GenericGroovyTemplateView.groovy:73)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:303)
at grails.views.mvc.renderer.DefaultViewRenderer.render(DefaultViewRenderer.groovy:111)
at grails.artefact.controller.RestResponder$Trait$Helper.internalRespond(RestResponder.groovy:188)
at grails.artefact.controller.RestResponder$Trait$Helper.respond(RestResponder.groovy:62)
at grails.rest.RestfulController.index(RestfulController.groovy:64)
at grails.transaction.GrailsTransactionTemplate$2.doInTransaction(GrailsTransactionTemplate.groovy:96)
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133)
at grails.transaction.GrailsTransactionTemplate.execute(GrailsTransactionTemplate.groovy:93)
... 4
If I comment out the post.user (and ratings reference) it works OK, but when I try and post the post.user it fails with the above. There was a note in the docs about ensuring that your query pulled the refs with a fetch join - so I tried to provide a override to ensure I returned the fetch join - all I get is empty returned to the client:
class PostRestController extends RestfulController {
static responseFormats = ["json", "xml"]
//constructor - tells rest controller which domain class to scaffold
PostRestController() {
super (Post)
}
def index() {
Collection<Post> res = Post.list([fetch:[user:"join",rating:"join"]])
res
}
}
Why when I do it without the JSON view it works fine and when I use the JSON view I can't get the output including references? I checked the list request and it returns the list successfully in the debugger - but breaks in the rendering.
If I can get this to work, JSON views on Grails 3.2.6 looks pretty nice.
Aaargh - think this issue is with jsonViews 1.1.5 - its not ready for java 8 LocalDateTime.
i saw a trace on stackoverflow see topic
hibernate will now take localdateTime in your domain classes - that works. But the json template rendering wont, even if you add the java8 plugin.
So i went back into domain class changed my LocalDateTime back to Date, also changed the json template to use the older SimpleDateTime format (instead of DateTimeFormatter) and re ran - low and behold it worked.
I'll be really glad when we can say grails is properly java8 ready.
apparently json views requires feature enablement which is due in json views 2 (think this is an M2 right now) - so i've had to revert to java 7 Date until then.
blimey another lost day in the weeds.
I am having difficulties getting multiple datasets out of my database with RestTemplate. I have many routines that extract a single row, with a format like:
IndicatorModel indicatorModel = restTemplate.getForObject(URL + id,
IndicatorModel.class);
and they work fine. However, if I try to extract a set of data, such as:
Map<String, List<S_ServiceCoreTypeModel>> coreTypesMap =
restTemplate.getForObject(URL + id, Map.class);
this returns values in a
Map<String, LinkedHashMap<>>
format. Is there an easy way to return a List<> or Set<> in the desired format?
Fundamentally the issue is that your Java object model does not match the structure of your json document. You are attempting to deserialize a single json element into a java List. Your JSON document looks like:
{
"serviceCoreTypes":[
{
"serviceCoreType":{
"name":"ALL",
"description":"All",
"dateCreated":"2016-06-23 14:46:32.09",
"dateModified":"2016-06-23 14:46:32.09",
"deleted":false,
"id":1
}
},
{
"serviceCoreType":{
"name":"HSI",
"description":"High-speed Internet",
"dateCreated":"2016-06-23 14:47:31.317",
"dateModified":"2016-06-23 14:47:31.317",
"deleted":false,
"id":2
}
}
]
}
But you cannot turn a serviceCoreTypes into a List, you can only turn a Json Array into a List. For instance if you removed the unnecessary wrapper elements from your json and your input document looked like:
[
{
"name": "ALL",
"description": "All",
"dateCreated": "2016-06-23 14:46:32.09",
"dateModified": "2016-06-23 14:46:32.09",
"deleted": false,
"id": 1
},
{
"name": "HSI",
"description": "High-speed Internet",
"dateCreated": "2016-06-23 14:47:31.317",
"dateModified": "2016-06-23 14:47:31.317",
"deleted": false,
"id": 2
}
]
You should be able to then deserialize THAT into a List< S_ServiceCoreTypeModel>. Alternately if you cannot change the json structure, you could create a Java object model that models the json document by creating some wrapper classes. Something like:
class ServiceCoreTypes {
List<ServiceCoreType> serviceCoreTypes;
...
}
class ServiceCoreTypeWrapper {
ServiceCoreType serviceCoreType;
...
}
class ServiceCoreType {
String name;
String description;
...
}
I'm assuming you don't actually mean database, but instead a restful service as you're using RestTemplate
The problem you're facing is that you want to get a Collection back, but the getForObject method can only take in a single type parameter and cannot figure out what the type of the returned collection is.
I'd encourage you to consider using RestTemplate.exchange(...)
which should allow you request for and receive back a collection type.
I have a solution that works, for now at least. I would prefer a solution such as the one proposed by Ben, where I can get the HTTP response body as a list of items in the format I chose, but at least here I can extract each individual item from the JSON node. The code:
S_ServiceCoreTypeModel endModel;
RestTemplate restTemplate = new RestTemplate();
JsonNode node = restTemplate.getForObject(URL, JsonNode.class);
JsonNode allNodes = node.get("serviceCoreTypes");
JsonNode oneNode = allNodes.get(1);
ObjectMapper objectMapper = new ObjectMapper();
endModel = objectMapper.readValue(oneNode.toString(), S_ServiceCoreTypeModel.class);
If anyone has thoughts on how to make Ben's solution work, I would love to hear it.
I am doing documentation for a REST service returning an object like this:
Map<String, HashMap<Long, String>>
and i find no way to describe response fields for such object.
Let's have a look at my code.
The service:
#RequestMapping(value = "/data", method = RequestMethod.GET)
public Map<String, HashMap<Long, String>> getData()
{
Map<String, HashMap<Long, String>> list = dao.getData();
return list;
}
My unit-test-based documentation:
#Test
public void testData() throws Exception
{
TestUtils.beginTestLog(log, "testData");
RestDocumentationResultHandler document = document(SNIPPET_NAME_PATTERN ,preprocessResponse(prettyPrint()));
document.snippets(
// ,responseFields(
// fieldWithPath("key").description("key description").type("String"),
// fieldWithPath("value").description("value as a Hashmap").type("String"),
// fieldWithPath("value.key").description("value.key description").type("String"),
// fieldWithPath("value.value").description("value.value description").type("String"),
// )
String token = TestUtils.performLogin(mockMvc, "user", "password");
mockMvc
.perform(get(APP_BUILD_NAME + "/svc/data").contextPath(APP_BUILD_NAME)
.header("TOKEN", token)
)
.andExpect(status().is(200))
.andExpect(content().contentType("application/json;charset=UTF-8"))
.andExpect(jsonPath("$").isMap())
.andDo(document);
TestUtils.endTestLog(log, "testData");
}
As you can see the code for response fields is commented out since I haven't had any solution for it yet. I am working on that but i really appreciate your help. Thank you in advance.
Your JSON contains a huge number of different fields. There looks to be over 1000 different entries in the map. Each of those entries is itself a map with a single key-value pair. Those keys all appear to vary as well. Potentially, that gives you over 2000 fields to document:
cancel
cancel.56284
year
year.41685
segment_de_clientele
segment_de_clientele.120705
…
This structure is making it hard to document and is also a strong indicator that it will be hard to consume by clients. Ideally, you would restructure the JSON so that each entry has the same keys and it's only the values the vary from entry to entry. Something like this, for example:
{
"translations": [ {
"name": "cancel",
"id": 56284,
"text": "Exit"
}, {
"name": "year",
"id": 41685,
"text": "Year"
}, {
"name": "segment_de_clientele",
"id": 120705,
"text": "Client segment"
}]
}
This would mean that you only have a handful of fields to document:
translations[]
translations[].name
translations[].id
translations[].text
If that's not possible, then I'd stop trying to use the response fields snippet to document the structure of the response. Instead, you should describe its structure manually in your main Asciidoctor source file.
There are two options:
1> Changing MAP to LIST of objects so that the response fields can be described easily.
2> Putting description manually to index.adoc file.
In my case, I go for option 2 because I have to stick to MAP.
I am creating a new project using a REStful service. I need to send a single object containing
collection of object containing data for UI
object User( Name, role etc)
Collection of error occured(If any).
So I have designed my class like this
public class ServiceREsponse
{
Collection<ServiceError> errorCollection { get; private set; }
Collection<object> objectCollection { get; set; }
User user { get; set; }
}
How do I populate values in this class, or how do I use this class in my service?
I don't know exactly the technologies / frameworks you use as client to the RESTful service. In fact, you need to instantiate your class and populate with data you want to send. Then you need to convert this object into a structure that can be put into the payload of the HTTP request.
Here is a sample:
PUT /myresource
(some headers like Content-Type: application/json)
{
"objectCollection": [
(...)
],
"user": {
(...)
}
}
The service may not support all structures but formats like JSON, XML or YAML are commonly supported. With JSON, we will have something like that:
{
"objectCollection": [
{ "field1": (...) },
{ "field1": (...) },
{ "field1": (...) },
(...)
],
"user": {
"name": "my name",
(...)
}
}
You can notice that collections must be converted into arrays in the structure.
Moreover the generated structure must match the structure expected by the service.
With Java, you have some great REST client like Restlet (http://restlet.com/products/restlet-framework/) and object to/from JSON / XML / YAML like Jackson (http://wiki.fasterxml.com/JacksonHome).
Hope it helps,
Thierry