I'm using spring-data-cosmosdb 2.2.2 with azure-cosmos 3.6.0 and Scala.
I'm using CosmosTemplate to create custom DocumentQueries using sorting and pagination.
My problem is to navigate through the pages in my request using requestContinuation. Every request (POST) is returning a requestContinuation like that:
"requestContinuation": "{\"compositeToken\":\"{\\"token\\":null,\\"range\\":\\"{\\\\"min\\\\":\\\\"05C1DFFFFFFFFC\\\\",\\\\"max\\\\":\\\\"FF\\\\",\\\\"isMinInclusive\\\\":true,\\\\"isMaxInclusive\\\\":false}\\"}\",\"orderByItems\":[{\"item\":\"8b90e589-09d8-42e7-a3e2-d26cf2c38a63\",\"map\":{\"item\":\"8b90e589-09d8-42e7-a3e2-d26cf2c38a63\"}}],\"rid\":\"I9lLALNXyUNECgEAAAAACA==\",\"inclusive\":true}"
The first thing I saw is the inner "token" is always null.
I tried to copy the whole string and put it on my request as parameter but it didn't work:
POST https://baseurl/api/documents/recent?page=1&requestContinuation=...
The requestContinuation never changes and the documents returned in the page are always the same. I'm using Postman and Insomnia.
I don't find good examples to use requestContinuation.
Am I missing something? Maybe encoding the token with base64?
I don't know if the problem is just to pass the requestContinuation or if there's something wrong in my code.
In resume I'm just using paginationQuery method to execute a documentQuery with some criterias and a "pageable" object.
My Controller:
#RestController
#RequestMapping(path = Array("/api"))
class DocumentController(#Autowired private val service: DocumentService) extends BaseController {
#PostMapping(path = Array("/documents/recent"), produces = Array("application/json"))
def findRecentDocuments(pageable: Pageable,
#RequestBody params: DocumentParams,
#RequestHeader(name="apiKey") token: String
): Page[Document] = {
service.findRecentDocuments(pageable, Option(params))
}
My service:
class DocumentService(#Autowired private val documentRepository: DocumentRepository, #Autowired private val template: CosmosTemplate) extends BaseService {
def findRecentDocuments(pageable: Pageable, params: Option[DocumentParams]): Page[Document] = {
val documentClass = classOf[Document]
val collectionName = template.getCollectionName(documentClass)
val defaultSort = Sort.by(Direction.ASC, "id")
val sort = pageable.getSortOr(defaultSort)
val criteria = QueryUtils.getCriteria(params, documentClass)
getCriteriaInfo(criteria)
documentRepository.findRecentDocuments(pageable, criteria, sort, documentClass, collectionName)
}
My repo:
class DocumentRepository(#Autowired private val template: CosmosTemplate) {
def findRecentDocuments(pageable: Pageable, criteria: Criteria, sort: Sort, documentClass: Class[Document], collectionName: String): Page[Document] = {
val query = new DocumentQuery(criteria)
template.paginationQuery(query.`with`(sort).`with`(pageable), documentClass, collectionName)
}
}
Thanks for any help.
Did you try the recommended way of pagination provided in the readme section of spring-data-cosmosdb: https://github.com/microsoft/spring-data-cosmosdb/blob/master/README.md
Section: Supports Spring Data pageable and sort.
Also documented here: https://learn.microsoft.com/en-us/azure/java/spring-framework/how-to-guides-spring-data-cosmosdb#pagination-and-sorting
As I already commented in the original issue on MS Github I found the solution changing the "Pageable" object to "CosmosPageRequest" object which uses requestContinuation.
This worked for me.
Here's the code:
https://github.com/microsoft/spring-data-cosmosdb/issues/484
Thanks.
Lucas Porto, If you open to using the Java SDK, I have implemented infinite scroll with reactive Java SDK 4.0.
The "next" method in the following controller class is the server side entry point for the pagination used by the jQuery Ajax call.
Code on GitHub
Related
I'm beginner to Scala and Playframework. I have some code in Java and I have a problem to translate it into Scala. Can you suggest something how can I do it? I check documentation about ScalaForms but still I can't understand how to do it. My code is as following:
// Injecting FormFactory
public Result create(){
Form<Book> bookForm = formFactory.form(Book.class);
return ok(create.render(bookForm));
}
public Result save(){
Form<Book> bookForm = FormFactory.form(Book.class).bindFromRequest();
Book book = bookForm.get();
Book.add(book);
return redirect(routes.BooksController.index());
}
Ok so I have something like this:
def index = Action {
val books: Set[Book] = Book.allBooks
Ok(views.html.index(books))
}
def create = Action{
Ok(views.html.create())
}
def save = Action {
implicit request =>
val (id, title, price, author) = bookForm.bindFromRequest.get
Book.addBook(id = id, title = title, price = price, author = author)
Redirect(routes.BooksController.index())
}
My routes:
GET /books controllers.BooksController.index
GET /books/create controllers.BooksController.create
POST /books/create controllers.BooksController.save
And the problem is with Redirect, I have an error: "object java.lang.ProcessBuilder.Redirect is not a value"
Ok so for Scala version of dealing with form, this is what I do:
You use bindFromRequest to do, most of the times, two things: 1. If there is an error within the form return it back to the user and 2. If there is no error within the form, use the given data by the user to do some other operations (e.g., call a third party API, or internal API, etc.)
If things are fine (second sub step of the above first step), I return an HTTP response to the user.
So, I would have something like this in my controller method:
def save() = Action {
implict request =>
bookForm.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.whateverTheViewIs(bookForm))
}, formData =>
//Do whatever you need to do
Ok("saved")//Again, here you can use any views; and pass the required parameters.
)
}
It might be the case, that you have difficulty on defining the bookForm itself, so in Scala, you first define your data model using a case class. Lets define a very simple one:
case class Book(title: String, author: String, isbn: String)
Then you define your form:
val bookForm = Form(
mapping(
"title": String -> NonEmptyText
"author" : String -> NonEmptyText
"isbn" : String -> NonEmptyText
)(Book.apply)(Book.unapply)
)
Note on Redirection: For the redirect, as you mentioned in the comment, you could use the URL used. For example ,if I have:
GET /confirmation controllers.Book.confirmation (bookId: String)
Then I want to redirect to the confirmation URL, knowing that I need to also provide the `userId', I can do the following:
val bookId = "1" //You need to get the Id, from a database or an API.
Redirect(s"/confirmation?bookId=$bookId")
Further Step [Use Future API]: The important thing here is that, for simplicity; I did not use the Future API, for asynchronously respond to the request. This should be your aim, because you want to write non-blocking response, especially if you want to call other APIs, for processing/storing the form's valid input.
I'm working with playframework for final project at university and I'm getting a problem when routing a delete or put method.
When I'm requesting a DELETE or PUT methods I'm getting:
[info] play.api.Play - Application started (Dev)
[debug] a.ErrorHandler - onClientError: statusCode = 404, uri = /Rest/deleteCity, message ="
My JQuery ajax call is:
$("#scalaDelete").click(function(){
$("#result").empty();
$.ajax({
url: "http://localhost:9000/Rest/deleteCity",
method: "DELETE",
data: {city: "Alvorada"},
dataType: "json",
success: function(result){
$("#result").append("Result: "+result.Result);
},
error: function (request, status, error) {
alert(status);
}
});
});
My Route Play Route:
DELETE /Rest/deleteCity controllers.RestController.deleteCity()
My Controller Method:
case class UserDelete(city:String)
class RestController #Inject()(db: Database, cc: ControllerComponents) extends AbstractController(cc) {
val userDeleteForm = Form(
mapping(
"city" -> text
)(UserDelete.apply)(UserDelete.unapply)
)
def deleteCity = Action{ implicit request=>
val userPar = userDeleteForm.bindFromRequest.get
//DatabaseDelete
Ok(jsonResult)
}
}
I've already activated cross domain in chrome, I've used a CORS extension for it.
Thanks for helping
This seems related to Restful http delete in play, i.e. DELETE with data can be sketchy.
Instead of passing data, I would just move this to the url:
DELETE /Rest/deleteCity/:city controllers.RestController.deleteCity(city: String)
# or with a query string
DELETE /Rest/deleteCity controllers.RestController.deleteCity(city: String)
and then do
http://localhost:9000/Rest/deleteCity/Alvorada
# or with a query string
http://localhost:9000/Rest/deleteCity?city=Alvorada
Personally I prefer the latter.
I agree with #AndyHayden.
Play ignores the body of the DELETE request, that is the correct behavior to my mind, but you can work around by explicitly passing a body parser:
def delete = Action(parse.json) { implicit request =>
val json = request.body
val someProp = (json \ "someprop").as[String]
Ok(s"Prop is: $someProp")
}
(this example was given by one of the developers of the Play itself:
https://github.com/playframework/playframework/issues/4606#issuecomment-109192802.)
About the doubts in comments:
I've seen another post here where a guy said some browsers just support get and post method.
POST and GET are only valid for the method attribute of the form tag.
You are using javascript request, so you can use any method that server supports. i.e. DELETE is completely fine there.
But something interesting for you to know is that playframework uses akka and this framework does not support DELETE request for security reasons, in fact it wasn't well explained on post. Then if you wanna make a DELETE method you gotta make a post method for complete your code.
Akka HTTP supports the DELETE request (as well as Play Framework): https://doc.akka.io/docs/akka-http/current/scala/http/routing-dsl/directives/method-directives/delete.html
Is there a way to reconfigure the Grails 3 Link Generator to create Restful links, i.e. localhost:8080/book/{id} rather than the old style that includes the action in the URL, localhost:8080/book/show/{id}?
I'd like to have restful URLs in the location headers of the responses to save actions.
I've been using this Grails Restful Link Generator as a workaround. I'm not perfectly happy with it, but it's the best I've been able to come up with thus far.
1. Create a trait in src/main/groovy that removes the superfluous action from the URL
import grails.web.mapping.LinkGenerator
trait RestfulLinkGeneratorTrait {
LinkGenerator grailsLinkGenerator
String generateLink(Map map) {
map.controller = map.controller ?: this.controllerName
map.absolute = map.absolute ?: true
map.action = map.action ?: "show"
grailsLinkGenerator.link(map).replace("/$map.action", "")
}
}
2. Implement the RestfulLinkGenerator on your controller(s) and call generateLink(id: obj.id) to generate links.
#Secured('ROLE_USER')
class BookController extends RestfulController implements RestfulLinkGeneratorTrait {
//... other methods ...//
#Transactional
def save() {
// ... save you resource ... //
response.addHeader(HttpHeaders.LOCATION, generateLink(id: book.id))
respond book, [status: CREATED, view: 'show']
}
//... other methods ...//
}
I have a Object like this:
// I want to test this Object
object MyObject {
protected val retryHandler: HttpRequestRetryHandler = new HttpRequestRetryHandler {
def retryRequest(exception: IOException, executionCount: Int, context: HttpContext): Boolean = {
true // implementation
}
}
private val connectionManager: PoolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager
val httpClient: CloseableHttpClient = HttpClients.custom
.setConnectionManager(connectionManager)
.setRetryHandler(retryHandler)
.build
def methodPost = {
//create new context and new Post instance
val post = new HttpPost("url")
val res = httpClient.execute(post, HttpClientContext.create)
// check response code and then take action based on response code
}
def methodPut = {
// same as methodPost except use HttpPut instead HttpPost
}
}
I want to test this object by mocking dependent objects like httpClient. How to achieve this? can i do it using Mokito or any better way? If yes. How? Is there a better design for this class?
Your problem is: you created hard-to test code. You can turn here to watch some videos to understand why that is.
The short answer: directly calling new in your production code always makes testing harder. You could be using Mockito spies (see here on how that works).
But: the better answer would be to rework your production code; for example to use dependency injection. Meaning: instead of creating the objects your class needs itself (by using new) ... your class receives those objects from somewhere.
The typical (java) approach would be something like:
public MyClass() { this ( new SomethingINeed() ); }
MyClass(SomethingINeed incoming) { this.somethign = incoming; }
In other words: the normal usage path still calls new directly; but for unit testing you provide an alternative constructor that you can use to inject the thing(s) your class under test depends on.
I'm trying to add a custom GORM event listener class in Bootstrap.groovy, as described in the Grails documentation but its not working for me. Here is the code straight from the docs:
def init = {
application.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
applicationContext.addApplicationListener new MyPersistenceListener(datastore)
}
}
When I run it, the compiler complains that application and applicationContext are null. I've tried adding them as class level members but they don't get magically wired up service-style. The closest I've got so far is:
def grailsApplication
def init = { servletContext ->
def applicationContext = servletContext.getAttribute(ApplicationAttributes.APPLICATION_CONTEXT)
grailsApplication.mainContext.eventTriggeringInterceptor.datastores.each { k, datastore ->
applicationContext.addApplicationListener new GormEventListener(datastore)
}
}
But I still get errors: java.lang.NullPointerException: Cannot get property 'datastores' on null object.
Thanks for reading...
EDIT: version 2.2.1
If you do:
ctx.getBeansOfType(Datastore).values().each { Datastore d ->
ctx.addApplicationListener new MyPersistenceListener(d)
}
This should work without needing the Hibernate plugin installed
That looks like it should work, although I'd do it a bit differently. BootStrap.groovy does support dependency injection, so you can inject the grailsApplication bean, but you can also inject eventTriggeringInterceptor directly:
class BootStrap {
def grailsApplication
def eventTriggeringInterceptor
def init = { servletContext ->
def ctx = grailsApplication.mainContext
eventTriggeringInterceptor.datastores.values().each { datastore ->
ctx.addApplicationListener new MyPersistenceListener(datastore)
}
}
}
Here I still inject grailsApplication but only because I need access to the ApplicationContext to register listeners. Here's my listener (simpler than what the docs claim the simplest implementation would be btw ;)
import org.grails.datastore.mapping.core.Datastore
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEvent
import org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener
class MyPersistenceListener extends AbstractPersistenceEventListener {
MyPersistenceListener(Datastore datastore) {
super(datastore)
}
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
println "Event $event.eventType $event.entityObject"
}
boolean supportsEventType(Class eventType) { true }
}
Finally stumbled onto a working Bootstrap.groovy, thanks to this post but I don't think its the best way to do it, rather its a work around.
def init = { servletContext ->
def applicationContext = servletContext.getAttribute(ApplicationAttributes.APPLICATION_CONTEXT)
applicationContext.addApplicationListener new GormEventListener(applicationContext.mongoDatastore)
}
So basically I'm hard-coding the MongoDB datastore directly as opposed to iterating over the available ones, as the docs suggest.
To save you reading the comments to the first answer, the adapted version I provided in the Question (as well as Burt's answer) only works if the Hibernate plugin is installed but in my case I was using the MongoDB plugin so had no need for the Hibernate plugin (it in fact broke my app in other ways).