How to : JAX-WS + android / java me
Досталась однажды задачка написать приложение для телефонов. И не просто приложение , а коннектящееся к серверу для обмена данными,и с локальной бд. И конечно для начала нужно было выбрать на чем и как все это будет писаться. Предложенное RMI вскоре было отвергнуто по причинам весьма малого количества информации и примеров связки rmi + mobile , и то довольно старыми. Книг по rmi тоже не густо, одна изд. О'релли 2001г. Веб сервисы производили куда более благоприятное впечатление.
Далее из веб сервисов soap/rest был выбран soap , исключительно из-за простоты , ведь в приоритетах традиционно стоит скорость разработки. На стороне сервера все элементарно - веб сервис описывается аннотациями и уже готов к работе. На стороне клиента использую библиотеку для работы с соап: ksoap2.
Задача: написать приложение для мобильных устройств на ос андроид, а также мидлет для простых телефонов. Обмен данными с сервером реализовать веб сервисом soap. По веб сервису необходимо передавить не только простейшие типы, но и свой класс, список своих классов, класс содержащий простые типы и список своих классов.
среда разработки : Netbeans 7.0.1 ;
библиотеки : ksoap2-android , ksoap2-j2me-core-2.1.1
ос : Ubuntu 11.10 ;
сервер : Glassfish 3.1 ;
Процесс.
Вкрации :
1. Сервер.
2. Клиент андроид.
3. Классы передаваемые сервером.
4. Обработка принятых классов на клиенте.
5. Передача веб сервису сложных объектов.
5.1 Клиентсвие классы.
5.2 Серверные классы.
6. Клиент java me : незначительные изменения в коде.
1. Сервер.
Код серверной части самого сервиса состоит из интерфейса и реализации описанных аннотациями.
Интерфейс :
В нетбинсе автоматически появляется написанный сервис в списке веб сервисов.
2. Клиент андроид.
Добавить библиотеку ksoap2-android в папку libs, или через свойства проекта указать ее нахождение. Предпочтительней первый способ:
Теперь можно начинать работать с этой библиотекой. Код клиента :
3. Классы передаваемые сервером.
Достаточно описать аннотациями класс, и он уже готов к передаче по сервису. Воспользуюсь спецификацией JAXB.
Если класс имеет конструктор, у него должен быть конструктор по умолчанию, указанный явно. Поля должны быть protected. Содержит другой объект QuestionContaner, он должен быть указан в аннотации @XmlSeeAlso , а сам обозначен как XmlType
4. Обработка принятых классов на клиенте.
Клиент принимает объекты в виде SoapObject, вытаскиваем свойства и формируем нужные классы для работы в андроид приложении.
Простые поля можно вытащить по названию , а там же по индексу, по имени конечно удобнее:
5. Передача веб сервису сложных объектов
При обмене сложными объектами , от клиента к серверу, они оба, и клиент и сервер, содержат эти классы, со специфическими модификациями. Получается некое подобие классов-заглушек, только проще. Начну пожалуй с клиентских классов, ведь передает содержательную информацию именно он. Но можно начать для простоты с серверного куска.
Передача сложных объектов осуществляется в третьем методе описанного веб сервиса , receiveRezults , никакими специфическими указаниями он не обладает по этому поводу, все стандартно, как и в других методах.
5.1. Клиентские классы
Все передаваемые классы должны реализовывать интерфейс KvmSerializable из библиотеки ksoap2-android. Это относится и к спискам. Далее пример класса со списком также собственных классов для передачи серверу. Со списком примитивов аналогично.
И не забыть при передаче веб сервису указать меппинг :
5.2. Серверные классы
Описанные классы клиента на стороне сервера имеют как бы дубли, все кроме вектора RezultVector, ведь он нужен только для передачи, чтобы сериализовать объекты списка. Достаточно описать аннотациями и сервер поймет что же он принимает.
Класс, содержащий список :
6. Клиент java me : незначительные изменения в коде.
Для мидлета никаких существенных изменений не понадобилось. Все аналогично работает с библиотекой ksoap2 для java me. Несколько отличий все же есть.
Используется HttpTransport вместо HttpTransportSE.
Для получения ответа нужно вызывать SoapObject bodyIn = (SoapObject)evenlope.bodyIn; , иначе будет получен лишь первый объект списка.
Класс расширяющий вектор теперь реализует не параметризированный вектор:
public class RezultVector extends Vector implements KvmSerializable {...}
В остальном все осталось таким же.
Источники :
http://roderickbarnes.com/blog/droid-chronicles-web-services-handling-complex-parameters
http://blog.inflinx.com/2010/10/09/jax-ws-using-weblogic-10-3/
http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks
Далее из веб сервисов soap/rest был выбран soap , исключительно из-за простоты , ведь в приоритетах традиционно стоит скорость разработки. На стороне сервера все элементарно - веб сервис описывается аннотациями и уже готов к работе. На стороне клиента использую библиотеку для работы с соап: ksoap2.
Задача: написать приложение для мобильных устройств на ос андроид, а также мидлет для простых телефонов. Обмен данными с сервером реализовать веб сервисом soap. По веб сервису необходимо передавить не только простейшие типы, но и свой класс, список своих классов, класс содержащий простые типы и список своих классов.
среда разработки : Netbeans 7.0.1 ;
библиотеки : ksoap2-android , ksoap2-j2me-core-2.1.1
ос : Ubuntu 11.10 ;
сервер : Glassfish 3.1 ;
Процесс.
Вкрации :
1. Сервер.
2. Клиент андроид.
3. Классы передаваемые сервером.
4. Обработка принятых классов на клиенте.
5. Передача веб сервису сложных объектов.
5.1 Клиентсвие классы.
5.2 Серверные классы.
6. Клиент java me : незначительные изменения в коде.
1. Сервер.
Код серверной части самого сервиса состоит из интерфейса и реализации описанных аннотациями.
Интерфейс :
package org.setupit.ischool.webservices.for_mobile;
/**
*
* @author str
*/
import org.setupit.ischool.webservices.for_mobile.stubs.receive.RezultComplexStub;
import org.setupit.ischool.webservices.for_mobile.stubs.send.TestStub;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
@WebService(name = "HelloWS", targetNamespace = "http://for_mobile/")
public interface IMobileLearningWS
{
@WebMethod(operationName = "login")
public Integer login(@WebParam(name = "login") String login, @WebParam(name = "password") String password);
@WebMethod(operationName="tests")
@WebResult(name="TestStub")
public TestStub[] tests(@WebParam(name = "userId") int userId);
@WebMethod(operationName="receiveRezults")
public int receiveRezults(@WebParam(name=RezultComplexStub.NAMEELEMENT) RezultComplexStub task);
}
Реализация : /**
*
* @author str
*/
@WebService( portName = "MobileLearningWSPort",
serviceName = "MobileLearningWSService",
targetNamespace = "http://for_mobile/",
endpointInterface = "org.setupit.ischool.webservices.for_mobile.IMobileLearningWS")
@LocalBean
public class MobileLearningWS implements IMobileLearningWS {
@EJB
UserFacade userFacade;
@Override
public Integer login(String login, String password) {
// ...
}
@Override
public TestStub[] tests(int userId) {
// ...
}
@Override
public int receiveRezults(RezultComplexStub rezultComplexStub) {
// ...
}
}
В сервисе три метода, первый обрабатывает простые типы, второй возвращает список собственных классов, третий принимает сложный объект и возвращает код операции.В нетбинсе автоматически появляется написанный сервис в списке веб сервисов.
2. Клиент андроид.
Добавить библиотеку ksoap2-android в папку libs, или через свойства проекта указать ее нахождение. Предпочтительней первый способ:
И в файле AndroidManifest.xml разрешить доступ к сети :
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Теперь можно начинать работать с этой библиотекой. Код клиента :
public class WSClient {
static String DOMAIN_RELEASE = "*********";
static String DOMAIN_DEVELOP = "127.0.0.1:80/";
public static final String NAMESPACE = "http://for_mobile/";
public static String URL = "http://"+DOMAIN_RELEASE+"/MobileLearningWSService?WSDL";
public static final String METHOD_NAME_LOGIN = "login";
public static final String METHOD_NAME_TESTS = "tests";
public static final String METHOD_RECEIVE_REZ = "receiveRezults";
public static final String SOAP_ACTION_LOGIN = NAMESPACE + METHOD_NAME_LOGIN;
public static final String SOAP_ACTION_TESTS = NAMESPACE + METHOD_NAME_TESTS;
public static final String SOAP_ACTION_RECEIVE_REZ = NAMESPACE + METHOD_RECEIVE_REZ;
public UserModel login(String login, String passw) {
SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME_LOGIN);
PropertyInfo propertyLogin = new PropertyInfo();
propertyLogin.name = "login";
propertyLogin.type = PropertyInfo.STRING_CLASS;
propertyLogin.setValue(login);
PropertyInfo propertyPassw = new PropertyInfo();
propertyPassw.name = "password";
propertyPassw.type = PropertyInfo.STRING_CLASS;
propertyPassw.setValue(passw);
request.addProperty(propertyLogin);
request.addProperty(propertyPassw);
//
// The constant SoapEnvelope.VER11 indicates SOAP Version 1.1. Assign the SoapObject
// request object to the envelop as the outbound message for the SOAP method call.
//
SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER11);
envelope.setOutputSoapObject(request);
// Create a org.ksoap2.transport.HttpTransportSE object that represents
// a J2SE based HttpTransport layer. HttpTransportSE extends the
// org.ksoap2.transport.Transport class, which encapsulates the serialization
// and deserialization of SOAP messages.
HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
try {
// Make the soap call using the SOAP_ACTION and the soap envelop.
//
androidHttpTransport.call(SOAP_ACTION_LOGIN, envelope);
// Get the web service response using the getResponse method of
// the SoapSerializationEnvelope object and cast the response object
// to SoapPrimitive, class used to encapsulate primitive types.
//
SoapPrimitive resultsRequestSOAP = (SoapPrimitive) envelope.getResponse();
if (resultsRequestSOAP == null) {
return null;
}
Integer userID = Integer.valueOf(resultsRequestSOAP.toString());
return new UserModel(userID, login, passw);
} catch (Exception e) {
return null;
}
}
public List<Testmodel> getTests(int userId) {
// формирование параметров и запрос к серверу аналогично первой функции
Object responceSoapObj = evenlope.getResponse();
if (responceSoapObj == null)
{
// ничего не принято
} else if (responceSoapObj instanceof Vector) // принят список
{
Vector<SoapObject> soapOjs = (Vector<SoapObject>) responceSoapObj;
// ...
} else // принят один объект
{
SoapObject so = (SoapObject) responceSoapObj;
// ...
}
// ...
}
public String saveRezult(int learnerTestId, RezultFinalModel rezultFinal) {
// передача сложного объекта веб сервису, об этом в п.5
}
3. Классы передаваемые сервером.
Достаточно описать аннотациями класс, и он уже готов к передаче по сервису. Воспользуюсь спецификацией JAXB.
Если класс имеет конструктор, у него должен быть конструктор по умолчанию, указанный явно. Поля должны быть protected. Содержит другой объект QuestionContaner, он должен быть указан в аннотации @XmlSeeAlso , а сам обозначен как XmlType
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;
/**
*
* @author str
* JAXB annotations.
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(value = {QuestionContaner.class})
public class TestStub {
protected int id;
// другие поля int и String
@XmlElement(type = QuestionContaner.class, nillable = true)
protected QuestionContaner questionContaner;
public TestStub() {
// a no-arg default constructor
}
public TestStub(....) {
// ...
}
// gets , sets
}
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
public class QuestionContaner {
@XmlElement
protected List<QuestionStub> questionsStub;
// конструкторы, get, set
}
4. Обработка принятых классов на клиенте.
Клиент принимает объекты в виде SoapObject, вытаскиваем свойства и формируем нужные классы для работы в андроид приложении.
Простые поля можно вытащить по названию , а там же по индексу, по имени конечно удобнее:
Integer id = Integer.parseInt(so.getPropertyAsString("id"));
String name = so.getPropertyAsString("name");
Списки объектов по индексам (если вызывать getProperty с именем пропертиз, то вернется только первый объект. Поэтому:SoapObject soQuestionContaner = (SoapObject) so.getProperty("questionContaner");
int countQuestions = soQuestionContaner.getPropertyCount();
for (int i = 0; i < countQuestions; i++) {
SoapObject soQuestionStub = (SoapObject) soQuestionContaner.getProperty(i);
// ...
}
Это в случае т.н объекта-контейнера, которые не содержит ничего кроме списка, поэтому пробежав по всем пропертиз, получим лишь нужный список список. Если класс содержит другие поля, помимо списка, необходимо проходить по диапазону свойств:final int CONS_PROP = 4; // число фиксированных пропертизов. после них идет список объектов ансвер
int allProps = so.getPropertyCount(); // всего пропертизов
for(int i = CONS_PROP; i < allProps; i++){
SoapObject soAnswer = (SoapObject)so.getProperty(i);
//...
}
5. Передача веб сервису сложных объектов
При обмене сложными объектами , от клиента к серверу, они оба, и клиент и сервер, содержат эти классы, со специфическими модификациями. Получается некое подобие классов-заглушек, только проще. Начну пожалуй с клиентских классов, ведь передает содержательную информацию именно он. Но можно начать для простоты с серверного куска.
Передача сложных объектов осуществляется в третьем методе описанного веб сервиса , receiveRezults , никакими специфическими указаниями он не обладает по этому поводу, все стандартно, как и в других методах.
5.1. Клиентские классы
Все передаваемые классы должны реализовывать интерфейс KvmSerializable из библиотеки ksoap2-android. Это относится и к спискам. Далее пример класса со списком также собственных классов для передачи серверу. Со списком примитивов аналогично.
package org.setupit.MLAndroid.wsclient.stubs;
import java.util.Hashtable;
import org.ksoap2.serialization.KvmSerializable;
import org.ksoap2.serialization.PropertyInfo;
/**
*
* @author str
*/
public class RezultsComplexStub implements KvmSerializable {
public static final String NAMEELEMENT = "rezultComplexStub";
private int learnerTestId;
protected RezultVector rezults;
private static final int INT_PROPERTY_COUNT = 2;
private static final int INT_PROPERTY_LEARNER_TEST_ID = 0;
private static final int INT_PROPERTY_REZULTS = 1;
// названия пропертизов. они должны совпадать с сервачными.
private final String NAME_PROPERTY_LEARNERTEST_ID = "learnerTestId";
private final String NAME_PROPERTY_REZULTS = "rezults";
public RezultsComplexStub() {
}
public RezultsComplexStub( int learnerTestId, RezultVector rezults) {
this.learnerTestId = learnerTestId;
this.rezults = rezults;
}
@Override
public Object getProperty(int intPropertyIndex) {
switch (intPropertyIndex) {
case INT_PROPERTY_LEARNER_TEST_ID:
return this.getLearnerTestId();
case INT_PROPERTY_REZULTS:
return this.getRezults();
}
return null;
}
@Override
public int getPropertyCount() {
return INT_PROPERTY_COUNT;
}
@Override
public void setProperty(int intPropertyIndex, Object objectPropertyNewValue) {
switch (intPropertyIndex) {
case INT_PROPERTY_LEARNER_TEST_ID:
this.setLearnerTestId((Integer) objectPropertyNewValue);
break;
case INT_PROPERTY_REZULTS:
this.setRezults((RezultVector) objectPropertyNewValue);
break;
default:
break;
}
}
@Override
public void getPropertyInfo(int intPropertyIndex, Hashtable arg1, PropertyInfo info) {
switch (intPropertyIndex) {
case INT_PROPERTY_LEARNER_TEST_ID:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = NAME_PROPERTY_LEARNERTEST_ID;
break;
case INT_PROPERTY_REZULTS:
info.type = RezultVector.class;
info.name = NAME_PROPERTY_REZULTS;
break;
default:
break;
}
}
// gets , sets
}
Расширяем вектор , чтобы реализовать интерфейс KvmSerializable : public class RezultVector extends Vector<VectorItem> implements KvmSerializable {
public RezultVector() {
}
@Override
public Object getProperty(int index) {
return this.get(index);
}
@Override
public int getPropertyCount() {
return this.size();
}
@Override
public void getPropertyInfo(int index, Hashtable properties, PropertyInfo info) {
info.name = VectorItem.NAMEELEMENT;
info.type = VectorItem.class;
}
@Override
public void setProperty(int index, Object value) {
this.add((VectorItem) value);
}
}
Элементы вектора, это просто классы, аналогично вышеописанному RezultsComplexStu:public class VectorItem implements KvmSerializable {
public static final String NAMEELEMENT = "vectorItem";
private Integer answerId;
private String freeText;
private int questionId;
private static final int INT_PROPERTY_COUNT = 3;
private static final int INT_ANSWER_ID = 0;
private static final int INT_FREE_TEXT = 1;
private static final int INT_QUESTION_ID = 2;
// названия пропертизов. они должны совпадать с сервачными.
private final String NAME_PROPERTY_ANSWER_ID = "answerId";
private final String NAME_PROPERTY_FREE_TEXT = "freeText";
private final String NAME_PROPERTY_QUESTION_ID = "questionId";
public VectorItem() {
}
public VectorItem(Integer answerId, String freeText, int questionId) {
this.answerId = answerId;
this.freeText = freeText;
this.questionId = questionId;
}
@Override
public Object getProperty(int intPropertyIndex) {
switch (intPropertyIndex) {
case INT_ANSWER_ID:
return this.getAnswerId();
case INT_FREE_TEXT:
return this.getFreeText();
case INT_QUESTION_ID:
return this.getQuestionId();
}
return null;
}
@Override
public int getPropertyCount() {
return INT_PROPERTY_COUNT;
}
@Override
public void setProperty(int intPropertyIndex, Object objectPropertyNewValue) {
switch (intPropertyIndex) {
case INT_ANSWER_ID:
this.setAnswerId((Integer)objectPropertyNewValue);
break;
case INT_FREE_TEXT:
this.setFreeText((String)objectPropertyNewValue);
break;
case INT_QUESTION_ID:
this.setQuestionId((Integer)objectPropertyNewValue);
break;
default:
break;
}
}
@Override
public void getPropertyInfo(int intPropertyIndex, Hashtable arg1, PropertyInfo info) {
switch (intPropertyIndex) {
case INT_ANSWER_ID:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = NAME_PROPERTY_ANSWER_ID;
break;
case INT_FREE_TEXT:
info.type = PropertyInfo.STRING_CLASS;
info.name = NAME_PROPERTY_FREE_TEXT;
break;
case INT_QUESTION_ID:
info.type = PropertyInfo.INTEGER_CLASS;
info.name = NAME_PROPERTY_QUESTION_ID;
break;
default:
break;
}
}
// gets , sets
}
И не забыть при передаче веб сервису указать меппинг :
PropertyInfo propertyInfo = new PropertyInfo(); propertyInfo.name = RezultsComplexStub.NAMEELEMENT; propertyInfo.type = RezultsComplexStub.class; request.addProperty(propertyInfo, rezultComplex); // ... envenlope.addMapping(NAMESPACE, RezultsComplexStub.NAMEELEMENT, RezultsComplexStub.class);
5.2. Серверные классы
Описанные классы клиента на стороне сервера имеют как бы дубли, все кроме вектора RezultVector, ведь он нужен только для передачи, чтобы сериализовать объекты списка. Достаточно описать аннотациями и сервер поймет что же он принимает.
Класс, содержащий список :
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType( propOrder={"learnerTestId", "rezults"})
@XmlRootElement(name = RezultComplexStub.NAMEELEMENT )
@XmlSeeAlso(value={VectorItem.class})
public class RezultComplexStub {
public static final String NAMEELEMENT = "rezultComplexStub";
@XmlElement(required=true)
protected int learnerTestId;
@XmlElementWrapper(name="rezults", required=false)
@XmlElement(required=false, name=VectorItem.NAMEELEMENT)
protected List<VectorItem> rezults;
public RezultComplexStub() {
}
// gets sets
}
Элемент списка :@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(propOrder={"answerId", "freeText", "questionId"})
public class VectorItem {
public static final String NAMEELEMENT = "vectorItem";
@XmlElement
protected Integer answerId;
@XmlElement
protected String freeText;
@XmlElement(required=true)
protected int questionId;
public VectorItem() {
}
// gets sets
}
6. Клиент java me : незначительные изменения в коде.
Для мидлета никаких существенных изменений не понадобилось. Все аналогично работает с библиотекой ksoap2 для java me. Несколько отличий все же есть.
Используется HttpTransport вместо HttpTransportSE.
Для получения ответа нужно вызывать SoapObject bodyIn = (SoapObject)evenlope.bodyIn; , иначе будет получен лишь первый объект списка.
Класс расширяющий вектор теперь реализует не параметризированный вектор:
public class RezultVector extends Vector implements KvmSerializable {...}
В остальном все осталось таким же.
Источники :
http://roderickbarnes.com/blog/droid-chronicles-web-services-handling-complex-parameters
http://blog.inflinx.com/2010/10/09/jax-ws-using-weblogic-10-3/
http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks


