Archive for March, 2012

Simplifying kSOAP2 clients – Part 2

In the previous post we saw how to simplify a kSOAP2 client by using a template based on the ksoap2-android library. In this time we will get in deep on how to deal with complex types as call parameters or return values, such as lists, arrays and complex objects (complex meaning non-primitive Java types). Again, we will use the kSOAP2 template to simplify the service client code.

In order to simplify the explanation, below are some examples showing how to treat each case. At the end, there is a link to the source code repository to download all of this.

Case 1: Single complex object as parameter

Suppose a service with the following interface:

@WebService(targetNamespace = "http://com.wordpress.jbaris/")
public interface CarService {
	void addCar(@WebParam(name = "car") Car car);
}

where the Car class is:

public class Car {
	private Long id;
	private String name;
	private Manufacturer manufacturer;
	private List<Owner> owners;
	// getters y setters
}
public class Manufacturer {
	private Long id;
	private String name;
	private String country;
	// getters y setters
}
public class Owner {
	private Long id;
	private String name
	// getters y setters
}

The SOAP request must to look like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://com.wordpress.jbaris/">
   <soapenv:Header/>
   <soapenv:Body>
      <com:addCar>
         <car>
            <id>?</id>
            <manufacturer>
               <country>?</country>
               <id>?</id>
               <name>?</name>
            </manufacturer>
            <name>?</name>
            <!--One or more repetitions:-->
            <owners>
               <id>?</id>
               <name>?</name>
            </owners>
         </car>
      </com:addCar>
   </soapenv:Body>
</soapenv:Envelope>

So, the client code looks like this:

KSoap2Template template;
template.call("addCar", "http://impl.services.com.wordpress.jbaris/addCar",
	new RequestPropertiesSetter() {
		@Override
		public void setValues(SoapObject request) {
			SoapObject owner = new SoapObject(null, "owners");
			owner.addProperty("id", 1L);
			owner.addProperty("name", "John");

			SoapObject owner2 = new SoapObject(null,"owners");
			owner2.addProperty("id", 2L);
			owner2.addProperty("name", "Mary");

			SoapObject manufacturer = new SoapObject(null, "manufacturer");
			manufacturer.addProperty("id", 1L);
			manufacturer.addProperty("name", "Chevrolet");
			manufacturer.addProperty("country", "USA");

			SoapObject car = new SoapObject(null, "car");
			car.addProperty("id", 1L);
			car.addProperty("name", "Aveo");
			car.addProperty("manufacturer", manufacturer);
			car.addSoapObject(owner);
			car.addSoapObject(owner2);

			request.addProperty("car", car);
	}});

Notice that the call method is invoked without a ResponseMapper parameter because it has a void return value. Moreover, the RequestPropertiesSetter parameter adds a property to the target request, property whose name (car) matchs with the web service operation name in the target WSDL. One more important thing: the involved SoapObjects must have a null namespace (the 1st. constructor parameter) and their names (the 2nd. constructor parameter) must match with the target WSDL’s complexTypes.

Case 2: Complex object list as parameter

Suppose a service with the following interface:

@WebService(targetNamespace = "http://com.wordpress.jbaris/")
public interface CarService {
	void addCarList(@WebParam(name = "cars") List<Car> cars);
}

The SOAP request must to look like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://com.wordpress.jbaris/">
   <soapenv:Header/>
   <soapenv:Body>
      <com:addCarList>
         <!--One or more repetitions:-->
         <cars>
            <id>?</id>
            <manufacturer>
               <country>?</country>
               <id>?</id>
               <name>?</name>
            </manufacturer>
            <name>?</name>
            <!--One or more repetitions:-->
            <owners>
               <id>?</id>
               <name>?</name>
            </owners>
         </cars>
      </com:addCarList>
   </soapenv:Body>
</soapenv:Envelope>

So, the client code looks like this:

