58
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 6/2008
Java Microedition
– metody integracji aplikacji
A
plikacje Java Microedition (konfiguracji
CLDC) działające na urządzeniach przeno-
śnych mają zazwyczaj ograniczone zaso-
by sprzętowe. W większości przypadków limitowa-
na pamięć oraz wolne procesory na urządzeniach
powodują, że pewne zadania są mało wydajne lub
niemożliwe do zrealizowania. Rozwiązaniem tego
problemu może być przeniesienie części funkcjo-
nalności aplikacji na stronę serwera. W ten sposób
realizuje się ubogiego klienta, który wymaga mniej
zasobów przy zakładanej funkcjonalności. Nato-
miast serwer przejmuje wymagające większych
zasobów zadania i zwraca do aplikacji klienckiej
tylko rezultaty swojej pracy.
Niniejszy artykuł skupi się na sposobach in-
tegracji aplikacji klienta JME z aplikacją serwera.
Zostanie zaprezentowany krótki przegląd metod
za pomocą których można skomunikować klien-
ta z serwerem.
Gruntownie zostaną opisane wybrane mecha-
nizmy integracji, które obecnie są najczęściej sto-
sowane. Na koniec zostanie przedstawiona ocena
wybranych metod w odniesieniu do wszechstron-
nego zastosowania.
Uwagi na temat technologii JME
W technologii JME paczki z bibliotekami dostępne
są bezpośrednio w maszynie wirtualnej KVM (Kilo-
byte Virtual Machine) urządzenia dzięki czemu pro-
gramista nie musi martwić się o ich dostępność. Z
drugiej strony istnieją rozwiązania, które wymagają
dołączenia odpowiedniej biblioteki zawartej w ar-
chiwum jar. Wiąże się to niestety ze wzrostem roz-
miaru (archiwum jar) aplikacji co ma istotne zna-
czenia w przypadku limitów maksymalnej wielko-
ści narzuconej przez producentów.
Zagadnienie to nie stanowi jednakże tematu
niniejszego artykułu. Zainteresowanym polecam
strony producentów urządzeń, gdzie w większości
przypadków tego rodzaju informacje są dostępne.
Jednym z lepszych zestawień danych technicz-
nych urządzeń mobilnych różnych producentów
jest strona J2MEPolish.
Kolejnym ważnym aspektem jest profil urzą-
dzenia Mobile Information Device Profile w skró-
Albert Wachowicz
Albert Wachowicz pracuje na stanowisku Software
Specialist w BLStream wchodzącym w skład Grupy
BLStream. Grupa BLStream powstała by efektywniej
wykorzystywać potencjał dwóch, szybko rozwijają-
cych się producentów oprogramowania – BLStream
i Gamelion. Firmy wchodzące w skład grupy specja-
lizują się w wytwarzaniu oprogramowania dla klien-
tów korporacyjnych, w rozwiązaniach mobilnych oraz
produkcji i testowaniu gier.
Kontakt z autorem: albert.wachowicz@gmail.com
Listing 1.
Przykładowa metoda wysyłająca
wiadomość poprzez gniazdo
void
sendSocketMsg
(
String
destAddr
,
String
msg
)
{
try
{
//utworzenie gniazda
SocketConnection
client
=
(
SocketConnection
)
Connector
.
open
(
"socket://"
+
destAddr
+
":3000"
)
;
//otwarcie strumienii wejscia/wyjscia
InputStream
is
=
client
.
openInputStr
eam
()
;
OutputStream
os
=
client
.
openOutputSt
ream
()
;
// wysłanie wiadomosci doserwera
os
.
write
(
msg
.
getBytes
())
;
os
.
write
(
'\n'
)
;
// odczytanie odpowiedzi (dla
przykladu do znaku konca
linii \n lub gdy serwer
zamknie polaczenie)
int
c
=
0
;
StringBuffer
sb
=
new
StringBuffer
()
;
while
((
c
=
is
.
read
())
!=
-
1
&&
(
c
!=
'\
n'
))
{
sb
.
append
((
char
)
c
)
;
}
System
.
out
.
println
(
"[Serwer]"
+
sb
.
toString
())
;
//analiza odpowiedzi...
// zamkniecie strumieni oraz
polaczenia
is
.
close
()
;
os
.
close
()
;
client
.
close
()
;
}
catch
(
IOException
ex
)
{
ex
.
printStackTrace
()
;
}
}
Java Microedition
59
www.sdjournal.org
Software Developer’s Journal 6/2008
cie MIDP oraz konfiguracja Connected Limited Device Con-
figuration CLDC. Określa się w nich zbiór interfejsów i klas
dostępnych w danej KVM. Aktualnie dominującym profilem
jest MIDP 2.0 w połączeniu z konfiguracją CLDC 1.1 (lub
1.0). Jednakże istnieją także urządzenia określane mianem
wersji MIDP 1.0. W artykule zostanie zaznaczone od któ-
rej wersji profilu dostępny jest dany mechanizm jeśli bę-
dzie to istotne.
Spis metod integracyjnych
Biorąc pod uwagę ogólne standardy komunikacji siecio-
wej w JME można wyróżnić między innymi następujące
mechanizmy:
• Komunikacja niskopoziomowa – UDP, TCP, TLS(SSL);
• HTTP/HTTPS;
• RMI – J2MEPolishRMI, implementacja RMI bazująca na
gniazdach;
• XML-RPC – kXMLRPC, J2MEPolishRPC;
• Web Services – kSOAP[5], klient WebServices w NetBe-
ans, JSR-172;
• WMA – SMS/MMS;
• SIP;
• SyncML – kSync, implementacja SyncML;
• Peer2Peer – JXTA;
• Bluetooth;
• Inne:
Listing 2.
Metoda nasłuchujące na TCP
public
void
wait4TcpConnection
()
{
try
{
// utworzenie serwera nasluchujacego na porcie
3000
ServerSocketConnection
server
=
(
ServerSocketConnection
)
Connector
.
open
(
"socket://:3000"
)
;
// oczekiwanie na polaczenia
System
.
out
.
println
(
"Oczekiwanie na
polaczenie..."
)
;
SocketConnection
client
=
(
SocketConnection
)
server
.
acceptAndOpen
()
;
System
.
out
.
println
(
"Zaakceptowanie polaczenia z
adresu"
+
client
.
getAddress
())
;
// utworzenie strumienie wejscia/wyjscia
InputStream
is
=
client
.
openInputStream
()
;
OutputStream
os
=
client
.
openOutputStream
()
;
// czytanie danych ze strumienia (dla przykladu
do znaku konca linii \n lub gdy
klient zamknal polaczenie)
int
c
;
StringBuffer
sb
=
new
StringBuffer
()
;
while
(((
c
=
is
.
read
())
!=
-
1
)
&&
(
c
!=
'\n'
))
{
sb
.
append
((
char
)
c
)
;
}
System
.
out
.
println
(
"[KLIENT]"
+
sb
.
toString
())
;
//analiza wiadomosci ...
//wyslanie odpowiedzi
String
responseMsg
=
new
String
(
"Odpowiedz
serwera OK
\n
"
)
;
os
.
write
(
responseMsg
.
getBytes
())
;
// zamkniecie strumienii oraz polaczenia
is
.
close
()
;
os
.
close
()
;
client
.
close
()
;
server
.
close
()
;
}
catch
(
IOException
ex
)
{
ex
.
printStackTrace
()
;
}
}
Listing 3.
Przykładowa metoda POST
private
void
doSendHTTP
(
String
request
,
String
url
){
String
responseStr
=
null
;
try
{
//utworzenie polaczenia HTTP i ustawienie
nagłowka
HttpConnection
conn
=
(
HttpConnection
)
Connector
.
open
(
url
)
;
conn
.
setRequestMethod
(
HttpConnection
.
POST
)
;
conn
.
setRequestProperty
(
"Content-Length"
,
Integer
.
toString
(
reques
t
.
length
()))
;
//wyslanie wiadomosci
OutputStream
out
=
conn
.
openOutputStream
()
;
int
requestLength
=
request
.
length
()
;
for
(
int
i
=
0
;
i
<
requestLength
;
++
i
){
out
.
write
(
request
.
charAt
(
i
))
;
}
InputStream
in
=
conn
.
openInputStream
()
;
StringBuffer
responseBuf
;
long
length
=
conn
.
getLength
()
;
if
(
length
>
0
){
responseBuf
=
new
StringBuffer
((
int
)
length
)
;
}
else
{
responseBuf
=
new
StringBuffer
()
;
}
//odczytanie odpowiedzi
int
ch
;
while
((
ch
=
in
.
read
())
!=
-
1
)
{
responseBuf
.
append
((
char
)
ch
)
;
}
responseStr
=
responseBuf
.
toString
()
;
System
.
out
.
println
(
responseStr
.
toString
())
;
}
catch
(
IOException
e
){
e
.
printStackTrace
()
;
}
catch
(
SecurityException
e
){
e
.
printStackTrace
()
;
}
}
60
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 6/2008
• JSON,
• Burlap,
• JINI,
• GASP,
• OpenDMTP,
• Payment API,
• Jsch.
Komunikacja
niskopoziomowa oraz HTTP
Gniazda są mechanizmem komunikacji definiującym inter-
fejs programowania do wymiany informacji. W Java Micro-
edition (MIDP 2.0) można zrealizować połączenia gniazd
wykorzystując UDP, TCP i TLS.
Ogólny szkielet nawiązania połączenia w JME wykony-
wany jest przez następującą metodę fabryki
Connector.ope-
n("protocol:address;parameters")
z paczki javax.microedi-
tion.io . Metoda zwraca określony przez parametr
protocol
konkretny obiekt typu
Connection
. Na Rysunku 1 przedsta-
wiona jest hierarchia interfejsów, które mogą implemen-
tować obiekty zwrócone przez metodę
Connector.open()
.
Przeźroczysty sposób tworzenia połączenia daje uniwer-
salną metodę, która zgłasza wyjątek
ConnectionNotFoun-
dException
w przypadku gdy urządzenie nie implementuje
określonego połączenia.
TCP
Transmission Control Protocol jest połączeniowym proto-
kołem zapewniającym niezawodną komunikację strumie-
niową. Ustanawia dwukierunkową współpracę między ho-
stami z możliwością sterowania przepływem (Rysunek 2).
Kontroluje poprawność oraz kolejność pakietów danych i
oczekuje potwierdzania ich odbioru.
W przypadku wystąpienia błędów komunikacyjnych po-
trafi przeprowadzić retransmisję. Protokół TCP nie zobo-
wiązuje się jednak na zrealizowanie połączenia w określo-
nym czasie.
Przy nadmiernym obciążeniu łącz daje się zaobserwo-
wać nagłe spowolnienie transmisji. Spowodowane to jest
między innymi błędnymi pakietami i wysyłaniem żądań ich
ponownej retransmisji.
TCP w Java Microedition realizowane jest poprzez wy-
wołanie metody
Connector.open
oraz określenie minimalnie
parametrów: protocol jako
socket
oraz
address
jako adre-
su hosta docelowego wraz z portem. Przykładową metodę
wysyłającą wiadomość typu
String
na określony adres po-
dany w parametrach wywołania metody jest zaprezentowa-
ny na Listingu 1.
Natomiast aplikacja nasłuchująca i wykorzystująca
gniazda TCP jest przedstawiona na Listingu 2. Została ona
zrealizowana w JME w celu zobrazowania możliwości tej
technologii. Równie dobrze serwer oczekujący na połącze-
nia może być zaimplementowany na dowolnej maszynie ob-
sługującej gniazda TCP.
TLS(SSL)
Transport Layer Security jest protokołem bezpieczeństwa,
który bazuje na starszym protokole Secure Socket Layer
(SSL).
Ze względu na brak w TCP mechanizmów ochrony prze-
syłanych danych wykorzystuje się TLS. Umiejscowiony jest
on pomiędzy warstwą TCP a warstwą aplikacji co zapew-
nia prostotę jego wykorzystania w programie. Umożliwia
szyfrowanie danych, potwierdzanie tożsamości serwera/
klienta, zapewnianie integralności przesyłanych komuni-
katów. W JME (MIDP 2.0) TLS realizuje się po niewielkich
modyfikacjach kodu (Rysunek 3).
Bibliografia
• XML-RPC – zdalne wywoływanie procedur oparte na języku
XML http://www.xmlrpc.com .
• WMA – Wireless Messaging API zbiór klas i metod do ob-
sługi komunikacji SMS/MMS
• http://java.sun.com/products/wma/index.jsp
• SIP – Session Initiation Protocol protokół inicjowania sesji
• http://developers.sun.com/mobility/apis/articles/sip/
Listing 4.
HttpServlet storna serwera http
public
class
DeliveryServlet
extends
HttpServlet
{
public
void
init
(
ServletConfig
config
)
throws
ServletException
{
super
.
init
(
config
)
;
...
}
public
void
doPost
(
HttpServletRequest
request
,
HttpServletResponse
response
)
throws
ServletException
,
IOException
{
//odebranie wiadomosci od klienta i analiza jej
BufferedReader
br
=
request
.
getReader
()
;
String
buf
=
br
.
readLine
()
;
System
.
out
.
println
(
buf
)
;
//operacje zwiazane z otrzymanymi danymi ...
//przygotowanie odpowiedzi do klienta i wysłanie jej
response
.
setContentType
(
"text/html"
)
;
PrintWriter
out
=
response
.
getWriter
()
;
out
.
println
(
"Odpowiedz serwera OK"
)
;
out
.
close
()
;
}
...
Listing 5.
Interfejs Serwera RMI
package pl.awa;
public
interface
RMIServer
extends
Remote
{
//metoda dodaje uzytkownika
public
User
addUser
(
String
name
,
String
password
,
Address
address
)
throws
RemoteException
;
//zwraca obiekt klasy Address
public
Address
getAddress
(
String
userName
)
throws
RemoteException
;
Java Microedition
61
www.sdjournal.org
Software Developer’s Journal 6/2008
HTTP
Hypertext Transfer Protocol może służyć jako nośnik infor-
macji między MIDletem a serwerem. Określa on formę żą-
dań klienta, które tworzymy między innymi za pomocą po-
leceń GET lub POST. Synchronicznie tworzone są odpo-
wiedzi na żądanie (
response
).
Ze względu na to że jest to protokół bezstanowy, nie
zachowuje informacji o poprzednich transakcjach stosu-
je się mechanizm cookies. W przypadku Java Microedition
można wykorzystać zapis do pliku w RMS lub systemie pli-
ków (FileConnection API) urządzenia aby zapamiętać stan
transakcji. Standardowo w konfiguracji CLDC wykorzystu-
je się metodę
Connector.open
z określeniem protokołu http:
// oraz podaniem adresu serwera. Obiekt typu
HttpConnec-
tion
utworzony w ten sposób posiada metodę
setRequest-
Method
określającą rodzaj żądania
HttpConnection.POST
lub
HttpConnection.GET
. W przypadku wywołania POST można
zdefiniować nagłówek http poprzez metodę
setRequestPro-
perty(„name”,”value”)
jak to jest pokazane na przykłado-
wym Listingu 3.
Właściwa treść wiadomości do serwera jest przesyła-
na strumieniowo analogicznie jak w gniazdach. Kod odpo-
wiedzi serwera otrzymujemy za pomocą metod
getRespon-
seCode
, która zwraca statyczną wartość typu
integer
okre-
ślającą kod (zdefiniowaną w klasie
HttpConnection
) lub
ge-
tResponseMessage
w przypadku gdy istotna jest treść od-
powiedzi.
Natomiast stronę serwera można zrealizować w dowol-
nej technologii obsługującej HTTP. W artykule zostanie po-
kazana implementacja za pomocą Java Servlet. Na Listn-
Listing 6.
Klasy implementujące interfejs Serializacji
import de.enough.polish.io.Serializable;
public
class
User
implements
Serializable
{
public
String
name
;
public
Address
address
;
public
String
password
;
}
import de.enough.polish.io.Externalizable;
public
class
Address
implements
Externalizable
{
private
String
street
;
private
String
city
;
public
Address
()
{
// wymagany konstruktor
}
public
Address
(
String
street
,
String
city
){
super
()
;
this
.
street
=
new
String
(
street
)
;
this
.
city
=
new
String
(
city
)
;
}
public
void
read
(
DataInputStream
in
)
throws
IOException
{
this
.
street
=
in
.
readUTF
()
;
this
.
city
=
in
.
readUTF
()
;
}
public
void
write
(
DataOutputStream
out
)
throws
IOException
{
out
.
writeUTF
(
this
.
street
)
;
out
.
writeUTF
(
this
.
city
)
;
}
}
Listing 7.
Poprawne wywołanie metody RemoteClient.open
this
.
server
=
(
RMIServer
)
RemoteClient
.
open
(
"pl.awa.RMIServer"
,
"http://localhost:8080/awa/myservice"
)
;
Listing 8.
Błędne wywołanie metody RemoteClient.open
String
myInterfaceName
=
"pl.awa.RMIServer"
;
this
.
server
=
(
RMIServer
)
RemoteClient
.
open
(
myInterfaceName
,
"http://localhost:8080/awa/myservice"
)
;
Listing 9.
Zdalne wywołanie metody serwera
//...
User
user
=
this
.
server
.
addUser
(
name
,
password
,
address
)
;
System
.
out
.
println
(
"Name:"
+
user
.
name
.
toString
())
;
//...
Listing 10.
Implementacja serwera RMI
import de.enough.polish.rmi.RemoteException;
import de.enough.polish.rmi.RemoteHttpServlet;
public
class
GameServerImpl
extends
RemoteHttpServlet
implements
GameServer
{
public
User
addUser
(
String
name
,
String
password
,
Address
address
)
throws
RemoteException
{
//...
//utworzenie obiektu klasy User ktory bedzie zwrocony
przez metode
User
user
=
new
User
()
;
user
.
name
=
name
;
user
.
password
=
password
;
user
.
address
=
address
;
//...
return
user
;
}
public
Address
getAddress
(
String
userName
)
throws
RemoteException
{
//...
//wyszukiwanie uzytkownika po nazwie z bazy danych
String
street
=
getStreetFromDB
(
userName
)
;
String
city
=
getCityFromDB
(
userName
)
;
Address
address
=
new
Address
(
street
,
city
)
;
//...
return
address
;
}
}
62
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 6/2008
gu 4 wykorzystany zostaje interfejs
HttpServlet
oraz im-
plementacja dwóch jego metod
doGET
oraz
doPOST
, które re-
alizują określone w nazwie żądania i generują odpowiedzi
do klienta. Aplikację serwerową można uruchomić na kon-
tenerze wspierającym serwety jak np. Apache Tomcat lub
JBoss. Odnośnie wykorzystania HTTPS dostępnego od
MIDP 2.0 to analogia użycia jest podobna jak w przypad-
ku TLS.
RMI
Remote Method Invocation jest mechanizmem, który organizuje
komunikację między obiektami Java działającymi na różnych
maszynach wirtualnych w środowisku rozproszonym. Techni-
ka ta umożliwia zdalne wywołania metod (RPC) obiektów w ję-
zyku Java w przeźroczysty sposób dla programisty ukrywając
niskopoziomowe szczegóły związane z obsługą protokołów.
Cała idea opiera się na traktowaniu obiektów zdalnych
w ten sam sposób jak tych działających lokalnie. W konfi-
guracji CLDC JME standardowo nie można spotkać biblio-
teki odpowiedzialnej za obsługę RMI. Istnieje gotowe roz-
wiązanie opcjonalne J2MEPolish RMI, które jest dostęp-
ne w dwóch licencjach GPL oraz w komercyjnej Commer-
cial License.
Za pomocą tego narzędzia można w przyjemy i szybki
sposób zaimplementować mobilnego klienta komunikujące-
go się ze zdalnym serwerem. Wszystkie wywołania metod
Rysunek 1.
Hierarchia interfejsów komunikacji w JME
CLDC Generic Connection
Framework Interfaces
SockedConnection
HttpConnection (MIDP 1.0)
SecureConnection
MIDP
2.0
Interfaces
Connection
DatagramConnection
OutputConnection
InputConnection
StreamConnection
StreamConnectionNotifier
ServerSockedConnection
UDPDatagramConnection
ContentConnection (MIDP 1.0)
Rysunek 2.
Komunikacja poprzez gniazda TCP
Klient
Serwer
Serwer
MIDIet
TCP
TCP
IP
IP
Strumień danych lub
komunikaty aplikacji
Pakiety
Pakiety IP
Rysunek 3.
Transformacja na gniazda bezpieczeństwa TLS
TCP
TLS (SSL)
SocketConnection socketConnection =
(SocketConnection) Connection.open("socket://hostName:5000");
socketConnection.setSocketOption(SocketConnection.DELAY, 0);
OutputSteream os = socketConnection.openOutputStream());
SecureConnection socketConnection =
(SecureConnection) Connector.open("ssl://hostName:5001");
String secureProtocolName =
secureConnection.getSecurityInfo().getProtocolName();
OutputSteream os = secureConnection.openOutputSteream();
63
Java Microedition
www.sdjournal.org
Software Developer’s Journal 6/2008
oraz dostępność obiektów dają wrażenie ich lokalnego wy-
stępowania po stronie klienta i vice versa. Aby to uzmysło-
wić drogiemu Czytelnikowi w dalszej części zostaną przed-
stawione kolejne kroki jakie należy podjąć aby to zrealizo-
wać.
Na pierwszym etapie definiujemy zdalny interfejs, który bę-
dzie wspólnym protokołem między klientem a serwerem. Okre-
śla się w nim metody, które będzie udostępniał serwer. Jak
można zauważyć na Listingu 5 wymagane jest aby dziedziczył
on po interfejsie
de.enough.polish.rmi.Remote
oraz każda me-
toda musi mieć możliwość zgłaszania wyjątku
de.enough.po-
lish.rmi.RemoteException
.
W kolejnym kroku należy zdefiniować zbiór klas, któ-
re będą wspólne dla obydwu stron. Klasy te muszą imple-
mentować interfejsy serializacji
de.enough.polish.io.Seria-
lizable
lub de.enough.polish.io.Externalizable. Serializacja
w najprostszym określeniu polega na procesie przekształ-
cenia obiektów do postaci strumienia bajtów. Dzięki zasto-
sowaniu tego mechanizmu w RMI można przesyłać obiek-
ty poprzez strumieniowy protokół sieciowy. W klasie imple-
mentującej Serializable programista nie musi martwić się
o proces serializacji dokonuje się on automatycznie jak to
widać na Listingu 6. Natomiast różnica w klasie implemen-
tującej
Externalizable
wynika z tego że należy zaimple-
Listing 11.
Przykładowy plik WSDL usługi Wheather
<
?xml version=
"1.0"
encoding=
"UTF-8"
?
>
<!-- Published by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is JAX-WS RI
2.1.2_01-hudson-189-. --><!-- Generated by JAX-WS RI at http://jax-ws.dev.java.net. RI's version is
JAX-WS RI 2.1.2_01-hudson-189-. -->
<
definitions xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsp=
"http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:
soap=
"http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns=
"http://calculator.me.org/"
xmlns:xsd=
"http:
//www.w3.org/2001/XMLSchema"
xmlns=
"http://schemas.xmlsoap.org/wsdl/"
targetNamespace=
"http://
calculator.me.org/"
name=
"CalculatorWSService"
>
<
types
>
<
xsd:schema
>
<
xsd:import namespace=
"http://calculator.me.org/"
schemaLocation=
"http://localhost:8080/CalculatorApp/CalculatorWSService?xsd=
1"
><
/xsd:import
>
<
/xsd:schema
>
<
/types
>
<
message name=
"add"
>
<
part name=
"parameters"
element=
"tns:add"
><
/part
>
<
/message
>
<
message name=
"addResponse"
>
<
part name=
"parameters"
element=
"tns:addResponse"
><
/part
>
<
/message
>
<
portType name=
"CalculatorWS"
>
<
operation name=
"add"
>
<
input message=
"tns:add"
><
/input
>
<
output message=
"tns:addResponse"
><
/output
>
<
/operation
>
<
/portType
>
<
binding name=
"CalculatorWSPortBinding"
type=
"tns:CalculatorWS"
>
<
soap:binding transport=
"http://schemas.xmlsoap.org/soap/http"
style=
"document"
><
/soap:binding
>
<
operation name=
"add"
>
<
soap:operation soapAction=
""
><
/soap:operation
>
<
input
>
<
soap:body use=
"literal"
><
/soap:body
>
<
/input
>
<
output
>
<
soap:body use=
"literal"
><
/soap:body
>
<
/output
>
<
/operation
>
<
/binding
>
<
service name=
"CalculatorWSService"
>
<
port name=
"CalculatorWSPort"
binding=
"tns:CalculatorWSPortBinding"
>
<
soap:address location=
"http://localhost:8080/CalculatorApp/CalculatorWSService"
><
/soap:address
>
<
/port
>
<
/service
>
<
/definitions
>
64
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 6/2008
Listing 12.
Klasa pnia wygenerowany automatycznie
public
class
CalculatorWSService_Stub
implements
CalculatorWSService
,
javax
.
xml
.
rpc
.
Stub
{
private
String
[]
_propertyNames
;
private
Object
[]
_propertyValues
;
public
CalculatorWSService_Stub
()
{
_propertyNames
=
new
String
[]
{
ENDPOINT_ADDRESS_
PROPERTY
}
;
_propertyValues
=
new
Object
[]
{
"http://localhost:8080/
CalculatorApp/CalculatorWSService"
}
;
}
public
void
_setProperty
(
String
name
,
Object
value
)
{
int
size
=
_propertyNames
.
length
;
for
(
int
i
=
0
;
i
<
size
;
++
i
)
{
if
(
_propertyNames
[
i
]
.
equals
(
name
))
{
_propertyValues
[
i
]
=
value
;
return
;
}
}
String
[]
newPropNames
=
new
String
[
size
+
1
]
;
System
.
arraycopy
(
_propertyNames
, 0,
newPropNames
,
0,
size
)
;
_propertyNames
=
newPropNames
;
Object
[]
newPropValues
=
new
Object
[
size
+
1
]
;
System
.
arraycopy
(
_propertyValues
, 0,
newPropValues
,
0,
size
)
;
_propertyValues
=
newPropValues
;
_propertyNames
[
size
]
=
name
;
_propertyValues
[
size
]
=
value
;
}
public
Object
_getProperty
(
String
name
)
{
for
(
int
i
=
0
;
i
<
_propertyNames
.
length
;
++
i
)
{
if
(
_propertyNames
[
i
]
.
equals
(
name
))
{
return
_propertyValues
[
i
]
;
}
}
if
(
ENDPOINT_ADDRESS_PROPERTY
.
equals
(
name
)
||
USERNAME_PROPERTY
.
equals
(
name
)
||
PASSWORD_PROPERTY
.
equals
(
name
))
{
return
null
;
}
if
(
SESSION_MAINTAIN_PROPERTY
.
equals
(
name
))
{
return
new
Boolean
(
false
)
;
}
throw
new
JAXRPCException
(
"Stub does not recognize
property: "
+
name
)
;
}
protected
void
_prepOperation
(
Operation
op
)
{
for
(
int
i
=
0
;
i
<
_propertyNames
.
length
;
++
i
)
{
op
.
setProperty
(
_propertyNames
[
i
]
,
_propertyValu
es
[
i
]
.
toString
())
;
}
}
public
int
add
(
int
i
,
int
j
)
throws
java
.
rmi
.
RemoteException
{
Object
inputObject
[]
=
new
Object
[]
{
new
Integer
(
i
)
,
new
Integer
(
j
)
}
;
Operation
op
=
Operation
.
newInstance
(
_qname_operation_
add
,
_type_add
,
_type_addResponse
)
;
_prepOperation
(
op
)
;
op
.
setProperty
(
Operation
.
SOAPACTION_URI_PROPERTY
,
""
)
;
Object
resultObj
;
try
{
resultObj
=
op
.
invoke
(
inputObject
)
;
}
catch
(
JAXRPCException
e
)
{
Throwable
cause
=
e
.
getLinkedCause
()
;
if
(
cause
instanceof
java
.
rmi
.
RemoteException
)
{
throw
(
java
.
rmi
.
RemoteException
)
cause
;
}
throw
e
;
}
return
((
Integer
)((
Object
[])
resultObj
)[
0
])
.
intValue
()
;
}
protected
static
final
QName
_qname_operation_add
=
new
QName
(
"http://calculator.me.org/"
,
"add"
)
;
protected
static
final
QName
_qname_addResponse
=
new
QName
(
"http://calculator.me.org/"
,
"addResponse"
)
;
protected
static
final
QName
_qname_add
=
new
QName
(
"http://calculator.me.org/"
,
"add"
)
;
protected
static
final
Element
_type_addResponse
;
protected
static
final
Element
_type_add
;
static
{
_type_addResponse
=
new
Element
(
_qname_addResponse
,
_complexType
(
new
Element
[]
{
new
Element
(
new
QName
(
""
,
"return"
)
,
Type
.
INT
)})
, 1, 1,
false
)
;
_type_add
=
new
Element
(
_qname_add
,
_complexType
(
new
Element
[]
{
new
Element
(
new
QName
(
""
,
"i"
)
,
Type
.
INT
)
,
new
Element
(
new
QName
(
""
,
"j"
)
,
Type
.
INT
)})
, 1, 1,
false
)
;
}
private
static
ComplexType
_complexType
(
Element
[]
elements
)
{
ComplexType
result
=
new
ComplexType
()
;
result
.
elements
=
elements
;
return
result
;
}
}
65
Java Microedition
www.sdjournal.org
Software Developer’s Journal 6/2008
mentować metody
read
i
write
, które odpowiednio czyta-
ją ze strumienia oraz zapisują do strumienia. Jeśli jednak
nie ma potrzeby tworzenia nowych klas, które będą nośni-
kiem informacji możemy wykorzystać standardowe klasy
Java Microedition takie jak np.
String
lub
Vector
. W zależ-
ności o skomplikowania zastosowania mogą one stanowić
elementy parametrów metod jak i być zwracane przez me-
tody serwera. Większość standardowych klas wspiera se-
rializację jednak występują pewne ograniczenia w niektó-
rych przypadkach.
Zalecane jest zapoznanie się przed ich zastosowaniem
w dokumentacji dostępnej na stronie J2MEPolish. Na tym
poziomie kończy się definiowanie wspólnej części interfej-
su wymiany między klientem a serwerem. W dalszej części
należy zdefiniować klasy, które będą wykorzystywały owy
interfejs po stronie klienta i serwera. W kliencie tworzy-
my połączenie z serwerem wykorzystując metodę
de.eno-
ugh.polish.rmi.RemoteClient.open()
. Listing 7 przedstawia
poprawne konstrukcję wywołania tej metody. Przyjmuje
ona pełną nazwę interfejsu, który zdefiniowaliśmy oraz ad-
res serwera wraz ze ścieżką do serwisu RMI. Należy pa-
miętać aby definiować te parametry bezpośrednio w wy-
wołaniu metody a nie przypisując je do zmiennych a na-
stępnie przekazywaniu ich do metody jak to jest pokaza-
ne na Listingu 8.
W przypadku sukcesu nawiązania połączenia z serwe-
rem poprzez powyższą metodę klient może wywoływać me-
tody serwera poprzez interfejs o nazwie (RMIServer). Wy-
korzystuje się do tego referencje obiektu, który zwróciła
metoda open jak to przedstawia Listing 9.
W odpowiedzi w tym przypadku zwracany jest obiekt
klasy z którym należy obchodzić się zupełnie w sposób
lokalny.
Ostatnim etapem konstruowania naszego mechanizmu
wykorzystującego RMI jest implementacja serwera. Klasa
serwera jest serwletem dziedziczącym po de.enough.po-
lish.rmi.RemoteHttpServlet oraz implementująca jednocze-
śnie metody naszego wspólnego interfejsu o nazwie RMI-
Server (Listing 10). Jest to część wykonawcza całego me-
chanizmu i należy zadbać o poprawną konstrukcję zwra-
canych obiektów.
Proces kompilacji oraz budowania serwera wykra-
cza poza ramy tego artykułu, która prezentuje tylko za-
rys z poziomu języka Java. Jednak dla ułatwienia w kata-
logu {j2mepolish-dir}/samples/rmi znajduje się przykłado-
Listing 13.
Zdalny interfejs serwera
public
interface
CalculatorWSService
extends
java
.
rmi
.
Remote
{
public
int
add
(
int
i
,
int
j
)
throws
java
.
rmi
.
RemoteExc
eption
;
}
Listing 14.
Przykład wywołania metody serwera
public
Integer
getRemoteResult
(
int
x
,
int
y
){
Integer
result
=
null
;
try
{
//utworzenie obiektu reprezentujacego serwer
CalculatorWSService
webClient
=
new
CalculatorWSService_Stub
()
;
//wywolanie zdalnej metody
int
z
=
webClient
.
add
(
x
,
y
)
;
result
=
new
Integer
(
z
)
;
}
catch
(
Exception
ex
)
{
System
.
err
.
(
ex
)
;
}
return
result
;
}
Rysunek 4.
Web services podstawowe elementy i operacje
Serwer usług
Rejestr UDDI
3. Komunikacja SOAP
1. Rejestracja usługi - opis WSDL
2. Wyszukiwane usługi
W Sieci
• http://developers.sun.com/mobility/allarticles/#networking
• h t t p : / / d e v e l o p e r s . s u n . c o m / m o b i l i t y / m i d p / a r t i c l e s/
socketRMI/
• http://www.j2mepolish.org/cms/leftsection/documentation/
rmirpc.html
• http://kxmlrpc.objectweb.org
• http://ksoap.objectweb.org/
• ht tp : //w w w.netbeans .org /kb / 60 /mobilit y /mobile - dil-
bert.html
• http://developers.sun.com/mobility/allarticles/#ws
• http://developers.sun.com/mobility/allarticles/#wma
• http://ksync.objectweb.org
• http://developers.sun.com/mobility/midp/articles/syncml/
• ht tp : //java .sun.com /developer/ Books /J2MEwireless/
J2ME12.pdf
• http://developers.sun.com/mobility/midp/articles/jxme/
• http://developers.sun.com/mobility/allarticles/#wma
• http://tavon.org/work/JSON-J2ME
• http://www.forum.nokia.com/main/resources/technologies/
java/documentation/networking.html
• http://developer.sonyericsson.com/wiki/display/leftnav/
Java+Connectivity
• http://developer.motorola.com/docstools/
• http://developer.samsungmobile.com/Developer/index.jsp
66
Inżynieria
oprogramowania
www.sdjournal.org
Software Developer’s Journal 6/2008
wy projekt gdzie został napisany skrypt w ANT budujący
jednocześnie aplikację klienta i serwer. Będzie on stano-
wił pomocą deskę dla początkujących programistów. Uwa-
gę należy zwrócić na proces obfuskacji dokonywany czę-
sto podczas budowania aplikacji. Podczas tego procesu
paczka jar wspólnych klas także zostaje poddana tej opty-
malizacji.
Należy pamiętać, że jeśli dokonano obfuskacji to pacz-
ka jar wspólnych klas musi się znaleźć po obu stronach
jednocześnie na kliencie jak i na serwerze. Pominięcie te-
go faktu jest sygnalizowane najczęściej wyjątkiem Class
not found: a lub Class cast exception wynika to przede
wszystkim ze zmiany nazwy klas po zakończeniu pracy
obfuscatora.
Web Services SOAP
Simple Object Access Protocol jest standardem wymiany
informacji, którego składnia oparta jest na XML. Protokół
SOAP należy do grupy rozwiązań określanych terminem
Web Services.
Technologia Web Services zapewnia konstrukcję roz-
proszonych komponentów usługowych. W ramach Web
Services oprócz SOAP odpowiedzialnego za zdalne wy-
woływanie usługi, istnieje jeszcze WSDL oraz UDDI (Ry-
sunek 4).
Język opisu interfejsu WSDL (Web Services Descrip-
tion Language) służy do dystrybucji usług sieciowych nie-
zależnie od jej implementacji. UDDI (Universal Descrip-
tion, Discovery and Integration) ułatwia udostępnianie do-
kumentów WSDL przez umieszczenie ich w specjalnej ba-
zie danych.
Zarejestrowane komponenty usług mogą być następnie
przeszukiwane przez klientów w celu ich wykorzystania.
Wracając do Java Microedition komunikację SOAP moż-
na zrealizować wykorzystując z jednej strony opcjonalną
paczkę JSR-172 lub dołączyć do aplikacji bibliotekę jar z
implementacją tego protokołu. Specyfikacja JSR-172 Web
Service jest uboższym zbiorem interfejsów pochodzącym
od JSE API JAX-RPC(1.1). Dostarcza infrastrukturę Web
Services bazująca na synchronicznym modelu zdalnych
wywołań procedur RPC.
Oferuje szeroką paletę typów danych oraz komunikację
poprzez wiele protokołów sieciowych między innymi popu-
larnego HTTP(S). W porównaniu do JSE wersja mobilna
jest poddana znacznym restrykcjom:
• nie wspiera asynchronicznych wiadomości;
• brak załączników w SOAP;
• wiadomości w reprezentacji literal (document/literal);
• brak wsparcia mapowania typów (brak paczki ja-
vax.xml.rpc.encoding);
• brak mechanizmu wyszukiwania UDDI.
Napisanie MIDletu zintegrowanego z serwerem za pomo-
cą SOAP dokonujemy od zlokalizowania adresu url usłu-
gi Web Service.
W artykule dla ułatwienia zostanie wykorzystany przy-
kładowy serwis Calculator pochodzący z pakietu NetBe-
ans 6.0 (menu File->New Project->Samples->Web Service-
>Calculator).
Zostaje on uruchomiony na lokalnym serwerze także do-
łączonym do tego narzędzia. Zatem adres do pliku wsdl jest
następujący: http://localhost:8080/CalculatorApp/CalculatorW
SService?wsdl
Definicja pliku WSDL dla danego serwisu może wyglądać
tak jak to przedstawia Listing 11.
W dalszej kolejności przechodzimy do realizacji klienta w
którym należy zaimplementować:
• stub czyli pień czyli najprościej określając klasę przez
którą przechodzą wszystkie żądania i odpowiedzi z
serwera;
• interfejs serwisu;
• klasę uruchamiającą zdalną metodę Web Service.
Generowanie klasy pnia można dokonać automatycznie korzy-
stając z takich narzędzi jak Carbide.j lub Netbeans IDE 6.0,
WTK Sun 2.5. We wszystkich przypadkach po prostu należy
podać adres url do pliku WSDL Web Service jak jest to poka-
zane na Rysunku 5 .
Więcej informacji na temat tych narzędzi oraz wspo-
mnianego procesu znajduje się na stronach: http://www.j
2mepolish.org/cms/leftsection/documentation/rmirpc.html, http://
kxmlrpc.objectweb.org. W wyniku zautomatyzowanego działa-
nia tych narzędzi zostaje wygenerowana klasa pnia (Listing 12)
oraz zdalny interfejs serwera (Listing 13).
Utworzone w ten sposób klasy po prostu dołączamy do
naszej aplikacji. Przykładowe wywołanie metody serwera
przy zastosowaniu wygenerowanych klas przedstawia Li-
sting 14.
Jak można zaobserwować proces tworzenia klienta oka-
zał się w większości automatyczny. Cała technika tworzenia
jest przeźroczysta dla programisty. Wygenerowane klasy są
bezpośrednio napisane w Java ukrywając szczegóły imple-
mentacyjne SOAP.
Podsumowanie
Aby ułatwić Czytelnikowi wybór właściwej metody do swoje-
go zastosowania zostanie przedstawione krótkie zestawienie
mocnych i słabych stron opisanych metod.
Gniazda są szybkim rozwiązaniem w przypadku pro-
stych wiadomości nie wymagające definiowania rozbudo-
wanego protokołu. Dostępne są na każdym urządzeniu od
Rysunek 5.
Carbide.j oraz NeatBeans IDE 6.0 generowanie
pnia