Search code examples
javaandroidksoap2

ksoap2 Receive Array of complex objects via SOAP


I'm trying to receive an array of a complex object from a soap-service written in PHP (nuSOAP). I'm trying to write an Android-Client and use the ksoap2 libraries (3.0.0 RC.4). There are some "solutions" for this online and several people who were facing the same problem - anyway, I tried many different ways and I'm still stuck for days now, so I decided to ask you guys for help. So I'll show you the code that in my opinion brought me the closest to what I wanna get.

first things first - the SOAP-Response (the Body):

<SOAP-ENV:Body>
    <ns1:GetListResponse xmlns:ns1="http://localhost/games_db/games_db.php">
        <return xsi:type="SOAP-ENC:Array" SOAP-ENC:arrayType="tns:DataPlusID[24]">
            <item xsi:type="tns:DataPlusID">
                <data xsi:type="xsd:string">shitload</data>
                <ID xsi:type="xsd:int">4</ID>
            </item>
            <item xsi:type="tns:DataPlusID">
                <data xsi:type="xsd:string">of</data>
                <ID xsi:type="xsd:int">7</ID>
            </item>
            <item xsi:type="tns:DataPlusID">
                <data xsi:type="xsd:string">imformation</data>
                <ID xsi:type="xsd:int">10</ID>
            </item>
        </return>
    </ns1:GetListResponse>
</SOAP-ENV:Body>

When I am not mapping anything "envelope.bodyIn.toString()" gives me the following.

GetListResponse{
    return=[
        DataPlusID{data=shitload; ID=4; },
        DataPlusID{data=of; ID=7; },
        DataPlusID{data=information; ID=10; }
    ];
}

Here are the classes, that should take care of the response someday...

public class GetListResponse implements KvmSerializable {

    private Vector<DataPlusID> datavector = new Vector<DataPlusID>();

    @Override
    public Object getProperty(int arg0) {
        return this.datavector;
    }

    @Override
    public int getPropertyCount() {
        return 1;
    }

    @Override
    public void getPropertyInfo(int index, Hashtable properties, PropertyInfo info) {
        info.name = "return";
        info.type = new Vector<DataPlusID>().getClass();
    }

    @Override
    public void setProperty(int index, Object value) {
        this.datavector = (Vector<DataPlusID>) value;
    }
}

and

public class DataPlusID implements KvmSerializable
{

    private String data;
    private int ID;


    @Override
    public Object getProperty(int arg0) {
        switch(arg0) {
        case 0:
            return data;
        case 1:
            return ID;
        }

        return null;
    }

    @Override
    public int getPropertyCount() {
        return 2;
    }

    @Override
    public void getPropertyInfo(int index, Hashtable arg1, PropertyInfo info) {
        switch(index) {
        case 0:
            info.type = PropertyInfo.STRING_CLASS;
            info.name = "data";
            break;
        case 1:
            info.type = PropertyInfo.INTEGER_CLASS;
            info.name = "ID";
            break;
        default:break;
        }
    }

    @Override
    public void setProperty(int index, Object value) {
        switch(index) {
        case 0:
            data = value.toString();
            break;
        case 1:
            ID = Integer.parseInt(value.toString());
            break;
        default:
            break;
        }
    }
}

Here is the code for the receiving the message

public GetListResponse GetList(String liste) throws Exception{

    SoapObject request = new SoapObject(Namespace, MethodGetList);

    request.addProperty("list", liste);

    final SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
    envelope.setOutputSoapObject(request);

    envelope.addMapping(Namespace, "GetListResponse", new GetListResponse().getClass());
    //envelope.addMapping(Namespace, "return", new Vector<DataPlusID>().getClass());
    envelope.addMapping(Namespace, "item", new DataPlusID().getClass()); //tried also "DataPlusID" instead of "item"

    try {
        HttpTransportSE transport = new HttpTransportSE(URL);
        transport.debug = true;

        transport.call(ActionGetList, envelope);

        Log.d("SOAPEnvelope", "Response: "+transport.responseDump);
    } catch (Exception e){
        Log.d("SOAPEnvelope", "Fehler bei Serverabfrage: "+e.toString());
    }
    //Log.d("SOAPEnvelope", "BodyIn: "+envelope.bodyIn.toString());

    GetListResponse result = new GetListResponse();
    result = (GetListResponse)envelope.bodyIn;

    return result;
}

