So I am new with restlet. I am creating a Android application that can communicate with a GAE server (with objectify DB)
I Did this very good tutorial to learn:
http://www.tutos-android.com/webservice-rest-android-appengine-restlet-objectify
It's working very well but do very little.
Onely 2 methods:
public interface UserControllerInterface {
#Put
void create(User user);
#Get
Container getAllUsers();
}
For my application its more complicated so I add many more methods:
public interface UserControllerInterface {
#Put
public void createUser(ObagooUser user);
#Put
public void createMessage(ObagooUser user, String message);
#Put
public void updateMessage(ObagooMessage message);
#Get
public List<ObagooUser> getAllUser();
#Get
public ObagooUser getUserById(String id);
#Get
public List<ObagooMessage> getAllMessage();
#Get
public List<ObagooMessage> getAllMessageFromSender(ObagooUser sender);
#Get
public ObagooMessage getFreeMessage(ObagooUser user);
}
Each of these mothds working server side (I tested with Junit).
Now I am coding the android part and I am having problems.
When I do a simple call to getAllMessage() I get an error:
java.lang.IllegalArgumentException: id cannot be zero
at com.google.appengine.api.datastore.KeyFactory.createKey(KeyFactory.java:44)
at com.googlecode.objectify.ObjectifyFactory.typedKeyToRawKey(ObjectifyFactory.java:269)
at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:159)
at com.googlecode.objectify.impl.ObjectifyImpl.find(ObjectifyImpl.java:183)
at com.obagoo.dao.ObagooUserDAO.getUserById(ObagooUserDAO.java:43)
at com.obagoo.controller.ObagooController.getUserById(ObagooController.java:47)
It's going in the wrong method (getUserById).
I put a break point in my getAllMessage and it's going in, but it is also going in other methods.
If I test many times, sometimes it's calling, createUser or another random method.
Do you see what I am doind wrong?
Adding the getAllMessage code:
public List<ObagooMessage> getAllMessage() {
// logger.debug("Getting all Obagoo Messages");
List<ObagooMessage> msg = new ArrayList<ObagooMessage>();
Objectify ofy = ObjectifyService.begin();
Query<ObagooMessage> q = ofy.query(ObagooMessage.class);
for (ObagooMessage u : q) {
msg.add(u);
}
return msg;
}
In the examples that I've seen, its always shown that you should separate the controller/resource handling the URI for the list resource from the single item (id/name based) resource. So you would have something like:
router.attach("/users", UsersController.class);
router.attach("/users/{id}", UserController.class
router.attach("/messages", MessagesController.class);
Notice the plural naming on the first class: UsersController, and singular naming on the the second class: UserController. The first class would handle cases where no id was being provided, such as a get of all users. Also, note when the id is provided in the URI, it can be automatically mapped into an id field on the class. So the Get method has no parameters on the method call.
As for handling a subset, then for messages from a specific user, that could be handled with query parameters. For instance when calling via a URI with /messages?sender=id, the MessagesController.class would use the following in the method handling the Get:
Form queryParams = getRequest().getResourceRef().getQueryAsForm();
String id = queryParams.getFirstValue("sender");
Hope that helps. I'm no expert, so anyone feel free to correct me.
As error says: you are creating a key with zero Id.
My gues is that your ObagoMessage Id field is long? You should make it Long. Primitive long Id values are not autogenerated, while object type Long are. See the docs.
Related
If I have a Get request that returns orders of clients, how can I filter the response to give me the objects that have a specific value for example that are made by those specific clients in Spring Boot?
I have tried with #PathVariable and #RequestParams but every attempt failed.
Thank you in advance.
If you want to show a specific order which has an identifier of some sort, use #PathVariable. In the following example, the identifier is a String, but in many case it will rather be long or an Integer.
#RestController
#RequestMapping("/orders")
public class OrdersController {
#GetMapping("/{id}")
public Order getOrder(#PathVariable("id") String id) {
// get the order with a specified id from the backend
}
}
The web request in this case will look like http:/<host>:<port>/orders/123
If you want to filter the order by some name, like 'madeBy John', use Request parameter:
#RestController
#RequestMapping("/orders")
public class OrdersController {
#GetMapping("/")
public List<Order> getOrdersFilteredByName(#RequestParam("madeBy") madeBy) {
// get the order filtered by the person who made the order
// note, this one returns the list
}
}
In this case the web request will look like this: http:/<host>:<port>/orders?madeBy=John
Note that technically you can implement whatever you want at the backend, so you can pass, say, John in the first example as a path variable, on server its a String after all, however what I've described is a straightforward and kind-of-standard way of doing these things - so can expect to see this convention in many projects at least.
#RestController
#RequestMapping("/order")
public class OrderController {
// http://<host>:<port>/order/1
#GetMapping("/{id}")
public Order getOrder(#PathVariable Long id) {
// Return your order
}
// http://<host>:<port>/order?madeBy=John
#GetMapping("/)
public List<Order> getOrdersMadeBy(#RequestParam("madeBy") String madeBy) {
// Return your order list
}
}
I was able to introduce exception handling but I could only really manage to apply it to endpoints that retrieved a specific id
#Repository
public interface OrganizationRepository extends JpaRepository<Organization, Long> {
#Query("FROM Organization WHERE name =?1")
Organization findByOrganizationNameLike(#Param("name") String name);
}
// service class method
public Organization getOrganizationById(final Long id){
return organizationRepository.findById(id).orElseThrow((ResourceNotFoundException::new));
}
//related controller method
#GetMapping("/{id}")
public ResponseEntity<Organization> getOrganizationByID(#PathVariable("id") final Long id) {
return ResponseEntity.ok(organizationService.getOrganizationById(id));
}
I am not sure if it can be applied to an endpoint such as #GetMapping("/{id}/applications") which returns a list of applications associated with a particular organization's id. However, I think it should because if I enter an invalid id into the above endpoint I get a 500 error that isn't handled nicely.
I want to be able to handle the fact that if I pass an invalid organization id into an endpoint like organizations/{id}/applications the exception will be handled gracefully.
The issue as I see it seems to me that I can’t call the orElseThrow method from a custom repository method such as findById(id).
// can invoke exception handling
public Organization getOrganizationById(final Long id){
return organizationRepository.findById(id).orElseThrow((ResourceNotFoundException::new));
}
// can't invoke exception handling
public List<Application> getAllApplicationsWithOrganizationId(final Long id){
return organizationRepository.findById(id).get().getApplications();
}
Am I correct in my assumption?
Any input is greatly appreciated.
The method orElseThrow is from Java's Optional class. If you instead call the get() method, you will no longer have an Optional, but whatever it contains.
What you could do, is use the map() method. This takes the value in the Optional and applies a method to it, or gives an empty Optional if there was no value. After map, you can again use orElseThrow. Like so:
organizationRepository.findById(id)
.map(Organization::getApplications)
.orElseThrow(ResourceNotFoundException::new);
I have a simple REST client with GET POST and DELETE methods.
Weird things is that only GET methods work, neither POST nor DELETE doesn't even get hit and response is "404 Not Found" of course.
Here's my REST service and the client:
Interface:
public interface MyInterface {
#GET
#Path("/content")
#Produces(MediaType.APPLICATION_JSON)
Response getAirports();
#DELETE
#Path("/content/{id}")
#Produces(MediaType.APPLICATION_JSON)
Response deleteAirport(#PathParam("id") String id);
}
Implementation:
#Path("/source")
public class SourceService extends AbstractService implements MyInterface {
#Override
public Response getContent() {
DBCollection collection = getDBCollection("content");
DBCursor cursor = collection.find();
String serialize = JSON.serialize(cursor);
return Response.status(Response.Status.OK).entity(serialize).build();
}
#Override
public Response deleteContent(#PathParam("id") Integer id) {
DBCollection collection = getDBCollection("content");
BasicDBObject query = new BasicDBObject();
query.append("id", id);
collection.remove(query);
return Response.status(Response.Status.OK).build();
}
}
Client:
// This is working
public void getContent() {
WebTarget path = collect.path("/content");
Response response = path.request().get();
LOGGER.info("collect.ping: " + response.readEntity(String.class) + "\n");
}
// This is not working
public void deleteContent(Integer id) {
WebTarget path = collect.path("/content/"+id);
Response response = path.request(MediaType.APPLICATION_JSON).delete();
System.out.println("object deleted:"+response);
}
I've tried requesting with jersey or apache clients but all of them return 404 and I'm like hopeless now.
Hope you can give me a direction.
This looks like a possible duplicate of Inheritance with JAX-RS. Have you tried replicating all annotations in the subclass or none, means do not use #PathParam in the implementation class at all?
If you actually can debug your client and you are indeed able to "Step through" the client code?
If you place a break-point within your server code and you never actually "break" on it? Then the problem is with the way you are exposing your web service and how you are then trying to consume it.
Try to change the parameter type expected by the Server and the type you pass from your client.
If you can change it on the server and client to a simpler type.. i.e.. an integer.. and then you can actually capture a breakpoint in both client and server, then you know that the problem is in your types.
I hope you can understand what I'm saying? You really need to simplify your parameters and/or try it without parameters first.
When you get something simpler working, then you can extend it to something else.
try just changing it to a string... such as "airport" Also, you are passing a parameter in the client as this:
public void deleteAirport(String iata) {
But you don't use "iata" in your client code...
I am using JAX-RS via Jersey and I have hit a "bump in the road". I have a method that is supposed to return a JSON object following an HTTP POST. It does execute successfully, but does not return the JSON Object (Unless I do a work around). I am hoping someone can tell me why this does not work as I expect it to. See the following code:
#Path("chatroom")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public class ChatroomResource {
ChatroomService service = new ChatroomService();
//this works properly and returns the object as json
#GET
public List<Chatroom> getChatrooms() {
return service.getChatrooms();
}
/**********
* This works, but does not return any content in response body
*******/
#POST
public Chatroom addRoom(Chatroom room) {
return service.addChatroom(room);
/*
* This one does produce content body
* service.addChatroom(room);
* return room;
*/
}
}
The following Method is in the service object:
public Chatroom addChatroom(Chatroom room) {
return Cache.getChatrooms().put(room.getRoomName(), room);
}
What might be wrong and how to fix it
Based on the superficial details you've provided, I believe the following instruction is returning null:
return Cache.getChatrooms().put(room.getRoomName(), room);
In the put(String, Chatroom) method, I guess you are adding the Chatroom instance to the cache, but you are returning null instead of the Chatroom instance.
The following should work:
public Chatroom addChatroom(Chatroom room) {
Cache.getChatrooms().put(room.getRoomName(), room);
return room;
}
Update 1
As you mentioned in the comments, you are using a Hashtable to implement your cache.
Be aware the put(K, V) method returns the previous value of the specified key in the hashtable, or null if it did not have one. For more details, consider reading the documentation.
Update 2
Have you ever consider using a HashMap instead of a Hashtable?
If synchronization becomes an issue, you might be interested in a ConcurrentHashMap.
I am using Jersey and converting my existing data services into RESTful data services. Most simple GETs and PUTs I could successfully convert. But following are some I am not able to convert:
X Get (T) // for complex queries with complex result
X Post (T) // for creating with complex result
X PUT (T) // for updating with some success message object
where T and X are a complex objects
I have tried #queryparam, #pathparam with complex objects on GET with #consume & #produce and those didn't work. Also tried POST (though I really needed GET) with url encoded and that didn't work too.
Please Help. I am in need of sample code that does it.
REST isn't designed to handle complex queries as the query is actually the URL. When you retrieve a resource you specify the ID of the resource you want. This is simply a number or string and is easily represented in the URL for example;
http://host/employee/57
would get you employee 57. If your requirements are more complicated then you might want to use a search method, where you pass several parameters. You could use #QueryParam here but this isn't really REST in a pure form.
If you are POSTing or PUTting data then you use the same URL as you would if you were doing a GET, only this time you send data in the content body. As you are able to serialize the object in order to return it to a GET request your client should also be able to serialize it to send it to you in a PUT or POST.
Here's an example of a GET and POST;
#XmlType
public class Employee {
private int id;
private String name;
//getters and setters
}
#Path("/employee")
public class EmployeeService {
#GET
#Path("/{id}")
#Produces(MediaType.APPLICATION_XML)
public Employee get(#PathParam("id") String id) {
Employee e = employeeDao.getEmployee(id);
if (e != null) {
return e;
} else {
throw new WebApplicationException(404);
}
}
#POST
#Consumes(MediaType.APPLICATION_XML)
#Produces(MediaType.APPLICATION_XML)
public Employee post(Employee employee) {
return employeeDao.insertEmployee(employee); //Assumes your DAO sets the ID
}
}