KSoap2Template template;
template.call("addCarList", "http://impl.services.com.wordpress.jbaris/addCarList",
	new RequestPropertiesSetter() {
		@Override
		public void setValues(SoapObject request) {
			SoapObject owner = new SoapObject(null,"owners");
			owner.addProperty("id", 1L);
			owner.addProperty("name", "John");

			SoapObject manufacturer = new SoapObject(null, "manufacturer");
			manufacturer.addProperty("id", 1L);
			manufacturer.addProperty("name", "Chevrolet");
			manufacturer.addProperty("country", "USA");

			SoapObject car = new SoapObject(null, "cars");
			car.addProperty("id", 2L);
			car.addProperty("name", "Astra");
			car.addProperty("manufacturer", manufacturer);
			car.addSoapObject(owner);

			SoapObject owner2 = new SoapObject(null, "owners");
			owner2.addProperty("id", 2L);
			owner2.addProperty("name", "Mary");

			SoapObject car2 = new SoapObject(null, "cars");
			car2.addProperty("id", 3L);
			car2.addProperty("name", "Vectra");
			car2.addProperty("manufacturer", manufacturer);
			car2.addSoapObject(owner2);

			request.addSoapObject(car);
			request.addSoapObject(car2);
		}});

Again, the call hasn’t a ResponseMapper parameter because the void return value. The RequestPropertiesSetter adds two “children” objects, just as you add items to a list. The rest is similar to the previous case.

Case 3: Primitive array as parameter

Suppose a service with the following interface:

@WebService(targetNamespace = "http://com.wordpress.jbaris/")
public interface CarService {
	List<Car> getCarsByNames(@WebParam(name = "names") String[] ids);
}

The SOAP request must to look like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://com.wordpress.jbaris/">
   <soapenv:Header/>
   <soapenv:Body>
      <com:getCarsByNames>
         <!--Zero or more repetitions:-->
         <names>?</names>
      </com:getCarsByNames>
   </soapenv:Body>
</soapenv:Envelope>

So, the client code looks like this:

KSoap2Template template;
List<Car> cars = template.call("getCarsByNames", "http://impl.services.com.wordpress.jbaris/getCarsByNames",
	new RequestPropertiesSetter() {
		@Override
		public void setValues(SoapObject request) {
			request.addProperty("names", "Vectra");
			request.addProperty("names", "Astra");
		}
	}, carListResponseMapper);

In this case, the RequestPropertiesSetter is almost trivial, because the involved parameters are primitives items of an array. The special feature is the multiple invocations to the addProperty method, one per item. However, the key of this call is on the ResponseMapper (called carListResponseMapper). A mapper have to convert the web service response into a Java object, in this case a list of Cars. We’ll dig deeper into mappers in the next case.

Case 4: Primitive list as return value

Suppose a service with the following interface:

@WebService(targetNamespace = "http://com.wordpress.jbaris/")
public interface CarService {
	List<String> getCarNamesList();
}

The SOAP request must to look like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://com.wordpress.jbaris/">
   <soapenv:Header/>
   <soapenv:Body>
      <com:getCarNamesList/>
   </soapenv:Body>
</soapenv:Envelope>

So, the client code looks like this:

List<String> carNames = getTemplate().call("getCarNamesList", "http://impl.services.com.wordpress.jbaris/getCarNamesList",
	new AbstractResponseMapper<List<String>>() {
		@Override
		public List<String> mapResponse(Object response) {
			return getList(response);
		}
	});

This time, there is no RequestPropertiesSetter because getCarNamesList don’t take parameters. However, the call uses a ResponseMapper to convert the service response into list of String. AbstractResponseMapper is an abstract class that aims to simplifying the mapper implementations by providing the most common conversion methods, e.g. the getList method invoked above.

Case 5: Complex object array as return value

Suppose a service with the following interface:

@WebService(targetNamespace = "http://com.wordpress.jbaris/")
public interface CarService {
	Car[] getCarsArray();
}

The SOAP request must to look like this:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:com="http://com.wordpress.jbaris/">
   <soapenv:Header/>
   <soapenv:Body>
      <com:getCarsArray/>
   </soapenv:Body>