handling the data:

new Thread(new Runnable() {
    public void run() {  
        try {
            GetListResponse response = new GetListResponse();
            response = GetList("genre");

            //content of datavector is below
            Vector<DataPlusID> datavector = new Vector<DataPlusID>();
            datavector = (Vector<DataPlusID>) response.getProperty(0);

            //EXCEPTION IS THROWN HERE
            String x = (String) datavector.get(0).getProperty(0);

            DataPlusID daten0 = new DataPlusID();
            //WOULD ALSO HAPPEN HERE
            daten0 = (DataPlusID) datavector.get(0);

            String genre1 = (String) daten0.getProperty(0);

        } catch (Exception e) {
            Log.d("SOAPEnvelope", e.toString());
        }
    }
}).start();

Here is the content of "datavector":

(java.util.Vector)
[DataPlusID{data=Action; ID=4; },
DataPlusID{data=Adventure; ID=7; },
DataPlusID{data=Aufbauspiel; ID=10; },
DataPlusID{data=Beat 'em up; ID=11; }]

The following exception is thrown:

java.lang.ClassCastException: org.ksoap2.serialization.SoapObject cannot be cast to com.example.gamesdb_client.DataPlusID

The weirdest thing is, that while debugging i actually get exatly the values i wanna get. --> When I inspect the expression "datavector.get(0).getProperty(0)" I get:

(java.lang.String) shitload

So he is actually seeing the data in the right format (string), but when he tries to attach it to a string-variable he gives me a ClassCastException?

Anyway, no matter what I tried the final result was allways the CCE so I'm pretty sure the problem can be found in the mapping (and ofc the class-definitions) part:

1 envelope.addMapping(Namespace, "GetListResponse", new GetListResponse().getClass());
2 envelope.addMapping(Namespace, "item", new DataPlusID().getClass());

Without line 1 I get another CCE, so I expect the mapping to work as it's supposed to. Without line 2 nothing changes, so I'm pretty sure that the problem is within the DataPlusID-class.

He just cannot link these:

DataPlusID{data=shitload; ID=4; },
DataPlusID{data=of; ID=7; },
DataPlusID{data=information; ID=10; }

to the DataPlusID-class.

So hopefully someone can take a look at this and give me some idea how to solve the problem. Maybe there is just some basic-stuff I didn't understood. - make my vacation not being wastet^^ Thank You.

EDIT: Re- R4j

The problem is, in the sample-code they're using a vector of string (adding it with this.add(value.toString()). this ain't possible for me since I have a vector of "DataPlusID". Something like this.add(value); won't work, because he can't put the argument (type: Object) into the DataPlusID-vector.

I've tried to solve this by changing the setProperty-method of my "GetListResponse"-class to:

public void setProperty(int index, Object value) {
    this.datavector = (Vector<DataPlusID>) value;

    for (int i = 0; i < datavector.size(); i++) {
        this.add(datavector.elementAt(i));
    }
}

That way I'd be able to set the single parts of the vector into the class.

Unfortunately this just throws the same ClassCastExcepiton in the line:

this.add(datavector.elementAt(i));

Solution

  • As far I know, your GetListResponse class should extend Vector instead of contain it. Check document here

     public class GetListResponse extends Vector<DataPlusID> 
                                    implements KvmSerializable {
      }
    

    Update:
    Sorry, I have never tried using mapping approach for array of complex objects before. You can try another approach which build a loop iterating through by using Vector<SoapObject> and get its properties by hand (you don't need GetListResponse class). Something like this:

    Vector<SoapObject> vectorOfSoapObject = (Vector<SoapObject>)envelop.getResponse();
    for (SoapObject soapObject : vectorOfSoapObject) {
       // put all properties into  DataPlusID  object
       DataPlusID  dataPlusIDObj = new DataPlusID();
       dataPlusIDObj.setData(soapObject.getPropertyAsString("data"));
    }
    

    This approach is in the document, and it should work.