How to validate rest path (spring boot) - java

How to validate path variables (storeId,customerId,accountId) given from the following URL or anything similar ?
/store/{storeId}/customers/{customerId}/accounts/{accountId}
In case a user starts writing random storeIds/customerIds, and try to create resources like
POST /store/478489/customers/56423/accounts (supposing 478489 and 56423 don't point to a valid resource) in the URL.
I want to return the correct error code e.g. HttpStatus.NOT_FOUND, HttpStatus.BAD_REQUEST.
I am using Java with spring boot.
Following question explains my problem more detailed, however it does not have much responses.
Validating path to nested resource

From the provided URL /store/{storeId}/customers/{customerId}/accounts/{accountId},
It is evident that store has customers and those customers have accounts.
Following approach includes extra database calls for validating store by ID and customer by ID, but that would be the appropriate approach because if we use query with joins on STORE and CUSTOMER tables then you might not be able to exactly tell if given storeId or customerId is incorrect/not in database.
If you go step by step you can show appropriate error messages,
If storeId is incorrect - There exists no store with given storeId: XYZ
If customerId is incorrect - There exists no customer with customerID: XYZ
Since, you mentioned that you are using Spring Boot, your code should be looking something like this:
#RequestMapping(value = "/store/{storeId}/customers/{customerId}/accounts",
method = RequestMethod.POST)
public ResponseEntity<Account> persistAccount(#RequestBody Account account, #PathVariable("storeId") Integer storeId,
#PathVariable("customerId") Integer customerId) {
// Assuming you have some service class #Autowired that will query store by ID.
// Assuming you have classes like Store, Customer, Account defined
Store store = service.getStoreById(storeId);
if(store==null){
//Throw your exception / Use some Exception handling mechanism like #ExceptionHandler etc.
//Along with proper Http Status code.
//Message will be something like: *There exists no store with given storeId: XYZ*
}
Customer customer = service.getAccountById(storeId, customerId);
if(customer==null){
//Throw your exception with proper message.
//Message will be something like: *There exists no store with given customerID: XYZ*
}
// Assuming you already have some code to save account info in database.
// for convenience I am naming it as saveAccountAgainstStoreAndCustomer
Account account = service.saveAccountAgainstStoreAndCustomer(storeId, customerId, account);
ResponseEntity<Account> responseEntity = new ResponseEntity<Account>(account, HttpStatus.CREATED);
}
Above code snippet is just a skeleton of what your code should look like, you make structure it in better way than it is given above by following some good coding practices.
I hope it helps.

Related

Springboot/Thymeleaf - How to execute a different SQL query based on the URL?

I would like to be able to display different data based on the previous category that was selected. For example, I currently have categories, that once clicked should redirect a user to a new page that displays all the relevant information for that category. The url should look something like localhost:8080/category/321 where the ID of that category is last. I need to find a way to execute a different sql query depending on the URL/category that was selected. For example, if category 1 is selected, I would like all comments that have a category ID of 1 to be displayed using a statement like
SELECT * FROM Comments WHERE CategoryID='the category id of the category that was selected';
I have used the findAll() method elsewhere in my application to display all data, but I am unsure how to perform specific queries based on the URL. I have also looked briefly into findByID() Thanks
You can add additional methods in your repositories. For your case something like:
List<Comment> findByCategoryID(Long categoryId);
Spring will resolve the query using method name.
Or with jpql:
#Query("SELECT c FROM Comment AS c WHERE c.CategoryID = :categoryId")
List<Request> findByCategoryID(Long categoryId);
Or use findAll overload which works with Example. Java doc - here.
Example:
Comment comment = new Comment;
comment.setCategoryId(1);
List<Comment> comments = repository.findAll(Example.of(comment));
You need to make a distinction between your controller and your repository. The controller is what is responsible for everything related to HTTP and HTML, so parsing the URL, generating the HTML, etc... The repository is the class that is responsible for querying the database.
A controller would typically look like this:
#Controller
#RequestMapping("/comments")
public class CommentsController {
#GetMapping
public String listAllComments(Model model) {
// Normally, you don't go directly to the repository,
// but use a service in between, but I show it like this
// to make a bit easier to follow
List<Comment> comments = repository.findAll();
model.addAttribute("comments", comments);
return "index"; // This references the Thymeleaf template. By default in a Spring Boot appliation, this would be `src/main/resources/templates/index.html`
}
#GetMapping("/{id}"
public String singleComment(#PathVariable("id") Long id, Model model) {
// Spring will automatically populate `id` with the value of the URL
// Now you can use the id to query the database
Comment comment = repository.findById(id).orElseThrow();
model.addAttribute("comment", comment);
return "single-comment";
}
}
That second method would handle a URL of the form /comments/123.
In your example, if comments have a category, then most likely, you would use a query parameter, and not a path variable. In that case, your controller method would be:
#GetMapping
public String commentsByCategory(#QueryParameter("categoryId")Long categoryId, Model model) {
List<Comments> comments = repository.findAllByCategoryId(categoryId);
model.addAttribute("comments", comments);
return "comments-by-category";
}
Then the URL would be /comments?categoryId=123
For the repository itself, be sure to read up on query methods in the documentation: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods

