I am writing Jersey RESTful web services. All my method like add, delete, get work. But i want create method who showing what book what user borrowing.
public class UserManagement {
private Map<Long, UserMaker> userMaker = DataBase.getUserMaker();
public UserManagement(){ //id , name, surname, nin, status of book
userMaker.put((long) 1, new UserMaker(1,"John", "Castles", 12345,0));
public UserMaker hireBook(UserMaker user, BookMaker book){ // method who update status hiring book , if 0 that means book is rented
if(user.getId() <= 0){
return null;
}
book.setStatus((int) user.getId()); //
user.setWhatIhave((int) (book.getId())); // convert int to long
userMaker.put(user.getId(), user);
return user;
} }
And now i want using method with multiple parameters
#Path("/user")
public class UserCRUD {
UserManagement userManagementWS = new UserManagement();
#PUT
#Path("/{idU}/{idB}")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public UserMaker hireBook(
#PathParam("idU") long idU, UserMaker user,
#PathParam("idB") long idB, BookMaker book) {
user.setId(idU);
return userManagementWS.hireBook(user, book); //borrowing books
} }
And i got error, but all looks fine:
Method public project.emil.lib.model.UserMaker project.emil.lib.resources.UserCRUD.hireBook(long,project.emil.lib.model.UserMaker,long,project.emil.lib.model.BookMaker) on resource class project.emil.lib.resources.UserCRUD contains multiple parameters with no annotation. Unable to resolve the injection source.
Any tip? :)
Resource methods may not have more than one entity parameter. You can have multiple #PathParam, #QueryParam, etc. but only one unannotated parameter in each resource method.
3.3.2.1 Entity Parameters
The value of a parameter not annotated with
#FormParam
or any of the annotations listed in in Section 3.2,
called the entity parameter, is mapped from the request entity body. Conversion between an entity body and
a Java type is the responsibility of an entity provider, see Section 4.2. Resource methods MUST have at
most one entity parameter.
http://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf
You could remove UserMaker user from your resource method and instead pass the user id to userManagementWS.hireBook(idU, book). And then retrieve the user from your Map<Long, UserMaker> via userMaker.get(idU).
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#get-java.lang.Object-
But I'd recommend you restructure your api. I found this link pretty informative http://www.vinaysahni.com/best-practices-for-a-pragmatic-restful-api.
Related
I need to ignore the field when return the response from spring boot. Pls find below info,
I have one pojo called Student as below
Student {
id,
name,
lastName
}
i am getting a body for as PostRequest as below
{
id:"1",
name:"Test",
lname:"Test"
}
i want get all the data from frontEnd (id,name,Lname) But i just want to return the same pojo class without id as below,
{
name:"Test",
lName:"Test"
}
I have tried #JsonIgnore for column id, But it makes the id column as null(id=null -it is coming like this even when i send data to id field from postman) when i get the data from frontEnd.
I would like to use only one pojo to get the data with proper data(withoud getting id as Null), and need to send back the data by ignoring the id column.
Is there any way to achieve it instead of using another pojo?
You just need to use #JsonInclude(JsonInclude.Include.NON_NULL) at class level and it will be helpful for ignore all your null fields.
For example :
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Test {
// Fields
// Constructors
// Getters - setters
}
As of now you are using only one POJO it's not good practice because it's your main entity into your project, so good practice is always make DTO for the same.
This is possible via the #JsonView annotation that is part of Jackson. Spring can leverage it to define the views used on the controller.
You'd define your DTO class like this:
class User {
User(String internalId, String externalId, String name) {
this.internalId = internalId;
this.externalId = externalId;
this.name = name;
}
#JsonView(User.Views.Internal.class)
String internalId;
#JsonView(User.Views.Public.class)
String externalId;
#JsonView(User.Views.Public.class)
String name;
static class Views {
static class Public {
}
static class Internal extends Public {
}
}
}
The Views internal class acts as a marker to jackson, in order to tell it which fields to include in which configuration. It does not need to be an inner class, but that makes for a shorter code snippet to paste here. Since Internal extends Public, all fields marked with Public are also included when the Internal view is selected.
You can then define a controller like this:
#RestController
class UserController {
#GetMapping("/user/internal")
#JsonView(User.Views.Internal.class)
User getPublicUser() {
return new User("internal", "external", "john");
}
#GetMapping("/user/public")
#JsonView(User.Views.Public.class)
User getPrivateUser() {
return new User("internal", "external", "john");
}
}
Since Spring is aware of the JsonView annotations, the JSON returned by the /public endpoint will contain only externalId and name, and the /internal endpoint will additionally include the internalId field.
Note that fields with no annotation will not be included if you enable any view. This behaviour can be controlled by MapperFeature.DEFAULT_VIEW_INCLUSION, which was false in the default Spring ObjectMapper when I used this for the last time.
You can also annotate your #RequestBody parameters to controller methods with JsonView, to allow/disallow certain parameters on input objects, and then use a different set of parameters for output objects.
I am experimenting with the library OmniPersistence.
I have a problem using the class org.omnifaces.persistence.model.VersionedEntity. In my code there is a simple entity class City.
#Entity
public class City extends VersionedEntity<Long> {
#Id
#GeneratedValue
private Long id;
private String postalCode;
private String name;
... (getter + setter)
}
There is a REST-Service that exposes the Entity for some client-applications. But every time I want to update an object a javax.persistence.OptimisticLockException is thrown. The problem is that the version attribute is always null. A look in the code of VersionedEntity revealed that there is no setter method, but a comment
// No setter! JPA takes care of this.
I do understand the intention of the absence of the setter method but that is the reason for the exception.
Question
Is my architecture so poor (exposing the entity class in a web-service) or is it maybe reasonable to add a setter method although JPA should handle the value/manipulation of the #Versioned attribute?
Edit (as requested by the comment)
My update method is basically the one in OmniPersistence' BaseEntityService. My service class looks like the following.
#Stateless
public class CityService extends BaseEntityService<Long, City> {
public Long count() {
return super.createLongQuery("select count(c) from City c").getSingleResult();
}
}
My controller is the REST endpoint.
#Path("city")
public class CityEndpoint {
#Inject
private CityService cityService;
#GET #Produces(MediaType.APPLICATION_JSON)
public Response getAll() {
List<City> cities = cityService.list();
return Response.ok(cities).build();
}
#GET #Produces(MediaType.APPLICATION_JSON)
#Path("{id}")
public Response get(#PathParam("id") Long id) {
return Response.ok(cityService.getById(id)).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
public Response create(City city) {
cityService.persist(city);
return Response.created(URI.create(String.format("city/%s", Objects.toString(city.getId())))).build();
}
#POST #Consumes(MediaType.APPLICATION_JSON)
#Path("update")
public Response update(City city) {
System.out.println(city);
City updated = cityService.update(city);
return Response.ok(updated).build();
}
#GET
#Path("count")
public Response count() {
return Response.ok(cityService.count()).build();
}
}
The JPA specification document provides an important hint that you must not manipulate the #Version attribute, see section 3.4.2, on page 90
An entity may access the state of its version field or property or
export a method for use by the application to access the version,
but must not modify the version value.
and
The version attribute is updated by the persistence provider runtime
when the object is written to the database.
So the comment (”No setter! JPA takes care of this.“) you find in VersionedEntity is absolutely reasonable. In essence, you should not change (or null) the #Version attribute from higher application levels.
In your case, it seems, you must compensate the ”lost“ (=nulled) version effect, eg by introducing a DTO for City. Otherwise, you will always run into an OptimisticLockException.
I'm rewriting an application, this time using a RESTful interface from Spring. I'm presuming that server-side authorization is best. That is:
Supppose user 1 works this REST repository. He/she accesses mysite.com/heroes/1 and gets the (id = 1) hero from the hero table.
User 2 doesn't have rights to see the (id = 1) hero, but could craft a cURL statement to try anyway. I claim the server should prevent user 2 from accessing the (id = 1) hero.
I believe that the server can extract a JWT payload that gives me the user name or password (I put it in there). From that payload the server fetches the user's account and knows what heroes he/she is entitled to see.
I have already accomplished this goal through services and DAO classes. However, the Spring Boot and JPA tutorials I see promote using CrudRepository implementations to reduce coding. I'd like to know how to do my filtering using this technology.
Here is an example from the web:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
}
When mysite.com/heroes/1 is accessed it automagically returns the data from hero (id = 1). I'd like to instruct it to let me choose which ID values to permit. That is, at runtime a query parameter is provided to it through code.
As a test I provided this code:
#RepositoryRestResource(collectionResourceRel = "heroes", path = "heroes")
public interface HeroRepository extends CrudRepository<Hero, Long> {
#Query ("from Hero h where id in (1, 3, 5)")
public Hero get();
}
However, it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
How do I get to my desired goal?
Thanks, Jerome.
UPDATE 5/13, 5:50 PM
My request is being misunderstood, so I further explain my intent.
Users 1 and 2 are ordinary users, accessing their accounts.
Each user must be confined to his/her own account.
A user can't cheat by crafting requests for other peoples' data.
Thus the need for the server to extract a user ID, or such, from a JWT token and apply it in code to whatever causes the /heroes query to work.
My original example originated with this tutorial. In it the only Java classes are Hero and HeroRepository. There are no explicit classes for DAO, services or controllers. The included Spring libraries let all of the /heroes fetching occur without further coding.
Thanks again for all of your interest and help. Jerome.
You can create a custom #Query, that uses informations (here: id) of the logged in user. With this solution an user have only access to an entity with the same id as he has.
#Override
#Query("SELECT h FROM Hero h WHERE h.id=?1 AND h.id=?#{principal.id}")
public Hero findOne(Long id);
You need to enable SpEl for #Query (link) and create an custom UserDetailsService (link) with custom UserDetails, that contains the id of the user, so you can do principal.id.
In the same way you should secure the findAll() method.
I have created HeroRepository to resolve all the queries up to my understanding.
I'd like to instruct it to let me choose which ID values to permit.
You can achieve the same using.
List<Hero> findByIdIn(List<Long> ids);
Or, if you prefer Query
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
it doesn't block mysite.com/heroes/2 from returning the (id = 2) hero.
I cannot see your Controller/Service methods, so I am assuming that findOne() is being called. You can prevent it using..
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
OR, if you want more control over your method invocations, you can also use #PreAuthorize from spring-security.
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
Summary
public interface HeroRepository extends CrudRepository<Hero, Long> {
// Disallow everybody to use findOne()
default Hero findOne(Long id) {
throw new RuntimeException("Forbidden !!");
}
// If u want to pass ids as a list
List<Hero> findByIdIn(List<Long> ids);
// Alternative to above one
#Query("SELECT H FROM Hero H WHERE H.id IN :ids")
List<Hero> alternativeFindByIdIn(#Param("ids") List<Long> ids);
// Authorization based method call
#PreAuthorize("hasRole('ADMIN')")
Optional<Hero> findById(Long id);
}
PS: Note that I am returning Optional<Hero> from the method. Optional.empty() will be returned if query produces no results. This will force us to check if the value is present before doing any operation, thereby avoiding NullPointerException.
use this code for Controller : -
#RestController
#RequestMapping("/cities")
public class CityController {
private static final Logger logger = LoggerFactory.getLogger(CityController.class);
#Autowired
private CityService cityService;
#RequestMapping(value = "/{id}", method = RequestMethod.GET)
public RestResponse find(#PathVariable("id") Long id) {
.
.
}
use below code for Repo :-
public interface CityRepo extends JpaRepository<FCity, Long> {
#Query("select e from FCity e where e.cityId = :id")
FCity findOne(#Param("id") Long id);
}
use below code for service :-
#Service
#Transactional
public class CityService {
#Autowired(required = true)
private CityRepo cityRepo;
public FCity findOne(Long id) {
return cityRepo.findOne(id);
}
}
Is this possible?
For example I have a controller method like the follows with the binding result.
#RequestMapping(value = "/employee", method = RequestMethod.POST)
public String employee(#ModelAttribute #Validated(BasicDetails.class) Employee employee, BindingResult binding {
if(binding.hasErrors()){
return "failView";
}
return "successView";
}
My employee class below(I have omitted a lot of unnecessary details). The name field is part of the BasicDetails groups ,when this field fails validation(when it is empty) it appears in the errors property of binding result but I can't see any information on the group. Does the group ever get passed to binding results? Is there any nice way to access the group information in my above controller method?
public class Employee{
#Size(min=1,groups=BasicDetails.class)
public String name;
public interface BasicDetails{}
}
Thanks.
I am using the jersey implementation of JAX-RS for the web service. I am very new to this JAX-RS.
I am trying to add a method in the service which accept an Employee object and returns the employee Id based on the Employee object values (there is a DB hit for this).
Following the Restful principles, I made the method as #GET and provided the url path as shown below:
#Path("/EmployeeDetails")
public class EmployeeService {
#GET
#Path("/emp/{param}")
public Response getEmpDetails(#PathParam("param") Employee empDetails) {
//Get the employee details, get the db values and return the Employee Id.
return Response.status(200).entity("returnEmployeeId").build();
}
}
For testing purpose, I wrote this Client:
public class ServiceClient {
public static void main(String[] args) {
ClientConfig config = new DefaultClientConfig();
Client client = Client.create(config);
WebResource service = client.resource(getBaseURI());
Employee emp = new Employee();
emp.name = "Junk Name";
emp.age = "20";
System.out.println(service.path("rest").path("emp/" + emp).accept(MediaType.TEXT_PLAIN).get(String.class));
}
private static URI getBaseURI() {
return UriBuilder.fromUri("http://localhost:8045/AppName").build();
}
}
When I run it, I am getting the error: Method, public javax.ws.rs.core.Response com.rest.EmployeeService.getEmpDetails(com.model.Employee), annotated with GET of resource, class com.rest.EmployeeService, is not recognized as valid resource method.
Edit:
Model:
package com.model;
public class Employee {
public String name;
public String age;
}
Please let me know where is the issue, I am a beginner in this and struggling to understand the concepts :(
JAX-RS cannot automatically convert a #PathParam (which is a string value), into an Employee object. Requirements for objects that can be automatically created from a #PathParam are:
String (defacto, because the data is already a string)
Objects with a constructor that accepts a (single) string as argument
Objects with a static valueOf(String) method
For cases 2 & 3 the object would be required to parse the string data and populate its internal state. This is not normally done (because it forces you to make assumptions about the content type of the data). For your situation (just beginning to learn JAX-RS), its best to just accept the incoming #PathParam data as a String (or Integer, or Long).
#GET
#Path("/emp/{id}")
public Response getEmpDetails(#PathParam("id") String empId) {
return Response.status(200).entity(empId).build();
}
Passing a complex object representation to a REST service in a GET method doesn't make much sense, unless its being used, eg, as a search filter. Based on your feedback, that is what you are trying to do. I've actually done this on a project before (generic implementation of search filters), the one caveat being that you need to strictly define the format of the search data. So, lets define JSON as the accepted format (you can adapt the example to other formats as needed). The 'search object' will be passed to the service as a query parameter called filter.
#GET
#Path("/emp")
public Response getEmployees(#QueryParam("filter") String filter) {
// The filter needs to be converted to an Employee object. Use your
// favorite JSON library to convert. I will illustrate the conversion
// with Jackson, since it ships with Jersey
final Employee empTemplate = new ObjectMapper().readValue(filter, Employee.class);
// Do your database search, etc, etc
final String id = getMatchingId(empTemplate);
// return an appropriate response
return Response.status(200).entity(id).build();
}
And in your client class:
final String json = new ObjectMapper().writeValueAsString(emp);
service
.path("rest")
.path("emp")
.queryParam("filter", json)
.accept(emp, MediaType.TEXT_PLAIN)
.get(String.class)
I got the same issue but I just fixed it
jersey jars used in your application should have same versions.
It's quite strange that you want to serialize an entity (Employee) into an url path segment. I'm not saying it's impossible, but strange, and then you have to make sure that the Employee class satisfies the following criteria (which is from the javadoc of #PathParam)
The type of the annotated parameter, field or property must either:
... (irrelevant part)
Have a constructor that accepts a single String argument.
Have a static method named valueOf or fromString that accepts a single String argument (see, for example, Integer.valueOf(String)).
You might want to pass the Employee object in the request body raher than in a path param. To chieve this, annotate the Employee class with #XmlRootElement,remove the #PathParam annotation from the method's empDetails argument, and change #GET to #POST.