2

I am trying to create a web service in Java that accepts two lists as FormData parameter.

I have used the Angualr JS http service as mentioned in this answer.

var queryRequest = $http({
    method:'POST',
    url:'services/test/testPath',
    data:$.param({"listA":myList1,"listB":myList2}),
    headers: {'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'}
});

My object myList1

var myList1=[];
var obj={};
obj["val1"]=1
obj["val2"]=2
myList1.push(obj);

My object myList2

var myList2=[];

var obj={};
obj["val11"]=3
obj["val12"]=4
myList2.push(obj);

var obj={};
obj["val11"]=5
obj["val12"]=6
myList2.push(obj);

I am using javax.ws.rs.* for the rest service

And my Java server endpoint, which accepts the data, is as-

@Path("testPath")
@POST
@Consumes("application/x-www-form-urlencoded;charset=UTF-8")
@Produces("application/json")
public DataDTO addIssuancesForFP(@FormParam("listA") List<TypeA> list1, @FormParam("listB") List<TypeB> list2) {

    System.out.println("Service is called correctly");
    return service.getDTO(list1,list2);
}

My classe 'TypeA'

 private Integer val1;
 private Integer val2;
 //getters and setters, and default constructor

My classe 'TypeB'

 private Integer val11;
 private Integer val12;
 //getters and setters, and default constructor

Endpoint is hitting correctly, but I am getting null in both the list. The request structure is:

