Spring-Data #RepositoryRestResource deleteByName uses wrong HTTP-Method when deleting a resource - spring-data

I'm faced with a scenario where the custom #RepositoryRestResource interface-method is involved by the wrong HTTP-Method. For example:
#RepositoryRestResource(path = "matches", collectionResourceRel = "matches")
public interface MatchRepo extends Neo4jRepository<Match, Long> {
Collection<Match> findAllByCodeName(#Param("codeName") String codeName);
#Transactional
Long deleteAllByCodeName(#Param("codeName") String codeName);
}
Request:
curl -i -X GET 'http://localhost:8003/spring-data/api/v1/matches/search/findAllByCodeName?codeName=Test-CodeName-1'
Note the above GET HTTP-Method. This is expected, & i'm happy with the Response:
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 20 Nov 2018 15:32:49 GMT
{
"_embedded" : {
"matches" : [ {
"id" : "1",
"codeName" : "Test-CodeName-1",
"round" : 1,
"me" : "ROCK",
"pc" : "ROCK",
"result" : "D",
"timestamp" : "Nov 20, 2018, 05:32:27 AM",
"lastUpdated" : "Nov 20, 2018, 05:32:27 AM",
"created" : "Nov 20, 2018, 05:32:27 AM",
"_links" : {
"self" : {
"href" : "http://localhost:8003/spring-data/api/v1/matches/22"
},
"match" : {
"href" : "http://localhost:8003/spring-data/api/v1/matches/22"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:8003/spring-data/api/v1/matches/search/findAllByCodeName?codeName=Test-CodeName-1"
}
}
}%
This is what appears on the Intelli-J Console-Mappings:
http://localhost:8003/spring-data/api/v1/{repository}/search
& I implemented the request as indicated in the mappings, as shown below. But the problem becomes evident when I am deleting a resource with a GET HTTP-Method as shown below:
Request:
curl -i -X GET 'http://localhost:8003/spring-data/api/v1/matches/search/deleteAllByCodeName?codeName=Test-CodeName-1'
Response:
HTTP/1.1 200
Content-Type: application/hal+json;charset=UTF-8
Transfer-Encoding: chunked
Date: Tue, 20 Nov 2018 15:51:33 GMT
{
"10":
}
I need to find a way to make my custom deleteAllByCodeName(#Param) interface-method from the MatchRepo class to execute with the correct HTTP-Method. Must use DELETE HTTP-Method & not the GET HTTP-Method and adhere to the REST-API Design Principles.

The manual notes that search resources only support GET requests.
https://docs.spring.io/spring-data/rest/docs/3.1.2.RELEASE/reference/html/#repository-resources.search-resource
You can prevent this repo method from being exported:
#RestResource(exported = false)
Long deleteAllByCodeName(#Param("codeName") String codeName);
and create a normal Spring MVC controller that handles the delete request.

Related

Spring Cloud Contract consumer side test returns 404

While running Spring Contract test on consumer side using stubs. I got following response first for endpoint creation then for request that was sent to it and then for the response.
Both type1 and type2 are enum fields, the other fields are string.
127.0.0.1 - POST /mappings
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/4.5.12 (Java/11.0.5)]
Host: [localhost:11291]
Content-Length: [834]
Content-Type: [text/plain; charset=UTF-8]
{
"id" : "7d0b6496-be88-43c0-891d-bdb8ef8ae033",
"request" : {
"url" : "/testEndpoint",
"method" : "PUT",
"headers" : {
"Content-Type" : {
"matches" : "application/json;charset=UTF-8.*"
}
},
"bodyPatterns" : [ {
"matchesJsonPath" : "$[?(#.['name'] == 'Test')]"
}, {
"matchesJsonPath" : "$[?(#.['type1'] == 'NONE')]"
}, {
"matchesJsonPath" : "$[?(#.['type2'] == 'NONE')]"
}, {
"matchesJsonPath" : "$[?(#.['description'] == null)]"
}, {
"matchesJsonPath" : "$[?(#.['comment1'] == null)]"
}, {
"matchesJsonPath" : "$[?(#.['comment2'] == null)]"
} ]
},
"response" : {
"status" : 201,
"transformers" : [ "response-template" ]
},
"uuid" : "7d0b6496-be88-43c0-891d-bdb8ef8ae033"
}
127.0.0.1 - PUT /testEndpoint
Connection: [keep-alive]
User-Agent: [Apache-HttpClient/4.5.12 (Java/11.0.5)]
Host: [localhost:11291]
Accept-Encoding: [gzip,deflate]
Content-Length: [117]
Content-Type: [application/json; charset=UTF-8]
{"name":"Test","type1":"NONE","type2":"NONE","description":null,"comment1":null,"comment2":null}
The response I got was:
Matched response definition:
(no response definition configured)
Response:
HTTP/1.1 404
(no headers)
Actually I see that the problem is that the regex for json content type is "application/json;charset=UTF-8.*" and the actual is "application/json; charset=UTF-8" (notice the space). You should change your contract to support a regex for that space (e.g. application/json.*)

Filter (nested) boolean field in Mongo / Meteor

I have a Mongo collection (using Meteor) structured like so:
{
"_id" : "xx",
"name" : "xx",
"handle" : "xx",
"tweets" : [
{
"published" : true,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
{
"published" : true,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
{
"published" : false,
"tweet_id" : "xx",
"text" : "xx",
"created_at" : "Mon Jul 31 18:18:38 +0000 2017",
"retweet_count" : 0,
"from" : "x",
"from_full_name" : "x",
"from_profile_image" : "x"
},
]
}
I only want to display the published tweets. I am using a template helper to retrieve and filter:
return Tweets.find({
handle:handle,
"tweets.published": true
});
I cannot seem to get the nested filter on 'published' to work. All tweets are displayed, using the above code. I have tried many different permutations of the "tweets.published": true. What is the correct way to filter out the unpublished tweets?
Since the tweets field is an array of objects, not just one object, your method will not work.
First you should use the handle to find the correct document:
return Tweets.find({
handle: handle,
});
This must then be combined with a fields, to select tweets which should be returned.
return Tweets.find({
handle: handle,
}, {
fields: { tweets: { $elemMatch: { published: true } } }
});
the $elemMatch part specifies the tweet must be publised. More information on the mongo page.
EDIT
If the snippet must run on the client (in your case), you have other options. You can use a server publication with my snippet to only give published tweets to the client.
Alternatively, give the transform option to the find request.
return Tweets.find({
handle: handle
}, {
transform: doc => {
doc.tweets = doc.tweets.filter(tweet => tweet.published);
return doc;
}
});

Sum Emails Sizes From MongoDB Collection

I have been asked to perform a very basic query task in MongoDB however I am unable to understand how to properly query the collection w/ aggregate functions in proper syntax.
I need to query the email collection for all email attachment sizes & sum them for today. Customer is aksing me to group all the email attachements for just their account for a single day (today). How would I find this?
Below is the output of db.email.findOne():
{
"_id" : ObjectId("55893983e4b0ea8af5a61550"),
"customer_id" : "12345",
"Subject" : "test message",
"Date" : ISODate("2016-08-04T10:48:13Z"),
"headers" : [
"Date: Tue, 23 Jun 2015 12:48:13 +0200 (CEST)",
"From: user#domain.tld",
"Message-ID: <240354118.javamail.email.server.tld>",
"Subject: Cats",
"Content-Type: text/plain; charset=us-ascii",
"Content-Transfer-Encoding: 7bit",
"To: undisclosed-recipients:;",
"X-ClamAV: clean"
],
"text" : "feed the cats please",
"attachments" : [ ],
"langprob" : 0.8301894511454121,
"original_message_file_id" : "239863489r7637208",
"account_id" : "xxx",
"received_time" : ISODate("2015-06-23T10:48:35.097Z"),
"direction" : "inbound",
"state" : "CLOSED",
"encryption_key_id" : null,
"size" : 1651,
"routing_type" : "PUSH",
"priority" : 1,
"closed_time" : ISODate("2015-07-10T21:02:53.409Z")
}
Can anyone please assist me in properly creating a query in JSON syntax to extract the data I need from MongoDB based on my predicates?
Thank you for any help!

Querying a list in MongoDB

I have a document that looks like this:
{
"_id" : ObjectId("570fc2381d4899be8a8ec9d9"),
"statuses" : [
{
"created_at" : "Wed Apr 13 09:56:39 +0000 2016",
"id" : 7.20188946337153e+017,
"id_str" : "720188946337153024",
"text" : "RT #BCC_Assicura: #FormulaAuto la #polizza #Auto e #Moto economica BccPordenonese - #BCC #Assicurazioni #Click2go"
},
{
"created_at" : "Wed Apr 13 09:40:13 +0000 2016",
"id" : 7.20184809658708e+017,
"id_str" : "720184809658707970",
"text" : "Auto e moto storiche, vademecum su assicurazione e bollo - \n#autostoriche #bollo #RCauto #ASI #FMI"
}
]}
How do I query for all the records where the variable text contains the string "assicur"?
Thank you!
One possibility would be to use a regex;
> db.test.find({"statuses.text":{$regex: 'assicur'}})
That said, this will not be possible to index in mongodb, so it's probably best done along with other filters that cut the documents down to a small set before doing the string matching.

Spring Boot Rest search 404

I noticed periodically that /search doesn't seem to work on my #RepositoryRestResource:
Spring Boot Output
2014-08-25 13:37:51.526 INFO 10645 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repository}/search],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.HttpEntity<?> org.springframework.data.rest.webmvc.RepositorySearchController.headForSearches(org.springframework.data.rest.webmvc.RootResourceInformation)
2014-08-25 13:37:51.526 INFO 10645 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repository}/search],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.hateoas.ResourceSupport org.springframework.data.rest.webmvc.RepositorySearchController.listSearches(org.springframework.data.rest.webmvc.RootResourceInformation)
2014-08-25 13:37:51.526 INFO 10645 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repository}/search/{search}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.lang.Object> org.springframework.data.rest.webmvc.RepositorySearchController.executeSearch(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.web.context.request.WebRequest,java.lang.String,org.springframework.data.domain.Pageable,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler)
2014-08-25 13:37:51.526 INFO 10645 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repository}/search/{search}],methods=[GET],params=[],headers=[],consumes=[],produces=[application/x-spring-data-compact+json],custom=[]}" onto public org.springframework.hateoas.ResourceSupport org.springframework.data.rest.webmvc.RepositorySearchController.executeSearchCompact(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.web.context.request.WebRequest,java.lang.String,java.lang.String,org.springframework.data.domain.Pageable,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler)
2014-08-25 13:37:51.526 INFO 10645 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repository}/search/{search}],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.lang.Object> org.springframework.data.rest.webmvc.RepositorySearchController.headForSearch(org.springframework.data.rest.webmvc.RootResourceInformation,java.lang.String)
curl on /persons
curl http://localhost:8080/persons
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"persons" : [ {
"firstName" : "Jimmy",
"lastName" : "Neutron",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/2"
}
}
}, {
"firstName" : "Jimmy",
"lastName" : "Page",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/3"
}
}
}, {
"firstName" : "Jimmy",
"lastName" : "Johns",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/4"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 3,
"totalPages" : 1,
"number" : 0
}
cURL on /search
curl -v http://localhost:8080/persons/search
* Adding handle: conn: 0x7f9903004400
* Adding handle: send: 0
* Adding handle: recv: 0
* Curl_addHandleToPipeline: length: 1
* - Conn 0 (0x7f9903004400) send_pipe: 1, recv_pipe: 0
* About to connect() to localhost port 8080 (#0)
* Trying ::1...
* Connected to localhost (::1) port 8080 (#0)
> GET /persons/search HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
>
< HTTP/1.1 404 Not Found
* Server Apache-Coyote/1.1 is not blacklisted
< Server: Apache-Coyote/1.1
< Content-Length: 0
< Date: Mon, 25 Aug 2014 18:55:27 GMT
<
* Connection #0 to host localhost left intact
Usually I can restart the Application and it randomly appears with 0 changes:
curl http://localhost:8080/persons
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons{?page,size,sort}",
"templated" : true
},
"search" : {
"href" : "http://localhost:8080/persons/search"
}
},
"page" : {
"size" : 20,
"totalElements" : 0,
"totalPages" : 0,
"number" : 0
}
Is there something needed to enable search functionality 100% of the time?
I had the same problem and found out that causes it.
Spring loads only one repository for your domain type to find search resource mappings. If you have multiple repositories for your domain type, spring somehow randomly selects one and uses that one. If that one does not contain your custom search method, it does not load.
I don't quite buy the randomness argument, but if you can really reproduce it, feel free to open a ticket in our tracker.
The general rule is that the search resource is only exposed if there's at least a single query method exposed (i.e. a query method that's not actively configured to not be exported) on the repository in question.
Generally speaking, you shouldn't really let clients construct URIs on the fly but rather inspect the response for links and follow them. Thus, I'd argue that if you get the first response, performing a call to /search is invalid as the client has no reason to assume the resource to even exist. If you start with the second response, you can discover a search link advertising a search resource being available and follow it.
Getting random "Not Found" error can happen in case you have multiple repositories defined for the same domain object. It is a known Spring Data Rest bug which happens due to undeterministic order in which those repositories are discovered/registered.
Please vote at https://jira.spring.io/browse/DATAREST-923 to get it fixed.