</soapenv:Envelope>

So, the client code looks like this:

KSoap2Template template;
Car[] cars = template.call("getCarsArray",
				"http://impl.services.com.wordpress.jbaris/getCarsArray",
				carArrayResponseMapper);

Again, this call don’t uses a RequestPropertiesSetter because getCarsArray don’t take parameters. All the magic is on the ResponseMapper (called carArrayResponseMapper):

ResponseMapper<Car[]> carArrayResponseMapper = new AbstractResponseMapper<Car[]>() {
	@Override
	public Car[] mapResponse(Object response) {
		return getArray((Collection<Object>) response, 
				carResponseMapper, Car.class);
	}
};
ResponseMapper<Car> carResponseMapper = new AbstractResponseMapper<Car>() {
	@Override
	public Car mapResponse(Object response) {
		Car result = new Car();
		result.setId(getProperty((SoapObject) response, 
				"id", Long.class));
		result.setManufacturer(getProperty((SoapObject) response,
			"manufacturer", manufacturerResponseMapper));
		result.setName(getProperty((SoapObject) response, "name"));
		result.setOwners(getList((SoapObject) response, 
			"owners", ownerResponseMapper));
		return result;
	}
};
ResponseMapper<Manufacturer> manufacturerResponseMapper = new AbstractResponseMapper<Manufacturer>() {
	@Override
	public Manufacturer mapResponse(Object response) {
		Manufacturer result = new Manufacturer();
		result.setCountry(getProperty((SoapObject) response, "country"));
		result.setId(getProperty((SoapObject) response, 
				"id", Long.class));
		result.setName(getProperty((SoapObject) response, "name"));
		return result;
	}
};
ResponseMapper<Owner> ownerResponseMapper = new AbstractResponseMapper<Owner>() {
	@Override
	public Owner mapResponse(Object response) {
		Owner result = new Owner();
		result.setId(getProperty((SoapObject) response, 
				"id", Long.class));
		result.setName(getProperty((SoapObject) response, "name"));
		return result;
	}
};

Notice that a ResponseMapper can use another ResponseMapper to meet its purpose (e.g. carArrayResponseMapper uses carResponseMapper). Once again, all the mappers extend to AbstractResponseMapper to inherits the common conversion methods.

For a complete set of call alternatives, there is a GitHub hosted project with a JUnit test case (called KSoap2TemplateTest). Feel free to download it and make pull requests to improve it. Again, this code may be incomplete or can be improved, so comments are welcome.

Simplifying kSOAP2 clients

kSOAP2 is a SOAP web service client library for constrained Java environments such as J2ME apps. Ksoap2-android is a fork of kSOAP2 for the Android platform. A typical ksoap2-android client code looks like this.

Inspired by the Spring JDBC template classes, there is below a template for simplifying your kSOAP2 clients:

KSoap2Template.java:

import org.ksoap2.SoapEnvelope;
import org.ksoap2.serialization.SoapObject;
import org.ksoap2.serialization.SoapSerializationEnvelope;
import org.ksoap2.transport.HttpTransportSE;

public class KSoap2Template {

	private static final ResponseMapper<Object> NULL_RESPONSE_MAPPER = new ResponseMapper<Object>() {
		public Object mapResponse(Object response) {
			return response;
		}
	};

	private static final RequestPropertiesSetter NULL_REQUEST_PROPERTIES_SETTER = new RequestPropertiesSetter() {
		public void setValues(SoapObject request) {
			// do nothing
		}
	};

	private String namespace;
	private String url;

	private String getUrl() {
		return url;
	}

	private String getNamespace() {
		return namespace;
	}

	public KSoap2Template(String url, String namespace) {
		this.url = url;
		this.namespace = namespace;
	}

	public void call(String methodName, String soapAction)
			throws ServiceAccessException {
		call(methodName, soapAction, NULL_REQUEST_PROPERTIES_SETTER);
	}

