Here is a scenario.
Case1: (#QueryParam("username") String username)
URL: example?username=yourname
Case2: (#QueryParam("username") String name)
URL: example?username=yourname
In these 2 cases which is correct way of using QueryParam.
When I use Case1, it works, yourname is printed. But when I use Case2, null is printed.
I want to implement Case 2 because of following some coding standards. Means I do not want to change the variable name (which is "String name"). But in URL I have to use "username".
Is there any way of using Case2 scenario for QueryParam.
Adding code(1st edit)
Here is the code which I replicated the issue that I am facing
#RequestMapping (value = "/username1", method = RequestMethod.GET)
#ResponseBody
public Response username1(#QueryParam("username") String username) {
System.out.println("Username1 is ==> " + username);
return Response.ok(username).build();
}
#RequestMapping (value = "/username2", method = RequestMethod.GET)
#ResponseBody
public Response username2(#QueryParam("username") String name) {
System.out.println("Username2 is ==> " + name);
return Response.ok(name).build();
}
/username1?username=yourname
Output: Username1 is ==> yourname
/username2?username=yourname
Output: Username2 is ==> null
Thank you
Yes, you're doing something wrong: you're using QueryParam, which is a JAX-RS annotation, in a Spring-MVC application.
The equivalent Spring annotation is RequestParam. JAX-RS and Spring MVC are two different things. You can't just use the annotations of one in the other.
Related
Using Mockito version 4.8.0
The controller method I need to test
#GetMapping(value = "getStringBuiltByComplexProcess")
public String getStringBuiltByComplexProcess(#RequestParam String firstName, #RequestParam String lastName ) {
Author a = new Author();
return a.methodWhichMakesNetworkAndDatabaseCalls(firstName, lastName);
}
here is the test method
#Test
public void testGetStringBuiltByComplexProcess01() {
final String firstName = "firstName";
final String lastName = "lastName";
try (MockedConstruction<Author> mock = mockConstruction(Author.class)) {
Author authorMock = new Author();
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
fails with a message of
org.opentest4j.AssertionFailedError: strings should match ==> expected: <when worked> but was: <null>
In this simplified example the controller method has more code but the core of what is not working is mocking the object which the controller method constructs.
The object you create on line
Author authorMock = new Author();
is different than the one created in the getBooksByAuthor() function. A debugger should show you that.
You can use mock.constructed().get(0) to get the object created in getBooksByAuthor(), but by the time you can do this, getBooksByAuthor() has already finished and you can't do much with that mock.
It's not exactly clear what your objective is. I guess you want to check that the Author object is created in a certain way, and the lines involving getFullName() aren't part of the actual code, just something you added to experiment, because they don't do anything.
If you want to verity that the object passed to dataAccessService satisfies some conditions, what you need is an ArgumentCaptor. Something like
ArgumentCaptor<Author> authorCaptor = ArgumentCaptor.forClass(Author.class);
when(dataAccessServiceMock.getBooks(authorCaptor.capture())).thenReturn(books);
List<Book> result = ut.getBooksByAuthor(firstName, lastName);
Author author = authorCaptor.value();
assertEquals(firstName, author.getFirstName());
If you use MockInitializer for stubbing , it should solve your problem :
try (MockedConstruction<Author> mocked = mockConstruction(Author.class, (mock, context) -> {
when(authorMock.methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName))).thenReturn("when worked");
})) {
assertEquals("when worked", ut.getStringBuiltByComplexProcess(firstName, lastName), "Strings should match");
verify(authorMock).methodWhichMakesNetworkAndDatabaseCalls(eq(firstName), eq(lastName));
}
}
But a much better way to test the controller is to use MockMvc. It allows you to test for a given HTTP request , do you configure spring-mvc properly such that it can do the following things correctly :
if the HTTP request can be parsed properly to execute the expected controller method with the expected paramater
if it can deserialize the object returned from the controller method into a expected JSON structure.
it allows to configure the current user who make the HTTP call and verify if he has enough permission to call this API and if not, will it return the expected error response
etc.
All of these things cannot be tested by your fragile mocking constructor approach.
For more details about MockMvc, refer to this guide.
I have some problem with slashes in variable thats hould be send in url.
For example id could be like this:
ID: /gfdge1/DkxA8P+jYw43
URL: localhost:8080/find-user//gfdge1/DkxA8P+jYw43
"error": "Internal Server Error",
"exception": "org.springframework.security.web.firewall.RequestRejectedException",
"message": "The request was rejected because the URL was not normalized.",
because of this slash on first place it make problem
Before that i had problems with these slash in middle of ID but I've solved that with this code:
#RequestMapping(name = "Get user data by ID", value = "/find-user/{userId}/**", method = RequestMethod.GET)
#ResponseBody
public User getUserData(#PathVariable String userId, HttpServletRequest request) {
final String path =
request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE).toString();
final String bestMatchingPattern =
request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
String arguments = new AntPathMatcher().extractPathWithinPattern(bestMatchingPattern, path);
String id="";
if (null != arguments && !arguments.isEmpty()) {
id = userId + '/' + arguments;
} else {
id = userId;
}
return userService.getUserData(id);
}
but this doesn't work for this case when slash is on first place.
I've also try to user RequestParam instead of PathVariable, but it have problems with some special characters for example when I user RequestParam it replace '+' with empty space,...
Does anyone can help me how to solve this problem?
Its an inherent issue with using Strings as path variables, it's not an issue with your code but how the HTTP request is interpreted so you can't do anything in your code to make this work.
You do have some options though:
Ensure the values you use cannot be created with special characters such as "/"
Avoid using Strings in path variables completely.
I lean more towards 2 as maintaining 1 for all possible problem characters/strings is pretty messy and unnecessary.
To do 2 correctly you should consider having all your REST getters finding their related entities by a numeric ID only e.g.
localhost:8080/find-user/3
If you need to add additional search parameters e.g. username in your case then you should use something like QueryDSL to create a predicate of search parameters which are passed as query parameters instead of path variables e.g.:
localhost:8080/find-user?username=/gfdge1/DkxA8P+jYw43
I am trying to implement datatable editor with spring boot ,but the client to server data varies for create ,update and delete even not constant for create as well and depends on fields
I have implemented this till now
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(HttpServletRequest request)
{
Enumeration<String> params = request.getParameterNames();
while(params.hasMoreElements()){
String paramName = params.nextElement();
System.out.println("Parameter Name - "+paramName+", Value - "+request.getParameter(paramName));
}
//System.out.println(data);
//System.out.println(request.);
//Map<String,String>ak=new HashMap<>();
//ak.put("data", "hello ");
return "done";
}
Above code prints following output on console for create
Parameter Name - action, Value - create
Parameter Name - data[0][username], Value - dddddd
Parameter Name - data[0][date], Value - 2018-11-28
Parameter Name - data[0][balance], Value - dddddddddd
and this for edit
Parameter Name - action, Value - edit
Parameter Name - data[5bfab595507af613f409c0c4][username], Value - four
Parameter Name - data[5bfab595507af613f409c0c4][date], Value - 2018-11-25
Parameter Name - data[5bfab595507af613f409c0c4][balance], Value - 9000.0
The only constant parameter here is action and so I can use
#RequestParam("action")
but how to get rest data ?? something like #RequestParam() String data
You can create a DTO class which can be mapped from the request and can be used further.
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(HttpServletRequest request)
{
UserDTO object = new ObjectMapper().setDateFormat(simpleDateFormat).readValue(request.getReader(), UserDTO.class);
performYourOperation(object);
}
I see we should utilize REST in more richer way here.
So create three different controller method handlling create, update and delete and maps them to difference HTTP methods like below :
//For Create. Take the parameter as (#RequestBody List<User>)
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
//For Update/Edit, Take the parameter as (#RequestBody List<User>)
#RequestMapping(value="/datatabledata" , method=RequestMethod.PUT)
//For Delete, Just take either list of ids or id to be delete. Nothiing else required
#RequestMapping(value="/datatabledata" , method=RequestMethod.DELETE)
Now you don't need action as parameter. Client just need to specify the correct http method.
You should use #RequestParam Map<String,String> allRequestParams in your endpoint:
#RequestMapping(value="/datatabledata" , method=RequestMethod.POST)
#ResponseBody
public String datatabledata(#RequestParam Map<String,String> allRequestParams) {
/ ... rest of your code
}
Requirement is to write a controller to handle POST requests from the below urls:
http://hostname:port/com/prod1?id=2&action=add
http://hostname:port/com/prod1?id=2&action=minus
http://hostname:port/com/prod2?id=2&action=add
http://hostname:port/com/prod2?id=2&action=minus
Can I have two methods, one for mapping urls with action=add, and another for urls with action=minus? All the requests are POST.
A couple years between question and answer, but yes it is possible to route based on query parameters with Spring MVC:
#RequestMapping(path = "/com/{product}", params = "add")
String add(#PathVariable("product") String product) {
System.out.println("add method; product=" + product);
return "add";
}
#RequestMapping(path = "/com/{product}", params = "minus")
String minus(#PathVariable("product") String product) {
System.out.println("minus method; product=" + product);
return "minus";
}
No to my knowledge. According to your url end points,
they are,
/com/prod1
/com/prod2
For these you can have 2 controller methods for each of these.
EDIT:
If I understand you correctly,
Instead of having the above end points, write 2 controller methods for request mappings,
com/add
com/minus
If you want to have it in a more granular manner, then
com/prod1/add
com/prod1/minus
com/prod2/add
com/prod2/minus
write request mapping methods for the above.
Then you have,
http://hostname:port/com/prod1/add?id=2
http://hostname:port/com/prod1/minus?id=2
http://hostname:port/com/prod2/add?id=2
http://hostname:port/com/prod2/munus?id=2
Or you can use another approach. which is, Just use the generic end points and depending on your request parameters, redirect to different request mappings such as,
com/add
com/minus
You cannot have two #RequestMapping methods to map to two URLs differing only by query parameters. The #RequestMapping only binds to the path portion of the URL.
You can have two methods if you dispatch on the action value, calling one method for add and another for minus.
Or, you can make add or minus a part of the URL path.
It is possible with the following code:
#GetMapping(path = "/com/{product}", params = "action=add")
public String add(#PathVariable("product") String product) {
System.out.println("add method; product=" + product);
return "add";
}
#GetMapping(path = "/com/{product}", params = "action=minus")
public String minus(#PathVariable("product") String product) {
System.out.println("minus method; product=" + product);
return "minus";
}
I am trying to write a Spring REST Controller getting an array of strings as input parameter of a HTTP GET request.
The problem arises when in the GET request, in some of the strings of the array, I use special characters like commas ,, blank spaces or forward slash /, no matter if I URL encode the query part of the URL HTTP GET request.
That means that the string "1/4 cup ricotta, yogurt" (edit which needs to be considered as a unique ingredient contained as a string element of the input array) in either this format:
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1/4 cup ricotta, yogurt
This format (please note the blank spaces encoded as + plus, rather than the hex code):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4+cup+ricotta%2C+yogurt
Or this format (please note the blank space encoded as hex code %20):
http://127.0.0.1:8080/[...]/parseThis?[...]&ingredients=1%2F4%20cup%20ricotta%2C%20yogurt
is not rendered properly.
The system does not recognize the input string as one single element of the array.
In the 2nd and 3rd case the system splits the input string on the comma and returns an array of 2 elements rather than 1 element. I am expecting 1 element here.
The relevant code for the controller is:
#RequestMapping(
value = "/parseThis",
params = {
"language",
"ingredients"
}, method = RequestMethod.GET, headers = HttpHeaders.ACCEPT + "=" + MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public HttpEntity<CustomOutputObject> parseThis(
#RequestParam String language,
#RequestParam String[] ingredients){
try {
CustomOutputObject responseFullData = parsingService.parseThis(ingredients, language);
return new ResponseEntity<>(responseFullData, HttpStatus.OK);
} catch (Exception e) {
// TODO
}
}
I need to perform HTTP GET request against this Spring controller, that's a requirement (so no HTTP POST can be used here).
Edit 1:
If I add HttpServletRequest request to the signature of the method in the controller, then I add a log statement like log.debug("The query string is: '" + request.getQueryString() + "'"); then I am seeing in the log a line like The query string is: '&language=en&ingredients=1%2F4+cup+ricotta%2C+yogurt' (So still URL encoded).
Edit 2:
On the other hand if I add WebRequest request to the signature of the method, the the log as log.debug("The query string is: '" + request.getParameter("ingredients") + "'"); then I am getting a string in the log as The query string is: '1/4 cup ricotta, yogurt' (So URL decoded).
I am using Apache Tomcat as a server.
Is there any filter or something I need to add/review to the Spring/webapp configuration files?
Edit 3:
The main problem is in the interpretation of a comma:
#ResponseBody
#RequestMapping(value="test", method=RequestMethod.GET)
public String renderTest(#RequestParam("test") String[] test) {
return test.length + ": " + Arrays.toString(test);
// /app/test?test=foo,bar => 2: [foo, bar]
// /app/test?test=foo,bar&test=baz => 2: [foo,bar, baz]
}
Can this behavior be prevented?
The path of a request parameter to your method argument goes through parameter value extraction and then parameter value conversion. Now what happens is:
Extraction:
The parameter is extracted as a single String value. This is probably to allow simple attributes to be passed as simple string values for later value conversion.
Conversion:
Spring uses ConversionService for the value conversion. In its default setup StringToArrayConverter is used, which unfortunately handles the string as comma delimited list.
What to do:
You are pretty much screwed with the way Spring handles single valued request parameters. So I would do the binding manually:
// Method annotations
public HttpEntity<CustomOutputObject> handlerMethod(WebRequest request) {
String[] ingredients = request.getParameterValues("ingredients");
// Do other stuff
}
You can also check what Spring guys have to say about this.. and the related SO question.
Well, you could register a custom conversion service (from this SO answer), but that seems like a lot of work. :) If it were me, I would ignore the declaration the #RequestParam in the method signature and parse the value using the incoming request object.
May I suggest you try the following format:
ingredients=egg&ingredients=milk&ingredients=butter
Appending &ingredients to the end will handle the case where the array only has a single value.
ingredients=egg&ingredients=milk&ingredients=butter&ingredients
ingredients=milk,skimmed&ingredients
The extra entry would need to be removed from the array, using a List<String> would make this easier.
Alternatively if you are trying to implement a REST controller to pipe straight into a database with spring-data-jpa, you should take a look at spring-data-rest. Here is an example.
You basically annotate your repository with #RepositoryRestResource and spring does the rest :)
A solution from here
public String get(WebRequest req) {
String[] ingredients = req.getParameterValues("ingredients");
for(String ingredient:ingredients ) {
System.out.println(ingredient);
}
...
}
This works for the case when you have a single ingredient containing commas