Request Type

    Accept:application/json, text/plain, */*
    Content-Type:application/x-www-form-urlencoded;charset=UTF-8

Form Data

    listA[0][val1]:1
    listA[0][val2]:2

    listB[0][val11]:3
    listB[0][val12]:4
    listB[1][val11]:5
    listB[1][val12]:6

It seems to be correct, I think mistake is in server part. How to resolve this?

Thanks

Note: This is just the mock data, which is in exact same format

Community
  • 1
  • 1
Vivek Vardhan
  • 1,118
  • 3
  • 21
  • 44

1 Answers1

1

Yeah so I don't think using form encoded data is going to work. The reason is that it is mainly for key value pairs, in the form

key1=value7&key2=value2&key3=value3...

What you are doing is using only two keys, listA and listB. So imagine what the values would need to look like, to send the entire list. It isn't pretty. For complex data, it is more viable to send the data in a format like JSON. The problem with this for your particular use case is that there are two unrelated objects (or arrays) to needs to send. For this, a solution would be to use multipart. You're in luck, because I just posted a late answer yesterday, on exactly how this can be handled with Angular.

I won't go through an explanation here about the code. Everything is explained in that link. Please read through it, as you go through this answer. I will be using Jersey as may JAX-RS implementation (as does the example in the link - but it also offers other alternative implementations)

Resource

import java.util.List;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import org.glassfish.jersey.media.multipart.FormDataParam;

@Path("/form")
public class FormResource {

    @POST
    @Consumes("multipart/form-data")
    @Produces("text/plain")
    public String addIssuancesForFP(@FormDataParam("listA") List<TypeA> list1, 
                                     @FormDataParam("listB") List<TypeB> list2) {

        StringBuilder response = new StringBuilder();
        for(TypeA a: list1) {
            response.append(a.toString()).append("; ");
        }

        for (TypeB b: list2) {
            response.append(b.toString()).append("; ");
        }

        System.out.println("Service is called correctly");
        return response.toString();
    }
}

Angular

<!DOCTYPE html>
<html ng-app="formApp">
    <head>
        <title>TODO supply a title</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="js/libs/jquery/jquery.js"></script>
        <script src="js/libs/angular.js/angular.js"></script>
        <script>
            angular.module("formApp", [])
            .controller("defaultCtrl", function($scope, $http) {
                $scope.sendData = function() {
                    var myList1 = [];
                    var obj = {};
                    obj["val1"] = "value1";
                    obj["val2"] = "value2";
                    myList1.push(obj);

                    var myList2 = [];

                    var obj = {};
                    obj["val11"] = "value11";
                    obj["val12"] = "value12";
                    myList2.push(obj);

                    var obj = {};
                    obj["val11"] = "value211";
                    obj["val12"] = "value212";
                    myList2.push(obj);

                    var list1 = JSON.stringify(myList1);
                    var list2 = JSON.stringify(myList2);

                    var boundary = Math.random().toString().substr(2);
                    var header = "multipart/form-data; charset=utf-8; boundary=" + boundary;

                    $http({
                        url: "/api/form",
                        headers: {"Content-Type": header},
                        data: createRequest(list1, list2, boundary),
                        method: "POST"
                    }).then(function(response) {
                        $scope.result = response.data;
                    });

                    function createRequest(list1, list2, boundary) {
                        var multipart = "";
                        multipart += "--" + boundary
                            + "\r\nContent-Disposition: form-data; name=listA"
                            + "\r\nContent-type: application/json"
                            + "\r\n\r\n" + list1 + "\r\n";
                        multipart += "--" + boundary
                            + "\r\nContent-Disposition: form-data; name=listB"
                            + "\r\nContent-type: application/json"
                            + "\r\n\r\n" + list2 + "\r\n";
                            multipart += "--" + boundary + "--\r\n";
                        return multipart;
                    }
                };
             });
        </script>
    </head>
    <body>
        <div ng-controller="defaultCtrl">
            <button ng-click="sendData()">Send</button>
            <p>{{result}}
        </div>
    </body>
</html>

Result

TypeA{val1=value1, val2=value2}; 
TypeB{val1=value11, val2=value12}; 
TypeB{val1=value211, val2=value212};

Which is expected, as I just built a string from the toString() methods, which I implemented in the TypeA and TypeB classes.

public class TypeA {
    public String val1;
    public String val2;
    @Override
    public String toString() {
        return "TypeA{" + "val1=" + val1 + ", val2=" + val2 + '}';
    }
}

public class TypeB {
    public String val11;
    public String val12;
    @Override
    public String toString() {
        return "TypeB{" + "val1=" + val11 + ", val2=" + val12 + '}';
    } 
}

Hope this helps.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • I tried this also, but I was getting something like `could not found boundary of media error`, What does this `boundary` is for? – Vivek Vardhan Mar 28 '15 at 10:58
  • 1
    It 's part of the [multipart](http://www.w3.org/TR/html401/interact/forms.html#h-17.13.4.2) specification. Most likely you did not send the request out correctly, maybe allowing the browser to create the request. Even if you set the content type to false/undefined, using `FormData` as proposed in some of [these answers](http://stackoverflow.com/q/5392344/2587435), the data would still be sent out as plain text, and you would still need to parse it on the server side. With the JSON approach, you let the framework parse it. In the solution above, we are created the request body ourselves – Paul Samsotha Mar 28 '15 at 11:02
  • And also, I do not have `@FormDataParam` annotation, that you have imported from `org.glassfish.jersey.media.multipart.FormDataParam;` and I was using `javax.ws.rs.FormParam;`. Is there any alternative for this in Spring web. I am also using this `org.springframework.web.bind.annotation.RequestBody` – Vivek Vardhan Mar 28 '15 at 11:11
  • 1
    Are you using Jersey? If so, go to the link, and it will have the Maven dependency you need to add. Then you need to register the feature also. The link shows Java configuration of the feature. If you are using web.xml, then you can set an init param for the jersey servlet `jersey.config.server.provider.classnames` - value - `org.glassfish.jersey.media.multipart.MultiPartFeature` – Paul Samsotha Mar 28 '15 at 11:12
  • I am not using `Jersey`, I am using `javax.ws.rs` APIs, and also Spring. For example `RequestBody` was Spring annotation and `QueryParam` was `javax` annotation. `FormDataParam` is not there. How can I add – Vivek Vardhan Mar 30 '15 at 07:04
  • `javax.ws.rs` does nothing. It is a specification API jar. There needs to be an implementation of it. If you're using only that jar, then you are using a Java ee server which has the implementation. Which server are you using? – Paul Samsotha Mar 30 '15 at 07:07
  • Also I don't know how you are integrating Spring with JAX-RS without an implementation. You sure you aren't using something like CXF (which is a JAX-RS implementation)? – Paul Samsotha Mar 30 '15 at 07:10
  • I am using `CXFUtils`. In my `web.xml`, I have a servlet ` CXFServlet org.apache.cxf.transport.servlet.CXFServlet ` Is that what you are asking? I have just started using these, so not of much idea.. learning – Vivek Vardhan Mar 30 '15 at 08:08
  • Do you know what version of CXF you are using? See if there's a `@Multipart` annotation. – Paul Samsotha Mar 30 '15 at 08:10
  • See [here](http://cxf.apache.org/docs/jax-rs-multiparts.html) for general support with multipart for CXF, and an example [here](http://stackoverflow.com/a/28483262/2587435) – Paul Samsotha Mar 30 '15 at 08:16
  • Its CXFUtils version 2.3, I just checked my `ivy`, and other one is `CXF Bundle jar` 2.2.6.. And yes `@Multipart` is there `org.apache.cxf.jaxrs.ext.multipart.Multipart` – Vivek Vardhan Mar 30 '15 at 08:19