	public void call(String methodName, String soapAction,
			RequestPropertiesSetter requestPropertiesSetter)
			throws ServiceAccessException {
		call(methodName, soapAction, requestPropertiesSetter,
				NULL_RESPONSE_MAPPER);
	}

	public <T> T call(String methodName, String soapAction,
			ResponseMapper<T> responseMapper) throws ServiceAccessException {
		return call(methodName, soapAction, NULL_REQUEST_PROPERTIES_SETTER,
				responseMapper);
	}

	public <T> T call(String methodName, String soapAction,
			RequestPropertiesSetter requestPropertiesSetter,
			ResponseMapper<T> responseMapper) throws ServiceAccessException {
		try {
			SoapObject request = new SoapObject(getNamespace(), methodName);
			SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
					SoapEnvelope.VER11);
			envelope.setOutputSoapObject(request);
			HttpTransportSE transport = new HttpTransportSE(getUrl());
			requestPropertiesSetter.setValues(request);
			transport.call(soapAction, envelope);
			return responseMapper.mapResponse(envelope.getResponse());
		} catch (Exception e) {
			throw new ServiceAccessException(e);
		}
	}

}

ServiceAccessException.java:

public class ServiceAccessException extends RuntimeException {

	private static final long serialVersionUID = 1L;

	public ServiceAccessException(Throwable throwable) {
		super(throwable);
	}

}

RequestPropertiesSetter.java:

import org.ksoap2.serialization.SoapObject;

public interface RequestPropertiesSetter {

	void setValues(SoapObject request);

}

ResponseMapper.java:

public interface ResponseMapper<T> {

	T mapResponse(Object response);

}

AbstractResponseMapper.java:

package jbaris.wordpress.com.client.ksoap;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.ksoap2.serialization.PropertyInfo;
import org.ksoap2.serialization.SoapObject;

public abstract class AbstractResponseMapper<T> implements ResponseMapper<T> {

	protected <U> U[] getArray(Collection<Object> collection,
			ResponseMapper<U> itemMapper, Class<U> resultType) {
		final U[] result = (U[]) Array.newInstance(resultType,
				collection.size());
		final Iterator<Object> iterator = collection.iterator();
		for (int i = 0; i < result.length; i++) {
			result[i] = itemMapper.mapResponse(iterator.next());
		}
		return result;
	}

	protected String[] getArray(Object response) {
		String[] result = new String[0];
		if (response instanceof Collection) {
			final Collection<Object> collection = (Collection<Object>) response;
			result = new String[collection.size()];
			final Iterator<Object> iterator = collection.iterator();
			for (int i = 0; i < result.length; i++) {
				result[i] = iterator.next().toString();
			}
		} else if (response != null) {
			result = new String[1];
			result[0] = response.toString();
		}
		return result;
	}

	protected char[] getCharArray(Object response) {
		char[] result = new char[0];
		if (response instanceof Collection) {
			final Collection<Object> collection = (Collection<Object>) response;
			result = new char[collection.size()];
			final Iterator<Object> iterator = collection.iterator();
			for (int i = 0; i < result.length; i++) {
				final String string = iterator.next().toString();
				result[i] = Character.valueOf((char) Integer.parseInt(string));
			}
		} else if (response != null) {
			result = new char[1];
			result[0] = Character.valueOf((char) Integer.parseInt(response
					.toString()));
		}
		return result;
	}

	protected List<String> getList(Object response) {
		final List<String> result = new ArrayList<String>();
		if (response instanceof Collection) {
			final Collection<Object> collection = (Collection<Object>) response;
			for (final Object item : collection) {
				result.add(item.toString());
			}
		} else if (response != null) {
			result.add(response.toString());
		}
		return result;
	}

	protected <U> List<U> getList(Object response, Class<U> resultType) {
		final List<U> result = new ArrayList<U>();
		if (response instanceof Collection) {
			final Collection<Object> collection = (Collection<Object>) response;
			for (final Object item : collection) {
				result.add(valueOf(resultType, item.toString()));
			}
		} else if (response != null) {
			result.add(valueOf(resultType, response.toString()));
		}
		return result;
	}

