Wstęp Modele powiązań WS-R
Transkrypt
Wstęp Modele powiązań WS-R
Integracja usług w Internecie laboratorium nr 5 Katedra Architektury Systemów Komputerowych Wydział Elektroniki, Telekomunikacji i Informatyki Politechniki Gdańskiej mgr inż. Karol Bańczyk Globus Toolkit WebServices Resource Framework. Usługi z wieloma zasobami, własności zasobów. Wstęp Niniejsza instrukcja stanowi podzbiór informacji zawartych w punktach 3-6 doskonałego samouczka programistów Globus Toolkit autorstwa Borji Sotomayora, dostępnego pod adresem: http://gdp.globus.org/gt4-tutorial/ . Zaleca się przerobienie owego samouczka, zwłaszcza, gdy ujęcie tematu w niniejszym opracowaniu okaże się dla czytelnika zbyt skondensowane. Niniejsza instrukcja traktuje o tym, jak tworzyć i osadzać usługi sieciowe powiązane z zasobami na platformie Globus Toolkit. Za zarządzanie takimi parami WS-Resource odpowiada specjalny podsystem globusa implementujący specyfikację WebServices Resource Framework. Specyfikacja ta stała się standardem organizacji OASIS. Można o niej poczytać na stronach: OASIS WSRF Page oraz Globus WSRF Page . Najważniejszą przyczyną dla której owa specyfikacja powstała był fakt, że istniejący standard WebServices, doskonale nadający się do sieci usług o globalnym zasięgu, nie wypowiada się do problemu stanowości. Ten niedostatek uzupełnia właśnie WSRF, w którym mowa już nie o samych usługach sieciowych, ale raczej o parach: WebService-Resource. Zasoby posiadają własności (ResourceProperties), opisujące ich stan. Niniejsza instrukcja skupia się właśnie na tym, jak można stworzyć i umieścić na platformie globus toolkit usługę sieciową połączoną z zasobami oraz jak dostać się do własności owych zasobów. Modele powiązań WS-R Można wyróżnić 3 podstawowe modele powiązań usługi sieciowej z zasobami: 1. Implementacja usługi sieciowej oraz zasobu znajduje się w tej samej klasie. 2. Zasób i usługa sieciowa są różnymi klasami, zasób jest pojedynczym obiektem (Singleton). 3. Usługa sieciowa może działać w kontekście różnych zasobów. Szerszy opis przypadków 1 i 2 znajduj się w samouczku w punktach 3 i 4. Niniejszym zostanie szerzej przedstawiony przykład stworzony w zgodzie z trzecim modelem, najbardziej rozbudowanym z pośród wymienionych. Opis tego modelu można poprzeć niniejszym rysunkiem zaczerpniętym z samouczka: Obiekt klienta współpracuje z dwoma usługami sieciowymi, nazwanymi na rysunku: Factory Service i Instance Service. Rolą pierwszego z nich jest dostarczenie fasady pozwalającej stworzyć nowy zasób (wzorzec projektowy Factory) i wykorzystać go w powiązaniu z usługą na nim operującą, czyli właśnie Instance Service. Obiekt ResourceHome jest wykorzystywany przez fabrykę do stworzenia nowego zasobu, który jest modelowany przez klasę Resource. Dla rozpoznania tego modelu przedstawiony zostanie przykład usługi sieciowej obliczającą całkę na zadanym obszarze. Obszar będzie dla prostoty jednowymiarowy i będzie opisany przez pewną całkowitoliczbową funkcję f(n). Stworzenie i umieszcznie usługi sieciowej na zainstalowanym kontenerze globus toolkit składa się z następujących etapów: 1. Zdefiniowanie interfejsu usług sieciowych (w tym przypadku InstanceService i FactoryService). Interfejs definiuje się w języku nieco rozszerzonym na potrzeby stanowości języku WSDL. 2. Implementacja usługi w języku programowania (w tym przypadku będzie to Java). 3. Zdefiniowanie plików deskryptorów: WSDD i JNDI. 4. Stworzenie archiwum GAR (globus archive) zbierającego wcześniej wytworzone artefakty i wygenerowane na ich podstawie pieńki (stubs) w jednym miejscu. 5. Wdrożenie pliku archiwalnego na kontener globusa. 6. Stworzenie i uruchomienie pliku klienta. Interfejs i własności usługi Wszystkie plik wytwarzane w ramach tego opisu najlepiej pobrać z załączonego archiwum. Interfejs usługi sieciowej będzie obejmować dwie operacje: ● setRange(x1,x2), określającą zakres całkowania oraz ● integrate(), rozkazujacą dokonania całkowania. Stan zasobu, z którego będzie korzystać WS składać się będzie z czterech własności: x1, x2, state, integral. Pierwsze dwie opisują przedział całkowania, trzecia określa stan zasobu (przed całkowaniem, po całkowaniu, w trakcie całkowania, błąd), integral przechowuje informację o wartości całki, o ile stan wskazuje na to, że została obliczona. Interfejsy tworzonej usługi właściwej oraz fabryki w rozszerzonym języku WSDL przedstawione są niżej. Nie będą tutaj dokładnie opisywane szczegóły, zwłąszcza, że istnieje pełna analogia do tego, co opisane jest w samouczku w punkcie 3 oraz szczególnie w dodatku A1 http://gdp.globus.org/gt4- tutorial/singlehtml/progtutorial_0.2.1.html#appendix_howto_wsdl . Plik Range.wsdl definiujacy usługę RangeService ma postać: <?xml version="1.0" encoding="UTF-8"?> <definitions name="RegionService" targetNamespace="http://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsrp="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.xsd" xmlns:wsrpw="http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft01.wsdl" xmlns:wsdlpp="http://www.globus.org/namespaces/2004/10/WSDLPreprocessor" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <wsdl:import namespace= "http://docs.oasis-open.org/wsrf/2004/06/wsrf-WS-ResourceProperties-1.2-draft-01.wsdl" location="../../wsrf/properties/WS-ResourceProperties.wsdl" /> <!--============================================================ T Y P E S ============================================================--> <types> <xsd:schema targetNamespace="http://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance" xmlns:tns="http://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!-- REQUESTS AND RESPONSES --> <xsd:element name="setRegion"> <xsd:complexType> <xsd:sequence> <xsd:element name="x1" type="xsd:int"/> <xsd:element name="x2" type="xsd:int"/> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="setRegionResponse"> <xsd:complexType/> </xsd:element> <xsd:element name="integrate"> <xsd:complexType/> </xsd:element> <xsd:element name="integrateResponse"> <xsd:complexType/> </xsd:element> <!-- RESOURCE PROPERTIES --> <xsd:element name="X1" type="xsd:int"/> <xsd:element name="X2" type="xsd:int"/> <xsd:element name="State" type="xsd:string"/> <xsd:element name="Integral" type="xsd:int"/> <xsd:element name="RegionResourceProperties"> <xsd:complexType> <xsd:sequence> <xsd:element ref="tns:X1" minOccurs="1" maxOccurs="1"/> <xsd:element ref="tns:X2" minOccurs="1" maxOccurs="1"/> <xsd:element ref="tns:State" minOccurs="1" maxOccurs="1"/> <xsd:element ref="tns:Integral" minOccurs="1" maxOccurs="1"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </types> <!--============================================================ M E S S A G E S ============================================================--> <message name="SetRegionInputMessage"> <part name="parameters" element="tns:setRegion"/> </message> <message name="SetRegionOutputMessage"> <part name="parameters" element="tns:setRegionResponse"/> </message> <message name="IntegrateInputMessage"> <part name="parameters" element="tns:integrate"/> </message> <message name="IntegrateOutputMessage"> <part name="parameters" element="tns:integrateResponse"/> </message> <!--============================================================ P O R T T Y P E ============================================================--> <portType name="RegionPortType" wsdlpp:extends="wsrpw:GetResourceProperty" wsrp:ResourceProperties="tns:RegionResourceProperties"> <operation name="setRegion"> <input message="tns:SetRegionInputMessage"/> <output message="tns:SetRegionOutputMessage"/> </operation> <operation name="integrate"> <input message="tns:IntegrateInputMessage"/> <output message="tns:IntegrateOutputMessage"/> </operation> </portType> </definitions> Plik Factory.wsdl definiujacy usługę FactoryService ma postać: <?xml version="1.0" encoding="UTF-8"?> <definitions name="FactoryService" targetNamespace="http://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:tns="http://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!--============================================================ T Y P E S ============================================================--> <types> <xsd:schema targetNamespace="http://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService" xmlns:tns="http://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:import namespace="http://schemas.xmlsoap.org/ws/2004/03/addressing" schemaLocation="../../ws/addressing/WS-Addressing.xsd" /> <!-- REQUESTS AND RESPONSES --> <xsd:element name="createResource"> <xsd:complexType/> </xsd:element> <xsd:element name="createResourceResponse"> <xsd:complexType> <xsd:sequence> <xsd:element ref="wsa:EndpointReference"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </types> <!--============================================================ M E S S A G E S ============================================================--> <message name="CreateResourceRequest"> <part name="request" element="tns:createResource"/> </message> <message name="CreateResourceResponse"> <part name="response" element="tns:createResourceResponse"/> </message> <!--============================================================ P O R T T Y P E ============================================================--> <portType name="FactoryPortType"> <operation name="createResource"> <input message="tns:CreateResourceRequest"/> <output message="tns:CreateResourceResponse"/> </operation> </portType> </definitions> Jeśli katalog rozpakowania dostarczonego archiwum to uznany zostanie za bieżący to przedstawione pliki znajują się odpowiednio w ./scheme/lab5/RangeService_instance/Range.wsdl i ./scheme/lab5/FactoryService/Factory.wsdl. Miejsce plików jest istotne, a zwłaszcza liczba zagłębień w stosunku do katalogu bieżącego. Należy dodatkowo stworzyć plik o nazwie namespace2package.mapping w katalogu bieżącym zawierający następujące mapowania przestrzeni nazw na pakiety zawierające klasy implementujące i stuby. http\://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance=org.pg.ti.lab5.stubs.RegionService_i nstance http\://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance/bindings=org.pg.ti.lab5.stubs.Region Service_instance.bindings http\://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance/service=org.pg.ti.lab5.stubs.RegionS ervice_instance.service http\://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService=org.pg.ti.lab5.stubs.FactoryService http\://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService/bindings=org.pg.ti.lab5.stubs.FactoryService .bindings http\://eti.pg.gda.pl/namespaces/ti/lab5/FactoryService/service=org.pg.ti.lab5.stubs.FactoryService. service Implementacja Wymagane jest, by klasy wchodzące w skład jednego archiwum GAR znajdowały się w pakiecie postaci nazwa1.nazwa2.nazwa3.impl. Wówczas deskryptory jndi i wsdd będą w pakiecie nazwa1.nazwa2.nazwa3. Na implementajcę skłądają się następujące klasy pakietu : FactoryService, RegionQNames, RegionResourceHome, RegionResource, RegionService, UniqueSequence i WorkerThread. Wszystkie zostały zawarte w pakiecie org.pg.ti.lab5.impl. Przed przystąpieniem do implementacji klas warto wygenerować pieńki (stubs), gdyż bez tego będzie niezręcznie tworzyć kod, który nie będzie się chciał skompilować. Z tego powodu należy w katalogu rozpakowanego archiwum wywołać polecenia: ./globus-build-service.sh -d org/pg/ti/lab5 -s schema/lab5/RegionService_instance/Region.wsdl -t stubs ./globus-build-service.sh -d org/pg/ti/lab5 -s schema/lab5/FactoryService/Factory.wsdl -t stubs Spowoduje to wygenerowanie samych pieńków i pozwoli na kompilację tworzonych klas. Implementacja klas FactoryService, RegionQName, RegionResourceHome, RegionResource jest w pełni analogiczna do przykładu przedstawionego w samouczku w punkcie 5, więc nie będzie omawiana szczegółowo. Klasa UniqueSequence została użyta na potrzeby uzyskania unikalnych identyfikatorów kolejno tworzonych zasobów. Klasa WorkerThread została wprowadzona, by zademonstrować, jak zadania mogą być wykonywane w tle. Z obiektem RegionService jest skojrzaona prosta pula 10 wątków, które gotowe są do przyjmowania zleceń. W wypadku wywołania na metody integrate na serwisie RegionService próbuje on przydzielić zadanie całkowania zadanego obszaru całkowania jednemu z wolnych wątków. Gdy brak takiego wątku zgłaszany jest wyjątek. Całkowanie przeprowadza zasób ze stanu UNINTEGRATED przez stan INTEGRATING do stanu INTEGRATED, gdy proces został zakończony. Pozwoli do m.in. klientowi określić, czy zasób już jest gotowy do odczytu. Oto kod klasy implementującej usługę sieciową. package org.pg.ti.lab5.impl; import java.rmi.RemoteException; import org.globus.wsrf.ResourceContext; import org.pg.ti.lab5.stubs.RegionService_instance.Integrate; import org.pg.ti.lab5.stubs.RegionService_instance.IntegrateResponse; import org.pg.ti.lab5.stubs.RegionService_instance.SetRegion; import org.pg.ti.lab5.stubs.RegionService_instance.SetRegionResponse; import org.pg.ti.lab5.impl.WorkerThread; import org.pg.ti.lab5.impl.WorkerThread; public class RegionService { WorkerThread[] thread = new WorkerThread[10]; public RegionService(){ // inicjalizacja puli wątków for (int i=0; i< thread.length; i++){ thread[i] = new WorkerThread(); thread[i].start(); } } // pobieranie zasobu skojarzonego z danym wołaniem (kontekstem) private RegionResource getResource() throws RemoteException { Object resource = null; try { ResourceContext resourceContext = ResourceContext.getResourceContext(); System.out.println("resource key = " + resourceContext.getResourceKey()); resource = ResourceContext.getResourceContext().getResource(); } catch (Exception e) { e.printStackTrace(); throw new RemoteException("", e); } return (RegionResource) resource; } // ustawienie obszaru całkowania public SetRegionResponse setRegion(SetRegion setRegion) throws RemoteException{ System.out.println("setRegion(" + setRegion.getX1() +","+ setRegion.getX2() + ")"); RegionResource resource = getResource(); resource.setX1( setRegion.getX1() ); resource.setX2( setRegion.getX2() ); resource.setState(RegionResource.UNINTEGRATED); resource.setIntegral(0); return new SetRegionResponse(); } // wysłanie polecenie całkowania public IntegrateResponse integrate(Integrate integrate) throws RemoteException{ assignResourceToThread(); return new IntegrateResponse(); } // próba przydzielenia wątku całkującego do bieżącego zasobu synchronized private void assignResourceToThread() throws RemoteException { for (int i=0; i<thread.length; i++){ if ( thread[i].isWaitingForNewRequests()) { thread[i].performIntegration(getResource()); return; } } throw new RemoteException("No worker thread available"); } } Deskryptory wdrożenia W nadpakiecie pakietu impl zawierającego klasy implementacyjne powinny znaleźć się pliki wdrożeniowe wsdd oraz jndi. Ich postać w przypadku omawianego przykładu jest następująca: deploy-servier.wsdd: <?xml version="1.0" encoding="UTF-8"?> <deployment name="defaultServerConfig" xmlns="http://xml.apache.org/axis/wsdd/" xmlns:java="http://xml.apache.org/axis/wsdd/providers/java" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <!-- Instance service --> <service name="ti/lab5/RegionService" provider="Handler" use="literal" style="document"> <parameter name="className" value="org.pg.ti.lab5.impl.RegionService"/> <wsdlFile>share/schema/lab5/RegionService_instance/Region_service.wsdl</wsdlFile> <parameter name="allowedMethods" value="*"/> <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/> <parameter name="scope" value="Application"/> <parameter name="providers" value="GetRPProvider"/> </service> <!-- Factory service --> <service name="ti/lab5/RegionFactory" provider="Handler" use="literal" style="document"> <parameter name="className" value="org.pg.ti.lab5.impl.FactoryService"/> <wsdlFile>share/schema/lab5/FactoryService/Factory_service.wsdl</wsdlFile> <parameter name="allowedMethods" value="*"/> <parameter name="handlerClass" value="org.globus.axis.providers.RPCProvider"/> <parameter name="scope" value="Application"/> <parameter name="instance" value="ti/lab5/RegionService"/> </service> </deployment> Wiele z parametrów wdrożeniowych poznanych już było przy okazji laboratoriów związanych z serwerem AXIS. Na szczególną uwagę zasługuje parametr “providers”, który dokonuje niejako wstrzyknięcie implementacji metody do serwisu RegionService umożliwiającej pobranie informacji o własnościach zasobu skojarzonego z usługą. Możliwe jest to również dzięki atrybutowi wsdlpp:extends="wsrpw:GetResourceProperty" zdefiniowanemu w pliku WSDL, stanowiącemu zarazem punkt rozszerzenia standardowego WSDL. deploy-jndi-config.xml: <?xml version="1.0" encoding="UTF-8"?> <jndiConfig xmlns="http://wsrf.globus.org/jndi/config"> <!-- Instance service --> <service name="ti/lab5/RegionService"> <resource name="home" type="org.pg.ti.lab5.impl.RegionResourceHome"> <resourceParams> <parameter> <name>resourceClass</name> <value>org.pg.ti.lab5.impl.RegionResource</value> </parameter> <parameter> <name>factory</name> <value>org.globus.wsrf.jndi.BeanFactory</value> </parameter> <parameter> <name>resourceKeyType</name> <value>java.lang.Integer</value> </parameter> <parameter> <name>resourceKeyName</name> <value>{http://eti.pg.gda.pl/namespaces/ti/lab5/RegionService_instance}RegionResourceKey</value> </parameter> </resourceParams> </resource> </service> <!-- Factory service --> <service name="ti/lab5/RegionFactory"> <resourceLink name="home" target="java:comp/env/services/ti/lab5/RegionService/home"/> </service> </jndiConfig> Plik JNDI wdraża obie usługi. Parametry dokonują powiązania usługi z obiektem Home oraz z obiektem zasobu. Dodatkowo przekazywana jest informacja o typie klucza identyfikującego usługę oraz o jego kwalifikowanej nazwie. Węzeł resourceLink pozwala na odwołanie się do własności zdefiniowanej gdzie indziej w pliku, w tym wypadku do własności home usługi RegionService. Przypisana ona zostaje też usłudze fabryki. Plik GAR Gdy wszystkie pliki są już gotowe należy skorzystać ze skryptu globus-build-service.sh . Dla wygody najlepiej stworzyć plik build.mappings w katalogu bieżącym i wpisać w nim wiersz: factory,org/pg/ti/lab5,schema/lab5/RegionService_instance/Region.wsdl,schem a/lab5/FactoryService/Factory.wsdl Pozwoli to utworzyć archiwum GAR przez wywołanie komendy: ./globus-build-service.sh factory Gdy plik jest gotowy należy jako użytkownik globus (lub inny posiadający uprawnienia) umieścić plik gar w kontenerze globusa poleceniem globus-deploy-gar ./org_pg_ti_lab5.gar oraz jako ten sam użytkownik uruchomić kontener globusa: globus-start-container -nosec (-nosec tylko dla celów testowych). Program testowy Program testowy działa w następujący sposób: Tworzy 5 niezależnych zasobów, każdemu przypisując inny zakres całkowania, następnie dla każdego zasobu uruchamia obliczanie całki i sprawdza przy użyciu standardowego mechanizmu pobierania informacji o własnościach zasobów, czy zadanie już zostało wykonane, a jeśli tak, to podaje wynik. Oto kod klienta: package org.pg.ti.client; import java.rmi.RemoteException; import java.util.HashSet; import java.util.Set; import javax.xml.namespace.QName; import javax.xml.rpc.ServiceException; import import import import import import import import import import import import import import import import import org.apache.axis.message.addressing.Address; org.apache.axis.message.addressing.EndpointReferenceType; org.apache.axis.types.URI.MalformedURIException; org.globus.wsrf.encoding.ObjectSerializer; org.globus.wsrf.encoding.SerializationException; org.pg.ti.lab5.impl.RegionQNames; org.pg.ti.lab5.stubs.FactoryService.CreateResource; org.pg.ti.lab5.stubs.FactoryService.CreateResourceResponse; org.pg.ti.lab5.stubs.FactoryService.FactoryPortType; org.pg.ti.lab5.stubs.FactoryService.service.FactoryServiceAddressingLocator; org.pg.ti.lab5.stubs.RegionService_instance.Integrate; org.pg.ti.lab5.stubs.RegionService_instance.RegionPortType; org.pg.ti.lab5.stubs.RegionService_instance.SetRegion; org.pg.ti.lab5.stubs.RegionService_instance.service.RegionServiceAddressingLocator; org.oasis.wsrf.properties.GetResourcePropertyResponse; org.oasis.wsrf.properties.InvalidResourcePropertyQNameFaultType; org.oasis.wsrf.properties.ResourceUnknownFaultType; public class Client { public static void main(String[] args) throws ServiceException, MalformedURIException, SerializationException, RemoteException, InterruptedException { FactoryServiceAddressingLocator factoryLocator = new FactoryServiceAddressingLocator(); RegionServiceAddressingLocator instanceLocator = new RegionServiceAddressingLocator(); String factoryURI = args[0]; EndpointReferenceType factoryEPR, instanceEPR; FactoryPortType regionFactory; // Get factory portType factoryEPR = new EndpointReferenceType(); factoryEPR.setAddress(new Address(factoryURI)); regionFactory = factoryLocator.getFactoryPortTypePort(factoryEPR); // Create resource and get endpoint reference of WS-Resource. // This resource is our "instance". RegionPortType[] regionService = new RegionPortType[5]; for (int i=0; i<5; i++) { CreateResourceResponse createResponse = regionFactory .createResource(new CreateResource()); instanceEPR = createResponse.getEndpointReference(); // Get instance PortType regionService[i] = instanceLocator.getRegionPortTypePort(instanceEPR); regionService[i].setRegion(new SetRegion( i*100 , (i+1) *100)); regionService[i].integrate(new Integrate()); } Set finished = new HashSet(); while ( true ) { for ( int i=0; i< regionService.length; i++) { RegionPortType pt = regionService[i]; if (finished.size() == 5) System.exit(0); if (finished.contains(new Integer(i))) continue; String String String String if (state.equals("INTEGRATED")) { System.out.println("Integral for range ( " + x1 + ", " + x2 + ") = " + integral ); } } } state = getResourceProperty(pt, RegionQNames.RP_STATE); x1 = getResourceProperty(pt, RegionQNames.RP_X1); x2 =getResourceProperty(pt, RegionQNames.RP_X2); integral =getResourceProperty(pt, RegionQNames.RP_INTEGRAL); finished.add(new Integer(i)); } if ( state.equals("ERROR")) { System.err.println("Error while integrating range :( " + x1 + ", " + x2 + ")") ; finished.add((new Integer(i))); } System.out.println("resource[" +( i +1) + "].state=" +state); } Thread.sleep(1000); static String getResourceProperty(RegionPortType pt, QName qname) throws InvalidResourcePropertyQNameFaultType, ResourceUnknownFaultType, RemoteException{ GetResourcePropertyResponse resourcePropertyResponse = pt.getResourceProperty(qname); String value = resourcePropertyResponse.get_any()[0].getValue(); return value; } Szczególną uwagę należy zwrócić na metodę getResourceProperty wykorzystującą standardowego dostawcę tej usługi wstrzykniętego w kod usługi sieciowej. Są jeszcze 3 inne metody dostępu do usługi zasobów: SetResourceProperties, GetMultipleResourceProperties oraz QueryResourceProperties. Szczegółów należy szukać w rozdziale 6 samouczka. Kod klienta można skompilować w następujący sposób: javac -classpath ./build/stubs/classes/:$CLASSPATH org/pg/ti/client/Client.java a następnie uruchomić (po uprzednim uruchomieniu serwera) w następujący sposób: java -classpath ./build/stubs/classes/:$CLASSPATH org.pg.ti.client.Client http://127.0.0.1:8080/wsrf/services/ti/lab5/RegionFactory