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