	protected <U> List<U> getList(Object response, ResponseMapper<U> itemMapper) {
		final List<U> result = new ArrayList<U>();
		if (response instanceof Collection) {
			final Collection<Object> responseCollection = (Collection<Object>) response;
			for (final Object soapPrimitive : responseCollection) {
				result.add(itemMapper.mapResponse(soapPrimitive));
			}
		} else if (response != null) {
			result.add(itemMapper.mapResponse(response));
		}
		return result;
	}

	protected <U> List<U> getList(SoapObject response, String key,
			ResponseMapper<U> itemMapper) {
		final List<U> result = new ArrayList<U>();
		final int propCount = response.getPropertyCount();
		for (int i = 0; i < propCount; i++) {
			final PropertyInfo propInfo = new PropertyInfo();
			response.getPropertyInfo(i, propInfo);
			if (propInfo.getName().equals(key)) {
				result.add(itemMapper.mapResponse(propInfo.getValue()));
			}
		}
		return result;
	}

	protected String getProperty(SoapObject soapObject, String key) {
		return soapObject.getProperty(key).toString();
	}

	protected <U> U getProperty(SoapObject soapObject, String key,
			Class<U> resultType) {
		return valueOf(resultType, getProperty(soapObject, key));
	}

	protected <U> U getProperty(SoapObject soapObject, String key,
			ResponseMapper<U> propertyMapper) {
		return propertyMapper.mapResponse(soapObject.getProperty(key));
	}

	protected <U> U valueOf(Class<U> resultType, Object object) {
		U result;
		if (object == null) {
			result = null;
		} else {
			result = valueOf(resultType, object.toString());
		}
		return result;
	}

	private <U> U valueOf(Class<U> resultType, String valueAsString) {
		try {
			return (U) resultType.getMethod("valueOf",
					new Class[] { String.class }).invoke(resultType,
					new Object[] { valueAsString });
		} catch (final Exception e) {
			throw new IllegalStateException(e);
		}
	}

}

Here are some examples of using the template. Look how simple it becomes your client code:

public class SampleClient {

	private static final String URL = "http://localhost:8080/ws/SampleService";
	private static final String NAMESPACE = "http://sample.org.ar/";
	private KSoap2Template template = new KSoap2Template(URL, NAMESPACE);

	public void callWithVoidResponse() {
		RequestPropertiesSetter rps = new RequestPropertiesSetter() {
			public void setValues(SoapObject request) {
				request.addProperty("aCar", new Car(200L, "A car name"));
			}
		};
		template.call("addCar", "http://impl.service.sample.org.ar/addCar", rps);
	}

	public void callWithoutParams() {
		template.call("getCarNames", "http://impl.service.sample.org.ar/getCarNames",
			new AbstractResponseMapper<List<String>>() {
				public List<String> mapResponse(Object response) {
					return getList(response);
				}
			});
	}

	public Car callWithParamsAndObjectResult() {
		RequestPropertiesSetter rps = new RequestPropertiesSetter() {
			public void setValues(SoapObject request) {
				request.addProperty("carId", 200L);
			}
		};
		return template.call("getCar", "http://impl.service.sample.org.ar/getCar", rps,
			new AbstractResponseMapper<Car>() {
				public Car mapResponse(Object response) {
					Car result = new Car();
					result.setId(getProperty((SoapObject) response, "id", Long.class));
					result.setName(getProperty((SoapObject) response, "name"));
					return result;
				}
			});
	}
}

Obviously this code may be incomplete and may be improved, so comments are welcome.

Android – WiFi disconnected at lock screen

In Android devices, if you want to keep the WiFi connected all the time, even if the screen gets locked, you have to go at:

Settings / Wireles & Network / Wi-Fi settings

then tap on

Menu key  / Advanced

and finally, change the Wi-Fi sleep policy to “Never when plugged in


Enhanced Links