How should a user-specific resource be protected using spring boot security?

Let's consider basic authentication flow. User A should be able to access entities only with IDs 1 and 2, and user B should be able to access only IDs 3 and 4.
Put it another way, I want user A to be able to access /foos/1 and foos/2, but get a 401 if trying to call foos/3.
The way I consider implementing it is getting the current user ID in the endpoint, checking which IDs are mapped to it, and if the requested resource ID is not in that list, throw an unauthorized exception. Something like this:
#GetMapping("/foo/{fooId}")
public Foo home(#AuthenticationPrincipal User user, #PathVariable String fooId) {
// Get Foo IDs mapped to User
// If fooId is not in the result, throw an unauthorized exception
}
It feels like there should be a more streamlined way to do this. Is there a better way?
How should I protect a user-specific resource from being retrieved by another user?
The way you consider to do it is fine...
Another way is to use SpEL directly in the FooRepository ref: jpa.query.spel-expressions
#Query("select foo from Foo foo where foo.id = ?1 and foo.user.id=?#{principal.id}")
Foo findFooById(String fooId);

What's best practice in Spring Data JPA Error handling

I'm new in the world of spring and Spring Data.
I've build a user- and permission management system where a user can grant and remove permissions for another user.
I've digged in the docs but I'm not shure how to handle querys where I just want to insert or manipulate Data in a Table.
In the docs query return type table I couldn't find something like a status or a status reporting object.
What I'm looking for is a pattern that allows me to controle is an operation was successfull. Right now I'm using primitives. When everything worked out I get "1" returned but when I query an empty table I get "0". So I need something with more details.
Thank you for guidance and sharing your experience.
EDIT:
Here is a code example:
#Transactional
#Modifying
#Query(value = "DELETE FROM permissions WHERE producerId=:producerId AND comsumerId=:consumerId", nativeQuery = true)
void clearAllPermissions(#Param("producerId") Long producerId,#Param("consumerId") Long consumerId);
The Method is provided by my repository class.
#Repository
public interface PermissionsRepository extends JpaRepository<ProducerConsumerPermissions, Integer>{
.
.
.
}
I call the class from my service layer which is used by my Controller layer.
I guess it'd be nice to know if this operation was successfull so that I can transport the exeption throught the layer till my frontend and throw a message to the user. But when the Table is empty I get a value of false back when I use Integer as return type.
Make your method return int then you know if any records are deleted.
#Transactional
#Modifying
#Query(value = "DELETE FROM permissions WHERE producerId=:producerId AND comsumerId=:consumerId", nativeQuery = true)
int clearAllPermissions(#Param("producerId") Long producerId,#Param("consumerId") Long consumerId);
If there is a problem it will throw and exception anyway. Spring Data throws DataAccessException that you can catch to see what went wrong.

How to detect duplicate request in method post with same time

I using spring boot and create api with method look like below:
#RestController
#RequestMapping("/api/products/")
#Api(value = "ProductControllerApi",produces = MediaType.APPLICATION_JSON_VALUE)
public class ProductController {
#PostMapping
public ResponseEntity<ProductDto> createProduct(#RequestBody Product product) {
URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(product.getId()).toUri();
return ResponseEntity.created(location).body(productService.createProduct(product));
}
}
But when user using device call my method(/api/products/) with same time, it duplicate create with same data. Example: When use create user look like
{
"name": "Samsung"
"cost": "26$"
}
it create two record in database with same data. How to detect duplicate data from different source(example : user using two mobile and call same time with same method and create same data). How to avoid it and if it call same time with same data it only insert one record to database
This isn't a problem for Spring Boot but rather for your persistence layer. The best practice is for your DB tables to modeled in such a way that two identical requests would create exactly the same primary key. Then your application code would deal with any exception from the DB layer at transaction commit time.

PUT method (RESTful) doesn't work as a way to update resources

According to this article(http://restcookbook.com/HTTP%20Methods/put-vs-post/), PUT is supposed to work as a method to update resources.
However, practicing RESTful with JAX_RS 2.0 and Jersey 2.0, I don't think it updates a particular resource.
(I.e. I'm studying RESTful with JAX_RS 2.0 and Jersey 2.0)
Here is a resouce like this.
<customer>
<name>Before</name>
<postcode>111</postcode>
</customer>
What I'm trying to do is to update (perhaps I should say "replace") this resource.
ClientConfig config = new ClientConfig();
Client client = ClientBuilder.newClient(config);
WebTarget target = client.target("http://xxx/yyy/zzz/end.cust");
Customer cust = new Customer();
cust.setName("After");
cust.setPostcode(222);
target.path("Before").request().put(Entity.xml(cust));
#Id annotation is set to "Name" in the "Customer" class, so the path "Before" is supposed to work as the ID and the first resource (named "Before") should be replaced with the second resource (named "After").
However, after the coding above is executed, the "Before" resource still remains, and there is a new "After" resrouce.
It seems that the PUT method worked to create a new resource, instead of updating something.
(i.e. There are both "Before" and "After" resources, and nothing has been updated)
I tested a POST method in order to create a new resource, and it created a new resource as I expected.
If you see anything I'm doing wrong or what needs to be done, could you please give some advice?
edit
I'll add the server side code. The method annotated with #PUT is like this.
#PUT
#Path("{id}")
#Consumes({"application/xml", "application/json"})
public void edit(#PathParam("id") String id, Customer entity) {
super.edit(entity);
}
This is inside a class called CustomerFacadeREST.java, automatically created after I created a "RESTful service from Database".
According to NetBeans' document, super.edit() method is originally like this.
public void edit(T entity) {
getEntityManager().merge(entity);
}
In the "Customer" class, #Id is set to the "name" value in this way.
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 80)
#Column(name = "Name")
private String name;
// Other fields, such as Postcode...
public Customer() {
}
// Other constructors and methods...
}
The idea behind "HTTP Verbs" like PUT, GET, POST, DELETE are just a matter of protocol semantics. Just performing an HTTP PUT operation doesn't do anything magical. It's just proper semantics we as developers should understand, while developing, as these semantics are known to all (that's why protocols exist). If no one followed these semantics, the world would be somewhere between the Great Depression and the Apocalypse.
That being said, these verbs (semantics) are a sort of guarantee (or maybe assurance is a better word) to the client performing the request with a certain verb will have some know semantics to it. One major factor is the idea of idempotence. Idempotence is the idea that no matter how many times I make a request, the result will be the same (or have the same effect).
Certain HTTP verbs are said to be idempotent, such as PUT, DELETE, GET. No matter how many times be make the exact same request, the general idea is that the result/effect should be the same. POST on the other hand is said to not be idempotent, as the exact same POST request may produce different results, for example submit an order, wrongfully, again, or creating a new customer twice.
If we want to make the world a better place, and do our part in saving the world from a complete meltdown, we should learn these semantics and be good citizens by following them. There's a lot more to learn about the verb semantics, than just idempotence, but understanding that much, is a good start. I'd suggest maybe picking up a good book on REST to learn some good practices. Or if you want you want to be a cool kid, take time to read the bible (actually the Fielding Dissertation).
All that being said, it's our job as developers to create the code to follow these semantics. The reason your method is creating a new resource, is probably because you are creating a new resource with your code. Maybe something like this would seem more appropriate:
#PUT
#Path("/customers/{id}")
#Consumes(MediaType.APPLICATION_JSON)
public Response updateCustomer(#PathParam("id") long id,
Customer updateCustomer) {
Customer customer = customerService.getCustomerById(id);
if (customer == null) {
throw new WebApplicationException("Can't find it", 404);
}
customer.setFirstName(updateCustomer.getFirstName());
customer.setLastName(updateCustomer.getLastName());
...
return Response.noContent().build();
}
So we are just update the customer that already exists in our database. Normally with a PUT request to update, the particular customer resource URI should be known. So say the client makes a request to http://blah.com/api/customers/1234, our service will look up the customer with the id 1234. If it can't be found, we return a 404 status code, as the resource doesn't exist. If it does exist, then we update the customer with the customer data provided in the request. If you wanted to create a new customer, where the URI is not known, then POST would be correct, and you'd send a customer representation to http://blah.com/api/customers.
Also keep just an FYI: in many cases a case like this, what happens is that the client requests (GET) a resource, say a customer, and updates that customer representation, then send it back as PUT request with the updated customer. On the sever it should use that information to update the particular customer's data, as you can see from the example above.
UPDATE
Per your edit. You are completely missing the point of how this is supposed to work.
Customer cust = new Customer();
cust.setName("After");
cust.setPostcode(222);
target.path("Before").request().put(Entity.xml(cust));
What's wrong with this is that with the new Customer, you are setting the identifier to "After", which is different from the identifier in the request path, you are using "Before". So the path variable {id} is "Before". With this request URI you are saying that you want to access the customer with id "Before". As seen in my code, it's your duty to check if a customer with the id "Before" exists in the database. If not, you should return back a 404 Not Found. The name (id) you set for the new Customer should be the id expected in the database. So if you want to update the customer with id in the databse "After". then you should put "After" in the path, instead of "Before". We should not try and change the identifier.
Like I said, when we want to update a resource, we normally, GET the resource, update some field (but not the identifier), and send it back. A sequence might look something like
final String PATH = "http://hello.com/api/customers"
WebTarget target = client.target(PATH);
Customer customer = target.path("1234").request().get(Customer.class);
// where 1234 is the id (or in your case `name` of the customer.
// I would avoid using the name as the DB id, that's why my example uses numbers
customer.setPostalCode(...);
target = client.target(PATH).path(customer.getName()); // getName should be 1234
Response response = target.request().put(Entity.xml(customer));
We are using the same id as we were provided with, in the path, because that is the how the resource is identified in the server.

Categories