I'm trying to catch key value pairs in a Map parameter at spring MVC side. This looks to me to be something simple but I can't wrap my head around it at the moment. Take following url
www.goudengids.be.localhost:8080/ms/view/sendContactForm.ajah?pageId=711408&listingId=685592&siteId=353009&url=http%3A%2F%2Fwww.goudengids.be.localhost%3A8080%2Fms%2Fms%2Fkbc-bank-versicherung-recht-4780%2Fms-353009-preview%2F&moduleId=65920100&mySiteId=353009&pageShortId=1&prefills[Naam]=janneke
You'll notice at the end my latest attempt to get this working prefills[Naam]=janneke. I like to catch this in the following controller.
public String getContactForm(#RequestParam(required = true) Long moduleId, #RequestParam(required = true) String url, #RequestParam(required=false) Map<String,String> prefills, Long mySiteId, Integer pageShortId,
DefaultPageParameters defaultPageParameters, ModelMap model, HttpServletRequest request, HttpServletResponse response, Locale locale) throws Exception {
However I'm recieving all parameters in the request in my prefills variable instead of just Naam,janneke. Is this even possible what I'm attempting or should I go with a large string with a token to tokenize ?
prefills=naam:janneke|title:maan|subject:space
I couldn't find a clean way out, so I went for the pragmatic solution
public String getContactForm(#RequestParam(required = true) Long moduleId, #RequestParam(required = true) String url, #RequestParam(required=false) List<String> prefills, Long mySiteId, Integer pageShortId,
DefaultPageParameters defaultPageParameters, ModelMap model, HttpServletRequest request, HttpServletResponse response, Locale locale) throws Exception {
private void prefillFieldsWithData(List<String> prefills, ModelMap model, BasicContactFormVo contactFormVo) {
if(prefills != null && !prefills.isEmpty()){
Map<String, String> valuesOfCustomFields = new HashMap<String, String>();
List<ContactFormElementVo> customFormElements = contactFormVo.getCustomFormElements();
for (String prefillData : prefills) {
if(prefillData.contains("|")){
String[] prefillFieldData = prefillData.split("|");
for (ContactFormElementVo contactFormElementVo : customFormElements) {
if(contactFormElementVo.getLabel().equals(prefillFieldData[0])){
valuesOfCustomFields.put("cfe"+contactFormElementVo.getId().toString(), prefillFieldData[1]);
break;
}
}
}
}
model.addAttribute("customFieldValues",valuesOfCustomFields);
}
}
It's a bit sad I have to do it this way, but it seems Spring has a generic way of detecting a Map as request parameter and filling it with all parameters in the request. There is no way around that except overloading that class which I rather do not considering it's part of the entire MVC mechanic.
I call the controller now with following URL. It works just ... meh ... not my favorite solution
http://www.goudengids.be.localhost:8080/ms/view/sendContactForm.ajah?pageId=711408&listingId=685592&siteId=353009&url=http%3A%2F%2Fwww.goudengids.be.localhost%3A8080%2Fms%2Fms%2Fkbc-bank-versicherung-recht-4780%2Fms-353009-preview%2F&moduleId=65920100&mySiteId=353009&pageShortId=1&prefills=Naam|janneke%20zag%20eens%20pruimen&prefills=E-mail|maan
Related
I'm working on a java spring mvc application. I have set a cookie in one of my controller's methods in this way:
#RequestMapping(value = {"/news"}, method = RequestMethod.GET)
public ModelAndView news(Locale locale, Model model, HttpServletResponse response, HttpServletRequest request) throws Exception {
...
response.setHeader("Set-Cookie", "test=value; Path=/");
...
modelAndView.setViewName("path/to/my/view");
return modelAndView;
}
This is working fine and I can see a cookie with name test and value "value" in my browser console. Now I want to get the cookie value by name in other method. How can I get value of test cookie?
The simplest way is using it in a controller with the #CookieValue annotation:
#RequestMapping("/hello")
public String hello(#CookieValue("foo") String fooCookie) {
// ...
}
Otherwise, you can get it from the servlet request using Spring org.springframework.web.util.WebUtils
WebUtils.getCookie(HttpServletRequest request, String cookieName)
By the way, the code pasted into the question could be refined a bit. Instead of using #setHeader(), this is much more elegant:
response.addCookie(new Cookie("test", "value"));
You can also use org.springframework.web.util.WebUtils.getCookie(HttpServletRequest, String).
private String getCookieValue(HttpServletRequest req, String cookieName) {
return Arrays.stream(req.getCookies())
.filter(c -> c.getName().equals(cookieName))
.findFirst()
.map(Cookie::getValue)
.orElse(null);
}
Spring MVC already gives you the HttpServletRequest object, it has a getCookies() method that returns Cookie[] so you can iterate on that.
private String extractCookie(HttpServletRequest req) {
for (Cookie c : req.getCookies()) {
if (c.getName().equals("myCookie"))
return c.getValue();
}
return null;
}
Cookie doesnt have method to get by value try this
Cookie cookie[]=request.getCookies();
Cookie cook;
String uname="",pass="";
if (cookie != null) {
for (int i = 0; i < cookie.length; i++) {
cook = cookie[i];
if(cook.getName().equalsIgnoreCase("loginPayrollUserName"))
uname=cook.getValue();
if(cook.getName().equalsIgnoreCase("loginPayrollPassword"))
pass=cook.getValue();
}
}
I have a controller mapping, where I pass 2 request params instead of 1. But when done like that Spring is not throwing any exception rather it is ignoring the additional request params.
For eg:
#RequestMapping(value="/test", method = RequestMethod.GET)
public ModelAndView eGiftActivation(#RequestParam("value") String value)
When I hit my app using /test.do?value=abcd it is working fine. But when I pass additional params like /test.do?value=abcd&extra=unwanted also it's working fine.
In this case I want Spring to restrict the second URL where additional params are passed.
How can I achieve this?
You could check it manually, like this:
#RequestMapping("/test")
public ModelAndView eGiftActivation(HttpServletRequest request) {
Map<String, String[]> params = request.getParameterMap();
if (params.size() != 1 || !params.containsKey("value")) {
throw new RuntimeException("Extra parameters are present"); // or do redirect
}
...
}
I don't think it's possible (For Spring to prevent the request to flow to any controller's method). The reason being that:
Your controller handles request based on the URI path like, /app/hello/{name} rather than the request parameters
Request parameters are there to give extra set of meta-info for the request rather than endpoint specification of request.
But, if you wanted to restrict the URI path as such, you can use regex and you can avoid. I'm afraid it's not feasible and even the requirement for that never arose.
Programmatical Way:
Having said that, you can take HttpServletRequest for parameters and loop through the parameters to check for extra ones:
#RequestMapping(value="/test", method = RequestMethod.GET)
public Object eGiftActivation(#RequestParam("value") String value, HttpServletRequest request){
//check the request.getParameterMap() and throw custom exception if you need and handle using Exception handler or throw invalid request
return new ResponseEntity<String>(HttpStatus.SC_BAD_REQUEST);
}
I prefer handling these kind of validations (if required, what ever may be the reason) inside the Filter generically so that the requests will not even reach the Controller methods.
Please find the required code to handle inside the Filter as below (logic is almost similar to Slava).
#Component
public class InvalidParamsRequestFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
Map<String, String[]> params = request.getParameterMap();
if (request.getRequestURI().contains("/test") && (params.size() != 1 || !params.containsKey("value"))) {
//Here, Send back the Error Response OR Redirect to Error Page
} else {
filterChain.doFilter(request, response);
}
}
}
i am mocking a HttpServletRequest , in servlet call there are new values getting set in request because using same request we are dispaching request to some jsp so request object is used as a input object to servlet as well as output for next page.
i mocked all input parameters , but for all request.setAttribute() , my code is doing nothing as it's a mocked class , say if i have
request.setAttribute(a,"10")
System.out.println("a = " + request.getAttribute("a"));
i get null cuz i haven't given any behavious for Request.getAttribute("a") , and i can't , it's my response for next page , so that explain i need 2 behaviour my request object thus partial mocking , and i am unable to spy or do any partial mocking on it so far. any ideas?
Code :
//Testcase
Myservlet.java
public void doPost(request,response)
{
String a = request.getAttribute("a");
String b = request.getAttribute("b");
int sum = Integer.parseInt(a) + Integer.parseInt(b);
request.setAttribute("sum",sum);
//well in this example i can use sum what i calculated but in real senario i can't , i have to use request.getAttribute("sum")
insertSumIntoDB(request.getAttribute("sum"));
}
}
//testMyservlet.java
#test
public void testServlet()
{
HttpServletRequest request = mock(HttpServletRequest.class);
HttpServletResponse response = mock(HttpServletResponse.class);
when(request.getAttribute(a)).thenReturn("10");
when(request.getAttribute(b)).thenReturn("20");
new Myservlet(request,response);
}
Spring's MockHttpServletRequest and MockHttpServletResponse are useful for that purpose. E.g.
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
request.addHeader(HttpHeaders.HOST, "myhost.com");
request.setLocalPort(PORT_VALID); // e.g. 8081
request.setRemoteAddr(REQUEST_IP); // e.g. 127.0.0.1
then I can call myclass.method(request, response, ...) and check whether some attribute has been correctly set into the request, e.g.
MyBean attr = (MyBean) request.getAttribute(ATTRIBUTE_NAME));
// do my Assert.* stuff with 'attr'
MockHttpServletRequest and MockHttpServletResponse work fine where mock(HttpServletRequest.class) fails, for instance where you need to get back the real content of a request attribute which has been previously set within your business logic.
You need to store attributes into a collection :
// Collection to store attributes keys/values
final Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
// Mock setAttribute
Mockito.doAnswer(new Answer<Void>() {
#Override
public Void answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = invocation.getArgumentAt(1, Object.class);
attributes.put(key, value);
System.out.println("put attribute key="+key+", value="+value);
return null;
}
}).when(request).setAttribute(Mockito.anyString(), Mockito.anyObject());
// Mock getAttribute
Mockito.doAnswer(new Answer<Object>() {
#Override
public Object answer(InvocationOnMock invocation) throws Throwable {
String key = invocation.getArgumentAt(0, String.class);
Object value = attributes.get(key);
System.out.println("get attribute value for key="+key+" : "+value);
return value;
}
}).when(request).getAttribute(Mockito.anyString());
Mockito supports real partial mocks: Real partial mocks (Since 1.8.0)
I think it fits your needs
I saw related questions and tried those none of those helped. I'm sending POST requst with jquery like this :
var data = {};
//this works every time and it's not issue
var statusArray = $("#status").val().split(',');
var testvalue = $("#test").val();
data.test = testvalue;
data.status = statusArray ;
$.post("<c:url value="${webappRoot}/save" />", data, function() {
})
On the controller side I've tried following :
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestBody String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam("status") String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
public void saveStatus(ModelMap model, Principal principal, HttpSession session, final HttpServletResponse response, #RequestParam String test, #RequestParam(name="status") String [] status) {
//I never get to this point, but when I set statusArray to required false test variable is being populated correctly
}
none of these worked I'm wondering what I'm doing wrong, whatever I do I get Bad request
Your status param should be #RequestParam(value = "status[]") String[] status (Spring 3.1).
I have also gone through the same problem of Bad request. I resolved it by doing the following code.
You can post an array to a controller by converting it into json string by JSON.stringify(array).
I have pushed muliple Objects into an array using push().
var a = [];
for(var i = 1; i<10; i++){
var obj = new Object();
obj.name = $("#firstName_"+i).val();
obj.surname = $("#lastName_"+i).val();
a.push(obj);
}
var myarray = JSON.stringify(a);
$.post("/ems-web/saveCust/savecustomers",{myarray : myarray},function(e) {
}, "json");
Controller :
You can use jackson for processing json string.
Jackson is a High-performance JSON processor Java library.
#RequestMapping(value = "/savecustomers", method = RequestMethod.POST)
public ServiceResponse<String> saveCustomers(ModelMap model, #RequestParam String myarray) {
try{
ObjectMapper objectMapper = new ObjectMapper().configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
List<DtoToMAP> parsedCustomerList = objectMapper.readValue(myarray, new TypeReference<List<DtoToMAP>>() { });
System.out.println(" parsedCustomerList :: " + parsedCustomerList);
}catch (Exception e) {
System.out.println(e);
}
}
Note : make sure that your dto should contain same variable name as you are posting with an array object.
in my case, my dto contains firstName,lastName as am posting with an array object.
Jackson Dependancy :
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.3</version>
</dependency>
I think your problem might be that to send an array to something you have to actually send the param multiple times.
In the case of a GET operation something like: ?status=FOO&status=BAR
I'm not sure spring would convert a comma separated string to an array for you automagically. You could however, add a PropertyEditor (see PropertyEditorSupport) to split the string on commas.
#InitBinder
public void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(String[].class, new PropertyEditorSupport() {
#Override
public String getAsText() {
String value[] = (String[]) getValue();
if (value == null) {
return "";
}
else {
return StringUtils.join(value, ",");
}
}
#Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.trim().length() == 0) {
setValue(null);
}
else {
setValue(StrTokenizer.getCSVInstance(text).getTokenArray());
}
}
});
}
Note, i'm using commons-lang to both join and split the string, but you could easily just do it yourself using any means you want.
Once you do this, any time you want a parameter bound to a String[] from a single string, spring will automatically convert it for you.
In one of the few questions (with answers) I have found on SO regarding JAX-RS and caching, the answer to generating ETags (for caching) is by setting some values on the Response object. As in the following:
#GET
#Path("/person/{id}")
public Response getPerson(#PathParam("id") String name, #Context Request request){
Person person = _dao.getPerson(name);
if (person == null) {
return Response.noContent().build();
}
EntityTag eTag = new EntityTag(person.getUUID() + "-" + person.getVersion());
CacheControl cc = new CacheControl();
cc.setMaxAge(600);
ResponseBuilder builder = request.evaluatePreconditions(person.getUpdated(), eTag);
if (builder == null) {
builder = Response.ok(person);
}
return builder.cacheControl(cc).lastModified(person.getUpdated()).build();
}
The problem is that will not work for us, since we use the same methods for both SOAP and REST services, by annotating the methods with #WebMethod (SOAP), #GET (and whatever else we might need to expose the service). The previous service would look like this to us (excluding the creation of headers):
#WebMethod
#GET
#Path("/person/{id}")
public Person getPerson(#WebParam(name="id") #PathParam("id") String name){
return _dao.getPerson(name);
}
Is there any way - through some extra configuration - of setting those headers? This is the first time I have found that using Response objects actually has some benefit over just auto-conversion ...
We are using Apache CXF.
Yes you might be able to use interceptors to achieve this if you could generate the E-tag AFTER you create your response object.
public class MyInterceptor extends AbstractPhaseInterceptor<Message> {
public MyInterceptor () {
super(Phase.MARSHAL);
}
public final void handleMessage(Message message) {
MultivaluedMap<String, Object> headers = (MetadataMap<String, Object>) message.get(Message.PROTOCOL_HEADERS);
if (headers == null) {
headers = new MetadataMap<String, Object>();
}
//generate E-tag here
String etag = getEtag();
//
String cc = 600;
headers.add("E-Tag", etag);
headers.add("Cache-Control", cc);
message.put(Message.PROTOCOL_HEADERS, headers);
}
}
If that way isn't viable, I would use the original solution that you posted, and just add your Person entity to the builder:
Person p = _dao.getPerson(name);
return builder.entity(p).cacheControl(cc).lastModified(person.getUpdated()).build();
or it can be as simple as sending back an "error" code... depending on what you want to do.
#Path("/{id}")
#GET
#Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public ProductSearchResultBean getProductById(#PathParam("id") Integer productId, #QueryParam("expand") List<String> expand, #Context HttpServletRequest request, #Context HttpServletResponse response) throws IOException {
ProductSearchResultBean productDetail = loadProductDetail(productId, expand);
EntityTag etag = new EntityTag(((Integer)(productDetail.toString().hashCode())).toString());
String otherEtag = request.getHeader("ETag");
if(etag.getValue().equals(otherEtag)){
response.sendError(304, "not Modified");
}
response.addHeader("ETag", etag.getValue());
return productDetail;
}
That's how I tackled the issure anyway. Good luck! (Use Spring MVC instead.... there's an out of the box filter that does EVERYTHING for you... even making a good ETag :) )
You might consider using a Response Filter for that. I developed a smell library doing exactly what you are looking for: https://github.com/tobilarscheid/jaxrs-etag-filter