Экспериментируя недавно с вызовом REST служб в Oracle ADF Mobile приложении, я столкнулся с одной интересной особенностью: при попытке вызова REST XML веб-службы, возвращающей данные в кодировке Cp1251 при помощи артефакта REST DataControl возникает ошибка, что данная кодировка не поддерживается:
Invalid
stream or encoding: java.io.UnsupportedEncodingException: Cp1251
(position:START_DOCUMENT null@0:0) caused by:
java.io.UnsupportedEncodingException: Cp1251; severity: ERROR; .type:
oracle.adfmf.framework.exception.AdfException; .exception: true; }
Эксперименты проводились с вызовом REST веб-сервиса ЦБ РФ, возвращающего информацию о курсах валют на текущую дату. URL Endpoint сервиса: http://www.cbr.ru/scripts/XML_daily.asp
Собственно, пытаясь найти решение данной проблемы, был сделан вывод, что для вызова подобных служб на данном этапе необходимо использовать программный подход и реализовывать всю логику вызова сервиса в Java коде, а не декларативно.
Рассмотрим решение этой проблемы по этапам:
Шаг 1. В среде JDeveloper создаем URL Connection (File -> New -> Connections -> URL Connection), где указываем URL Endpoint REST XML службы. В нашем случае это веб сервис ЦБ РФ с URL: http://www.cbr.ru/scripts/XML_daily.asp
Шаг 2. Для хранения данных, получаемых от веб-сервиса в ApplicationController проекте создаем дополнительный Data Object Java класс. Это вспомогательный класс, который позволяет хранить атрибутивные данные об объете (в нашем случае это курс валюты) и содержит в себе соответсвующие getter/setter методы. В нашем примере мы используем класс Currency, где определяем переменные, хранящие всю атрибутивную информацию о курсе валюты (код, наименование, значение и т.д.). Наполнение эти данных происходит в конструкторе при создании экземпляра этого класса.
public class Currency {
private String NumCode;
private String CharCode;
private String Nominal;
private String Name;
private String Value;
private transient PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
public Currency() {
super();
}
public Currency(String NumCode, String CharCode, String Nominal, String Name, String Value) {
super();
this.NumCode = NumCode;
this.CharCode = CharCode;
this.Nominal = Nominal;
this.Name = Name;
this.Value = Value;
}
// ...
// Other setters and getters here
// ...
public void setValue(String Value) {
String oldValue = this.Value;
this.Value = Value;
propertyChangeSupport.firePropertyChange("Value", oldValue, Value);
}
public String getValue() {
return Value;
}
}
Шаг 3. В проекте ApplicationController необходимо также создать еще один дополнительный, так называемый Service Object Java класс, который позже будет использоваться для создания Data Control артефакта. В нем, предварительно определив get метод, необходимо реализовать программный вызов службы, используя класс RESTServiceAdapter. В нашем случае Service Object Java класс это класс CurrencyRatesService.
RestServiceAdapter restServiceAdapter = Model.createRestServiceAdapter();
// Clear any previously set request properties, if any
restServiceAdapter.clearRequestProperties();
// Set the connection name
restServiceAdapter.setConnectionName("CBRF");
// Specify the type of request restServiceAdapter.setRequestType(RestServiceAdapter.REQUEST_TYPE_GET);
restServiceAdapter.addRequestProperty("Content-Type", "application/xml");
restServiceAdapter.addRequestProperty("Accept", "application/xml; charset=windows-1251");
// Specify the number of retries
restServiceAdapter.setRetryLimit(0);
// Set the URI which is defined after the endpoint in the connections.xml.
// The request is the endpoint + the URI being set
restServiceAdapter.setRequestURI("");
Шаг 4. Данные, возвращаемые веб-сервисом необходимо получать в формате массива byte, для этого нужно использовать sendReceive метод класса restServiceAdapter;
byte[] response = null;
response = restServiceAdapter.sendReceive("");
Шаг 5. После получения byte массива в кодировке Cp1251 необходимо преобразовать его в UTF-8 строку. Подробнее об алгоритме этого процесса можно ознакомиться здесь. В нашем случае преобразование реализовывается в static decodeCp1251 методе, определенном в этом же классе (CurrencyRatesService).
// Byte array into UTF8 string transformation.
String responseString = "";
responseString = decodeCp1251(response);
Шаг 6. На предыдущем этапе мы получили строку в формате UTF-8 содержащую XML данные от вызываемого веб-сервиса. На этом шаге необходимо выполнить парсинг полученного XML документа с помощью kXML библиотеки и наполнить данными структуру ArrayList. В нашем случае ArrayList будет содержать объекты класса Currency, определенного нами ранее:
// For GET request, there is no payload
response = restServiceAdapter.sendReceive("");
// Byte array into UTF8 string transformation.
responseString = decodeCp1251(response);
KXmlParser parser = new KXmlParser();
parser.setInput(new InputStreamReader(new ByteArrayInputStream(responseString.getBytes())));
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "ValCurs");
while(parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, null, "Valute");
while(parser.nextTag() == XmlPullParser.START_TAG) {
parser.require(XmlPullParser.START_TAG, null, "NumCode");
// handle element content
String NumCode = parser.nextText();
parser.require(XmlPullParser.END_TAG, null, "NumCode");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "CharCode");
// handle element content
String CharCode = parser.nextText();
parser.require(XmlPullParser.END_TAG, null, "CharCode");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "Nominal");
// handle element content
String Nominal = parser.nextText();
parser.require(XmlPullParser.END_TAG, null, "Nominal");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "Name");
// handle element content
String Name = parser.nextText();
parser.require(XmlPullParser.END_TAG, null, "Name");
parser.nextTag();
parser.require(XmlPullParser.START_TAG, null, "Value");
// handle element content
String Value = parser.nextText();
parser.require(XmlPullParser.END_TAG, null, "Value");
currencyList.add(new Currency(NumCode, CharCode, Nominal, Name, Value));
}
parser.require(XmlPullParser.END_TAG, null, "Valute");
}
parser.require(XmlPullParser.END_TAG, null, "ValCurs");
currency = (Currency[]) currencyList.toArray(new Currency[currencyList.size()]);
Подробнее о работе с kXML библитекой можно прочитать в документации:
Шаг 7. После преобразования данных в UTF-8 кодировку и наполнения структуры ArrayList необходимо привести их к виду, удобному для использования в UI слое Web View, в AMX страницах. Для этого необходимо преобразовать их в артефакт Data Control. В окне Application Navigator необходимо выделить Service Object Java класс (в нашем случае CurrencyRatesService),
и вызвав контекстное меню, выбрать пункт Create Data
Control.
После этого в окне Data Controls появится вновь созданный data control артефакт с коллекцией данных внутри него (в нашем случае это CurrencyRatesService data control с коллекцией данных currencyRates).
Шаг 8. Выделите коллекцию данных в окне Data Controls и перетащите её методом Drag&Drop на вновь созданную AMX страницу как List View AMX компонент.
Шаг 9. Приложение готово. Создайте профиль развертывания (Deployment Profile) и протестируйте его на эмуляторе или на реальном устройстве (Android, IOS).
В качестве дополнительной опции, при создании List View компонента, я использовал Main-Sub формат списка, т.е. элемент списка состоит из 2 частей: главной подписи (наименование валюты) и дополнительной подписи (значение курса). Все элементы списка группируются по алфавиту. Для этого, при создании List View в окне Edit List View значение Divider Mode необходимо задать как First Letter.
И, наконец, для большей функциональности приложения был создан taskFlow артефакт с 2-мя страницами: списком курсов валют и детальной информацией по каждому курсу:
Демонстрационный проект приложения можно загрузить здесь.
Комментариев нет:
Отправить комментарий