How to return a single item from a query with spring data REST - spring-data-rest

How can we make spring-data-rest return a single resource for a query, instead of an embedded list? This would make navigation much more intuitive for the client.
e.g.
t.follow("search", "byNameAndType", "identity", "mainContact")
instead of
t.follow("search", "byNameAndType", "$_embedded.credentials[0]._links.identity.href", "mainContact")
I have a JPA repository that always return 1 or 0 results. The uniqueness is enforced by a database contraint.
#RepositoryRestResource
public interface CredentialRepository extends PagingAndSortingRepository<Credential, Long>,
ExternalIdRepository<Credential, Long> {
#RestResource(path = "byNameAndType", rel = "byNameAndType")
Credential findByNameIgnoreCaseAndTypeIgnoreCase(#Param("name") String name, #Param("type") String type);
}
spring-data-rest instead of returning the single credential object, it returns an embedded list.
$ curl -H "Accept: text/plain, application/hal+json, */*" -H "X-VCIDB-User-Id:testUser" http://127.0.0.1:8090/credentials/search/byNameAndType?name=V6UqkSG8\&type=myType
{
"_embedded" : {
"credentials" : [ {
"version" : 1,
"lastUpdTs" : "2014-11-13T12:08:49.301+13:00",
"lastUpdBy" : ":integration-test",
"createdTs" : "2014-11-13T12:08:49.092+13:00",
etc etc
instead, it should return the same as if i retrieved it by primary key.
{
"version" : 1,
"lastUpdTs" : "2014-11-13T12:08:49.301+13:00",
etc etc
Otherwise the API to the client looks horrible
Traverson t = new Traverson(new URI("http://127.0.0.1:8090/credentials"), MediaTypes.HAL_JSON);
t.setRestOperations(template);
Map<String, Object> params = new HashMap<>();
params.put("name", "V6UqkSG8");
params.put("type", "myType");
String contactUuid = t.follow("search", "byNameAndType", "$_embedded.credentials[0]._links.identity.href", "mainContact")
.withTemplateParameters(params).<String> toObject("$.uuid");
LOG.info(contactUuid);
assertThat(contactUuid, Matchers.is("7a7faeaf-6da3-4188-9d28-afbb30ce38b3"));
when the traversion would be much easier to understand for the client if it was:
String contactUuid = t.follow("search", "byNameAndType", "identity", "mainContact")
.withTemplateParameters(params).<String> toObject("$.uuid");

Related

POST "inline" sub-ressource association for a Collection in Spring Data Rest

I have three entities:
#Entity
public class Presentation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
private User author;
#ManyToMany
#JoinTable(joinColumns = #JoinColumn(name = "PRESENTATION_ID", referencedColumnName = "ID"), inverseJoinColumns = #JoinColumn(name = "TAG_ID", referencedColumnName = "ID"))
private Collection<Tag> tags;
}
#Entity
public class Tag {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true, nullable = false)
private String name;
}
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(unique = true, nullable = false)
private String name;
}
Each Entity has an own Repository:
#Repository
public interface PresentationRepository extends CrudRepository<Presentation, Long> {
}
#Repository
public interface TagRepository extends CrudRepository<Tag, Long> {
}
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
}
So a Presentation have an Author (User) and a list of Tags.
If I create or update a Presentation I have to associate the resources User and Tags to that.
According to the documentation of Spring Data Rest - Association Resource it is possible to assign them using the header Content-Type: text/uri-list. But in this case I have to do multiple calls, one to create the Presentation, one to set the Author and another for the Tags, like that:
curl -i -X POST -H "Content-Type: application/json" -d '{"name": "P1"}' http://localhost:8080/api/presentations
curl -i -X PATCH -H "Content-Type: text/uri-list" -d "
http://localhost:8080/api/users/1" http://localhost:8080/api/presentations/1/author
curl -i -X PATCH -H "Content-Type: text/uri-list" -d "
http://localhost:8080/api/tags/1
http://localhost:8080/api/tags/2" http://localhost:8080/api/presentations/1/tags
I found a way to bypass the extra call for the Author and do that "inline" of the presentation call (thanks to Chetan Kokil):
// WORK:
curl -i -X POST -H "Content-Type: application/json" -d '{"name": "P1", "author": "http://localhost:8080/api/users/1"}' http://localhost:8080/api/presentations
So it saves me an additional call and according to my understanding it follows the ACID principle.
The same I would like to do with the tag list. So I tried the following call:
// DON'T WORK:
curl -i -X POST -H "Content-Type: application/json" -d '{"name": "P1", "author": "http://localhost:8080/api/users/1", "tags":["http://localhost:8080/api/tags/1","http://localhost:8080/api/tags/2"]}' http://localhost:8080/api/presentations
And I am getting following error message:
{
"cause": {
"cause": null,
"message": "Can not construct instance of com.example.model.Tag: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/tags/2')\n at [Source: N/A; line: -1, column: -1]"
},
"message": "Could not read payload!; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.example.model.Tag: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/api/tags/2')\n at [Source: N/A; line: -1, column: -1]"
}
My question is, is it possible to make that and if so, what is the correct syntax?
Thanks all.
While writing my question I found the solution myself :)
Maybe it will help others.
curl -i -X PATCH -H "Content-Type: application/json" -d '{"name": "P1", "author": "http://localhost:8080/api/users/1", "tags":[["http://localhost:8080/api/tags/1","http://localhost:8080/api/tags/2"]]}' http://localhost:8080/api/presentations
If you want to "inline" set the association resources for a list/collection you have to put the URIs inside TWO arrays:
// WORK only for updating of a list that already contains at least one resource:
[["a/b/c", "a/b/c"]]
Only one array doesn't work.
// DON'T WORK:
["a/b/c", "a/b/c"]`
UPDATE:
It looks like it only works if there are already some Tags associated. So that means this doesn't work while creating a new Presentation instance. If the list is empty, it gives me the following error:
Can not deserialize instance of com.example.model.Tag out of START_ARRAY token\n at [Source: N/A; line: -1, column: -1]"
Is it a bug?
I am still searching for a solution.

How to perform multiple parallel operations using a single POST API Call?

I have created an API using Ratpack and Groovy. I want a POST API such that the data should be processed and stored in 2 cassandra databases say table-A and table-B.
For Now I have this in my Ratpack.groovy, and thus I have to call both the APIs whenever a data has to be pushed:
prefix("A") {
post { registry.get(PostEndpointA).handle(context) }
}
prefix("B") {
post { registry.get(PostEndpointB).handle(context) }
}
I wanted a single POST API Call like this, so that by single API call the request can be delegated to both the endpoints together:
prefix("post") {
post { registry.get(PostEndpointA).handle(context) }
post { registry.get(PostEndpointB).handle(context) }
}
OR, I want this:
prefix("post") {
post { registry.get(PostEndpoint).handle(context) }
}
And in the PostEndpoint, I can perform both the operations as this:
saveJsonAsA(context)
.promiseSingle()
.then { ItemA updatedItem ->
context.response.headers
.add(HttpHeaderNames.LOCATION, "/item/api/A")
context.response.status(HttpResponseStatus.CREATED.code())
.send()
}
saveJsonAsB(context)
.promiseSingle()
.then { ItemB updatedItem ->
context.response.headers
.add(HttpHeaderNames.LOCATION, "/item/api/B")
context.response.status(HttpResponseStatus.CREATED.code())
.send()
}
In both the cases, the item is added to only table-A and not B or whatsoever is written in the code earlier.
Note That ItemA and ItemB relates to essentially same DB, only the primary keys are different, so as to facilitate the GET from 2 ways. Any idea how to do this in Ratpack?
If I'm understanding this correctly you could try doing something similar to this:
#Grab('io.ratpack:ratpack-groovy:1.3.3')
import static ratpack.groovy.Groovy.ratpack
import ratpack.http.Status
import ratpack.exec.Promise
import com.google.common.reflect.TypeToken
class ServiceA {
Promise<String> save(json) {
Promise.value(json)
}
}
class ServiceB {
Promise<String> save(json) {
Promise.value(json)
}
}
ratpack {
bindings {
bindInstance new ServiceA()
bindInstance new ServiceB()
}
handlers {
post(':name') { // parameterize on path token
def name = pathTokens.get('name') // extract token
def service = null
switch(name) {
case 'A':
service = get(ServiceA) // extract appropriate service
break
case 'B':
service = get(ServiceB) // extract appropriate service
break
}
parse(new TypeToken<Map<String, Object>>(){})
.flatMap(service.&save) // save your extracted item
.then {
response.headers.add('Location', "/item/api/$name")
response.status(Status.of(201))
response.send()
}
}
}
}
Sample curls look like this
$ curl -X POST -H'Content-Type: application/json' -d '{"foo":"bar"}' -v localh ost:5050/A
< HTTP/1.1 201 Created
< Location: /item/api/A
< content-length: 0
< connection: keep-alive
$ curl -X POST -H'Content-Type: application/json' -d '{"foo":"bar"}' -v localh ost:5050/B
< HTTP/1.1 201 Created
< Location: /item/api/B
< content-length: 0
< connection: keep-alive
From the volume of questions you have been posting I recommend signing up for the Ratpack community slack channel https://slack-signup.ratpack.io/
We use rxJava to do this:
Single
.zip(
service.add(cv1),
service.add(cv2),
(a, b) -> new Object[] { a, b }
)
.blockingGet();
We wrap that in a promise and process the array in the then block.

How to pass same parameter with different value

I am trying the following API using Alamofire, but this API has multiple "to" fields. I tried to pass an array of "to" emails as parameters. It shows no error but did not send to all emails. API is correct, I tested that from terminal. Any suggestions will be cordially welcomed.
http -a email:pass -f POST 'sampleUrl' from="email#email.com" to="ongkur.cse#gmail.com" to="emailgmail#email.com" subject="test_sub" bodyText="testing hello"
I am giving my code:
class func sendMessage(message:MessageModel, delegate:RestAPIManagerDelegate?) {
let urlString = "http://localhost:8080/app/user/messages"
var parameters = [String:AnyObject]()
parameters = [
"from": message.messageFrom.emailAddress
]
var array = [String]()
for to in message.messageTO {
array.append(to)
}
parameters["to"] = array
for cc in message.messageCC {
parameters["cc"] = cc.emailAddress;
}
for bcc in message.messageBCC {
parameters["bcc"] = bcc.emailAddress;
}
parameters["subject"] = message.messageSubject;
parameters["bodyText"] = message.bodyText;
Alamofire.request(.POST, urlString, parameters: parameters)
.authenticate(user: MessageManager.sharedInstance().primaryUserName, password: MessageManager.sharedInstance().primaryPassword)
.validate(statusCode: 200..<201)
.validate(contentType: ["application/json"])
.responseJSON {
(_, _, jsonData, error) in
if(error != nil) {
println("\n sendMessage attempt json response:")
println(error!)
delegate?.messageSent?(false)
return
}
println("Server response during message sending:\n")
let swiftyJSONData = JSON(jsonData!)
println(swiftyJSONData)
delegate?.messageSent?(true)
}
}
First of all if you created the API yourself you should consider changing the API to expect an array of 'to' receivers instead of multiple times the same parameter name.
As back2dos states it in this answer: https://stackoverflow.com/a/1898078/672989
Although POST may be having multiple values for the same key, I'd be cautious using it, since some servers can't even properly handle that, which is probably why this isn't supported ... if you convert "duplicate" parameters to a list, the whole thing might start to choke, if a parameter comes in only once, and suddendly you wind up having a string or something ...
And I think he's right.
In this case I guess this is not possible with Alamofire, just as it is not possible with AFNetworking: https://github.com/AFNetworking/AFNetworking/issues/21
Alamofire probably store's its POST parameter in a Dictionary which doesn't allow duplicate keys.

Does ServiceStack RedisClient support Sort command?

my sort command is
"SORT hot_ids by no_keys GET # GET msg:->msg GET msg:->count GET msg:*->comments"
it works fine in redis-cli, but it doesn't return data in RedisClient. the result is a byte[][], length of result is correct, but every element of array is null.
the response of redis is
...
$-1
$-1
...
c# code is
data = redis.Sort("hot_ids ", new SortOptions()
{
GetPattern = "# GET msg:*->msg GET msg:*->count GET msg:*->comments",
Skip = skip,
Take = take,
SortPattern = "not-key"
});
Redis Sort is used in IRedisClient.GetSortedItemsFromList, e.g. from RedisClientListTests.cs:
[Test]
public void Can_AddRangeToList_and_GetSortedItems()
{
Redis.PrependRangeToList(ListId, storeMembers);
var members = Redis.GetSortedItemsFromList(ListId,
new SortOptions { SortAlpha = true, SortDesc = true, Skip = 1, Take = 2 });
AssertAreEqual(members,
storeMembers.OrderByDescending(s => s).Skip(1).Take(2).ToList());
}
You can use the MONITOR command in redis-cli to help diagnose and see what requests the ServiceStack Redis client is sending to redis-server.

matching and verifying Express 3/Connect 2 session keys from socket.io connection

I have a good start on a technique similar to this in Express 3
http://notjustburritos.tumblr.com/post/22682186189/socket-io-and-express-3
the idea being to let me grab the session object from within a socket.io connection callback, storing sessions via connect-redis in this case.
So, in app.configure we have
var db = require('connect-redis')(express)
....
app.configure(function(){
....
app.use(express.cookieParser(SITE_SECRET));
app.use(express.session({ store: new db }));
And in the app code there is
var redis_client = require('redis').createClient()
io.set('authorization', function(data, accept) {
if (!data.headers.cookie) {
return accept('Sesssion cookie required.', false)
}
data.cookie = require('cookie').parse(data.headers.cookie);
/* verify the signature of the session cookie. */
//data.cookie = require('cookie').parse(data.cookie, SITE_SECRET);
data.sessionID = data.cookie['connect.sid']
redis_client.get(data.sessionID, function(err, session) {
if (err) {
return accept('Error in session store.', false)
} else if (!session) {
return accept('Session not found.', false)
}
// success! we're authenticated with a known session.
data.session = session
return accept(null, true)
})
})
The sessions are being saved to redis, the keys look like this:
redis 127.0.0.1:6379> KEYS *
1) "sess:lpeNPnHmQ2f442rE87Y6X28C"
2) "sess:qsWvzubzparNHNoPyNN/CdVw"
and the values are unencrypted JSON. So far so good.
The cookie header, however, contains something like
{ 'connect.sid': 's:lpeNPnHmQ2f442rE87Y6X28C.obCv2x2NT05ieqkmzHnE0VZKDNnqGkcxeQAEVoeoeiU' }
So now the SessionStore and the connect.sid don't match, because the signature part (after the .) is stripped from the SessionStore version.
Question is, is is safe to just truncate out the SID part of the cookie (lpeNPnHmQ2f442rE87Y6X28C) and match based on that, or should the signature part be verified? If so, how?
rather than hacking around with private methods and internals of Connect, that were NOT meant to be used this way, this NPM does a good job of wrapping socket.on in a method that pulls in the session, and parses and verifies
https://github.com/functioncallback/session.socket.io
Just use cookie-signature module, as recommended by the comment lines in Connect's utils.js.
var cookie = require('cookie-signature');
//assuming you already put the session id from the client in a var called "sid"
var sid = cookies['connect.sid'];
sid = cookie.unsign(sid.slice(2),yourSecret);
if (sid == "false") {
//cookie validation failure
//uh oh. Handle this error
} else {
sid = "sess:" + sid;
//proceed to retrieve from store
}