My Question is How can I validate request parameters if I use #PathParam.
For instance I have two request parameters, name and id
path is localhost:/.../search/namevalue/idvalue
if a user submits blanks for name or id I should send a response mentioning that name is required/ id is required.
I could do the validations if I use #QueryParam, but I'm not sure how to do it if I have to use pathvariables.
If I just test using http:/localhost:/.../search/namevalue orhttp:/localhost:/.../search/idvalue or http:/localhost:/.../search/ it's throwing servlet exception.
Below is the code, if i use QueryParams validations work just fine, Please let me know the approach when i use pathparam
#Controller
#Path("/customer")
public class CustomerController extends BaseController implements Customer {
#Override
#GET
#Produces({ "application/json", "application/xml" })
#Path("/search/{name}/{id}/")
public Response searchCustomerDetails(
#PathParam("name") String name,
#PathParam("id") Integer id) {
ResponseBuilder response = null;
CustomerValidations validations = (CustomerValidations) getAppContext()
.getBean(CustomerValidations.class);
CustomerResponse customerResponse = new CustomerResponse();
CustomerService customerService = (CustomerService) getAppContext()
.getBean(CustomerService.class);
try {
validations.searchCustomerDetailsValidation(
name, id,customerResponse);
if (customerResponse.getErrors().size() == 0) {
CustomerDetails details = customerService
.searchCustomerDetailsService(name, id);
if (details == null) {
response = Response.status(Response.Status.NO_CONTENT);
} else {
customerResponse.setCustomerDetails(details);
response = Response.status(Response.Status.OK).entity(
customerResponse);
}
} else {
response = Response.status(Response.Status.BAD_REQUEST).entity(
customerResponse);
}
}
catch (Exception e) {
LOGGER.error(e.getMessage());
response = Response.status(Response.Status.INTERNAL_SERVER_ERROR);
}
return response.build();
} }
#Component
#Scope("prototype")
public class CustomerValidations {
public void searchCustomerDetailsValidation(
String name, Integer id,
CustomerResponse customerResponse) {
if (id == null) {
customerResponse.getErrors().add(
new ValidationError("BAD_REQUEST",
""invalid id));
}
if (name== null
|| (name!= null && name
.trim().length() == 0)) {
customerResponse.getErrors().add(
new ValidationError("BAD_REQUEST", "invalid id"));
}
} }
#XmlRootElement
public class CustomerResponse {
private CustomerDetails customerDetails;
private List<ValidationError> errors = new ArrayList<ValidationError>();
//setters and getters }
public class ValidationError {
private String status;
private String message;
public ValidationError() {
}
public ValidationError(String status, String message) {
super();
this.status = status;
this.message = message;
}
//setters and getters }
You're receiving an exception because you have no methods mapped to #Path("/search/{foo}/") or #Path("/search/"), so you should be getting a default 404 response as these paths are not really defined.
I'm not sure why you would want to validate these "missing" request paths though - it looks like this endpoint is intended to be used as a query endpoint so I'd suggest you use #RequestParam/query parameters to more RESTfully describe the search you're attempting. A path of search/{name}/{id} would suggest a specific resource which permanently lives at this URL, though in this case you're querying for customers on this controller.
I would propose you drop the /search path completely and just map query parameters onto the "root" of the Customer controller, so you get something like
#Controller
#Path("/customer")
public class CustomerController extends BaseController implements Customer {
#GET
#Produces({"application/json", "application/xml"})
public Response searchCustomerDetails(
#RequestParam("name") String name,
#RequestParam("id") Integer id) {
// Returns response with list of links to /customer/{id} (below)
}
#GET
#Produces({"application/json", "application/xml"})
#Path("/{id}")
public Response getCustomerDetails(#PathVariable("id") String id) {
// GET for specific Customer
}
}
Related
I've created a custom web service client by extending WebServiceGatewaySupport and also implement custom ClientInterceptor to log some request/response data.
I have to create new interceptor for every call because it has to store some data about the request.
The problem occurs when I make two or more calls to my client. The first request applies its own interceptor with its clientId. The second should do the same. But since both requests use the same WebServicetemplate in my client, the second request replaces the interceptor with its own, with its clientId there.
As a result, I should get the following output to the console:
Request: clientId-1
Request: clientId-2
Response: clientId-1
Response: clientId-2
But I got this:
Request: clientId-1
Request: clientId-2
Response: clientId-2
Response: clientId-2
Here is come code examples (just for understanding how it should work):
#Data
class Response {
private final String result;
public Response(String result) {
this.result = result;
}
}
#Data
class Request {
private final String firstName;
private final String lastName;
}
#Data
class Context {
private final String clientId;
}
#Data
class Client {
private final String clientId;
private final String firstName;
private final String lastName;
}
class CustomInterceptor extends ClientInterceptorAdapter {
private final String clientId;
public CustomInterceptor(String clientId) {
this.clientId = clientId;
}
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Request: " + clientId);
return true;
}
#Override
public boolean handleResponse(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Response: " + clientId);
return true;
}
#Override
public boolean handleFault(MessageContext messageContext) throws WebServiceClientException {
System.out.println("Error: " + clientId);
return true;
}
}
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor(context.getClientId())};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate().marshalSendAndReceive(request);
}
}
#Service
#RequiredArgsConstructor
class CustomService {
private final CustomClient customClient;
public String call(Request request, Context context) {
Response response = customClient.sendRequest(request, context);
return response.getResult();
}
}
#RestController
#RequestMapping("/test")
#RequiredArgsConstructor
class CustomController {
private final CustomService service;
public CustomController(CustomService service) {
this.service = service;
}
#PostMapping
public String test(#RequestBody Client client) {
Request request = new Request(client.getFirstName(), client.getLastName());
Context context = new Context(client.getClientId());
return service.call(request, context);
}
}
Is it possible to implement custom interceptors with some state for each call? Preferably without any locks on WebServicetemplate to avoid performance degradation.
Okay. I've found the solution for my case.
I've created an implementation of WebServiceMessageCallback and using it I'm saving data of each request not in interceptor but in WebServiceMessage's mime header.
#Data
class CustomMessageCallback implements WebServiceMessageCallback {
private final String clientId;
#Override
public void doWithMessage(WebServiceMessage message) throws IOException, TransformerException {
MimeHeaders headers = ((SaajSoapMessage) message).getSaajMessage().getMimeHeaders();
headers.addHeader("X-Client-Id", clientId);
}
}
And pass this callback in my client implementation:
#Component
class CustomClient extends WebServiceGatewaySupport {
public Response sendRequest(Request request, Context context) {
CustomInterceptor[] interceptors = {new CustomInterceptor()};
setInterceptors(interceptors);
return (Response) getWebServiceTemplate()
.marshalSendAndReceive(request, new CustomMessageCallback(context.getClientId()));
}
}
So now I can get this data while processing request/response/error via interceptor.
#Override
public boolean handleRequest(MessageContext messageContext) throws WebServiceClientException {
String clientId = ((SaajSoapMessage) messageContext.getRequest())
.getSaajMessage()
.getMimeHeaders()
.getHeader("X-Client-Id")[0];
System.out.println("Request: " + clientId);
return true;
}
I'm making an ajax call to a method that returns a list of object, if something happens while getting the data in a try-catch block I have a response.setStatus(400) to then show the error in the front-end, also there I'm returning null, there is where I'm getting the SonarLint notification. Now if I change that to an empty collection then I get below error:
getWriter() has already been called for this response
I think the above is because I'm returning the empty collection and the http response status 400. If I leave it null then all works fine, just that SonarLint notification.
#GetMapping("/runquery")
#ResponseBody
public List<Map<String, Object>> runQuery(#RequestParam(name = "queryId") String queryId, #RequestParam(name = "formData") String formData, HttpServletResponse response) throws IOException {
(...)
try {
queryResult = namedParameterJdbcTemplateHive.queryForList(query, paramSource);
for (Map<String, Object> map : queryResult) {
Map<String, Object> newMap = new HashMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (key.contains(".")) {
key = key.replace(".", "_");
newMap.put(key, value);
} else {
newMap.put(key, value);
}
}
queryResultFinal.add(newMap);
}
} catch (Exception e) {
response.setStatus(400);
response.getWriter().write(e.getMessage());
return null; <-- SonarLint notification
}
return queryResultFinal;
}
Any idea on how to fix this notification?
I would recommend not catching the exception in this method, but instead throw it, and use an exception handler method in your controller to handle it. In that case you will never return null from the method, and Sonar will have nothing to complain about. It will also mean that you are using Spring the way it is designed to be used.
For example, something like the following:
#ExceptionHandler
#ResponseStatus(HttpStatus.BAD_REQUEST)
public void handleException(Exception e) {
log.error("Exception during request", e);
}
or the direct equivalent of your current handling:
#ExceptionHandler
public ResponseEntity<?> handleException(Exception e) {
return ResponseEntity.badRequest().body(e.getMessage()).build();
}
You can remove the HttpServletResponse response parameter from your normal method after switching to an exception handler.
I would recommend you to create a GenericReponse that wraps all of your responses, it's pretty good for front-end also because you're facing with a fixed template.
So via this solution, you can wrap up any object you want and send it to the response.
I coded the scenario like this:
1- Create a GenericResponse Class
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class GenericResponse {
private Boolean error;
private List<ErrorPayload> errorPayload;
private Object payload;
public GenericResponse(Boolean error) {
this.error = error;
}
public static GenericResponse ok() {
return new GenericResponse(false);
}
public GenericResponse payload(Serializable o) {
this.payload = o;
return this;
}
//Getters and Setters and other Constructors
2-Create ErrorPayload Class
#JsonInclude(JsonInclude.Include.NON_NULL)
#JsonIgnoreProperties(ignoreUnknown = true)
public class ErrorPayload {
private String errorMessage;
private String errorType;
//Getters and Setters and Constructors
}
3-Create ExceptionConverter Service (Used when we have exception)
#Service
public class ExceptionConverterService {
public GenericResponse convert(Exception x) {
GenericResponse genericResponse = new GenericResponse();
genericResponse.setError(true);
String exceptionType = x.getClass().getSimpleName();
String exceptionMessage = x.getClass().getSimpleName();
genericResponse.setErrorPayload(Collections.singletonList(new ErrorPayload(exceptionType, exceptionMessage)));
return genericResponse;
}
}
4-Change Your scenario with GenericResponse
All you need to do is:
Create aforementioned classes (Copy the code that I wrote in 1, 2 and 3)
Change your response form List<Map<String, Object>> to GenericResponse
Wrap your return types into GenericResponse
I changed your code as follows (Just change 3 lines)
#RestController
public class TestController {
#Autowired
private ExceptionConverterService exceptionConverter;
#GetMapping("/runquery")
#ResponseBody
//Changed (Change Return type to GenericResponse )
public GenericResponse runQuery(#RequestParam(name = "queryId") String queryId, #RequestParam(name = "formData") String formData, HttpServletResponse response) throws IOException {
try {
//Your code
}
} catch (Exception e) {
//Changed (Create GenericResponse for Exception)
GenericResponse genericResponse = exceptionConverter.convert(e);
return genericResponse;
}
//Changed (Create GenericResponse for main result)
return GenericResponse.ok().payload(queryResultFinal);
}
}
Examples for two scenarios (first, without exception and the second with exception)
Sample 1
Controller with GenericResponse (We have no exception in this sample)
#RestController
public class TestController {
#GetMapping(value = "/getNameAndFamily")
public GenericResponse getNameAndFamily() {
Map<String, String> person = new HashMap<>();
person.put("name", "foo");
person.put("family", "bar");
return GenericResponse.ok().payload((Serializable) person);
}
}
The result is like as follows:
{
"error": false,
"payload": {
"name": "foo",
"family": "bar"
}
}
Sample 2
controller with GenericResponse when we have Exception in business
#RestController
public class TestController {
#Autowired
private ExceptionConverterService exceptionConverter;
#GetMapping(value = "/getNameAndFamily")
public GenericResponse getNameAndFamily() {
try {
//Create Fake Exception
int i = 1 / 0;
return GenericResponse.ok();
} catch (Exception e) {
//Handle Exception
GenericResponse genericResponse = exceptionConverter.convert(e);
return GenericResponse.ok().payload((Serializable) genericResponse);
}
}
}
The result is as follows:
{
"error": true,
"errorPayload": [
{
"errorType": "ArithmeticException"
}
]
}
Here is my Authentication filter :
public class AuthenticationFilter implements ContainerRequestFilter {
private static Logger logger = LoggerFactory.getLogger(AuthenticationFilter.class);
#Autowired
private AuthenticationService service;
#Override
public void filter(ContainerRequestContext context) throws IOException {
String token = context.getHeaderString("mytoken");
if (token == null || token.isEmpty()) {
String message = "token.null_empty";
logger.warn("{}: Token is required to access to personal data (resource {}) => reject request", message, context.getUriInfo().getBaseUri());
throw new AuthenticationException(ErU2, message);
}
// check token and retrieve user information
logger.debug("Checking validity of token {}", token);
IUser user = this.service.getUser(token);
logger.warn("======= AuthenticationFilter#filter token={}, user={}", token, user);
// set user data as request property
context.setProperty("user", user);
logger.warn("======= AuthenticationFilter#filter token={}, user#context.setProperty={}", token, (IUser) context.getProperty("user"));//In this line ihave user not null
}
}
#Path("perso")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON + ";charset=UTF-8")
#Component
public class PersonalSpace {
private static Logger logger LoggerFactory.getLogger(PersonalSpace.class);
#Context
private ContainerRequestContext context;
private IUser getUser() {
logger.debug("[IN] PersonalSpace#getUser");
IUser user = (IUser) this.context.getProperty("user");//This returns null
logger.warn("======= PersonalSpace#getUser user={}", user);
return user;
}
#GET
#Path("/skanso/data")
public Response getData(#QueryParam("start") Integer start, #QueryParam("number") Integer number,
List<Integer> ids) {
return dataOperator.getDataLimitedList(getUser(), start, number, ids);
}
}
This code works nicely when i test with single request , but on concurrent request this.context.getProperty("user") returns null.
Is there à solution threadSafe or something like that ?
Please save my hair :)
Can I do something like this with Spring MVC ?
#RequestMapping(value = "/{root}")
public abstract class MyBaseController {
#PathVariable(value = "root")
protected ThreadLocal<String> root;
}
#Controller
public class MyController extends MyBaseController {
#RequestMapping(value = "/sayHello")
#ResponseBody
public String hello() {
return "Hello to " + this.root.get();
}
}
When I request to http://..../roberto/sayHello, I get this as response:
Hello to roberto
You can have a path variable in the controller URL-prefix template like this:
#RestController
#RequestMapping("/stackoverflow/questions/{id}/actions")
public class StackOverflowController {
#GetMapping("print-id")
public String printId(#PathVariable String id) {
return id;
}
}
so that when a HTTP client issues a request like this
GET /stackoverflow/questions/q123456/actions/print-id HTTP/1.1
the {id} placeholder is resolved as q123456.
you can code like this:
#RequestMapping("/home/{root}/")
public class MyController{
#RequestMapping("hello")
public String sayHello(#PathVariable(value = "root") String root, HttpServletResponse resp) throws IOException {
String msg= "Hello to " + root;
resp.setContentType("text/html;charset=utf-8");
resp.setCharacterEncoding("UTF-8");
PrintWriter out = resp.getWriter();
out.println(msg);
out.flush();
out.close();
return null;
}
}
and the result like this:
and,you can use ModelAndView return msg value to the jsp or other html page.
According to the docs:
http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html
the PathVariable annotation is itself annotated with #Target(value=PARAMETER) so it shouldn't be possible to be used the way you're saying as it's only applicable to method parameters.
I'm trying to use #DELETE request after a made some simple web application which I've tested using soapui. With this application I can add and get users/book to database. Now I'm trying to made a #DELETE request but I can't make it. Here is the code:
//UserServiceImpl
#PersistenceContext
private EntityManager em;
#Override
public void deleteUser(Long id) {
if (null == id || id.longValue() < 1) {
throw new IllegalArgumentException(" User id can not be null or less than zero. ");
}
User u = em.find(User.class, id);
em.remove(u);
}
//UserResource
#Autowired
private UserService userService;
#DELETE
#Path("/delete/{id}")
public Response deleteUser(#PathParam("id") String id) {
Response response;
try {
User user = userService.deleteUser(Long.valueOf(id));//here is the error
if (user != null) {
response = Response.status(HttpServletResponse.SC_OK).entity(user).build();
} else {
response = Response.status(HttpServletResponse.SC_NOT_FOUND).build();
}
} catch (IllegalArgumentException e) {
response = Response.status(HttpServletResponse.SC_NOT_FOUND).build();
}
return response;
}
I`ve fix my problem. The delete method which is in UserServiceImpl must not be void.... it must be public User deleteUser(Long id). The other delete method in Resource class ... just need to be of void type. There i do not use Response and i simply print the result like this:
System.out.print(Response.status(HttpServletResponse.SC_OK).entity(user).build());