PASOE i serwisy WebHandler cz. II

Reasumując to co można było prześledzić w poprzednim artykule, warstwa transportowa WEB jest związana z interfejsem biznesowym podobnym do REST. Wykorzystuje te same żądania (czasowniki) HTTP (GET, PUT, POST,…). Istotna różnica to przechwytywanie obsługi żądań przez WebHandler, czyli klasę OO ABL. Dzięki temu unika się żmudnego mapowania, a deweloperzy dostają duże możliwości dopasowania obsługi do własnych potrzeb. Dostęp do serwisu jest przez URI, w którym zamiast warstwy REST podaje się WEB oraz nazwę WebHandlera, który obsługuje dany serwis np: webh1/web/webh1.
Webh1 jest tutaj nazwą serwisu, WEB wartswą transportową, a następne wystąpienie webh1, WebHandlerem (domyślny WebHandler ma tę samą nazwę jak serwis).

W drugim przykładzie (także opartym na bazie wiedzy) pokażę jak zdefiniować w projekcie/serwisie dodatkowe URI i jak zrealizować dostęp do 2 obiektów business entity.

Utwórzmy kolejny projekt webh2, typ Server i warstwa transportowa WEB. Projekt jest połączony z baza sports2000. Kto nie wie jak to zrobić niech zajrzy do części I.

Definiujemy business entity: customerBE dla tablicy customer i orderBE dla tablicy order. Zostają utworzone dwie klasy customerBE.cls oraz orderBE.cls (a także pliki .i) – patrz poniżej.

Teraz klikamy prawym klikiem myszy na nazwę serwisu i wybieramy Edit oraz przycisk Add.


Dodajemy URI: /Customer oraz /Orders.

Modyfikujemy plik klasy webh2Handler.cls aby miał następującą postać (dokładniej, modyfikowana jest metoda HandleGet):

USING Progress.Lang.*.
USING OpenEdge.Web.WebResponseWriter.
USING OpenEdge.Net.HTTP.StatusCodeEnum.
USING OpenEdge.Web.WebHandler.
USING Progress.Json.ObjectModel.*.
USING customerBE.*.
USING orderBE.*.  

BLOCK-LEVEL ON ERROR UNDO, THROW.

CLASS webh2Handler INHERITS WebHandler: 

 {"customerbe.i"}
 {"orderbe.i"}   	
		
	/*------------------------------------------------------------------------------
            Purpose: Handler for unsupported methods. The request being serviced and
            		 an optional status code is returned. A zero or null value means 
            		 this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotAllowedMethod( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
		/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */
        	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
	END METHOD.


	/*------------------------------------------------------------------------------
            Purpose: Handler for unknown methods. The request being serviced and an 
                     optional status code is returned. A zero or null value means 
                     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotImplemented( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
		/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
   	END METHOD.
 	
	
	/*------------------------------------------------------------------------------
            Purpose: Default handler for the HTTP GET method. The request being 
                     serviced and an optional status code is returned. A zero or 
                     null value means this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
 	METHOD OVERRIDE PROTECTED INTEGER HandleGet( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
 	
	
		DEFINE VARIABLE oResponse AS OpenEdge.Net.HTTP.IHttpResponse NO-UNDO.
        DEFINE VARIABLE oWriter   AS OpenEdge.Web.WebResponseWriter  NO-UNDO.
        DEFINE VARIABLE oBody     AS OpenEdge.Core.String            NO-UNDO.
       
        DEFINE VARIABLE jsonObj AS JsonObject NO-UNDO.
        DEFINE VARIABLE lcJSON AS LONGCHAR NO-UNDO.

        ASSIGN 
            oResponse            = NEW OpenEdge.Web.WebResponse()
            oResponse:StatusCode = INTEGER(StatusCodeEnum:OK).
        

        jsonObj = NEW JsonObject().
        
        IF ENTRY(2,poRequest:PathInfo,"/") = "Customer" THEN
        DO: 
            DEFINE VARIABLE beCustomer AS customerBE NO-UNDO.
            DEFINE VARIABLE hTTCustomer AS HANDLE NO-UNDO.
            
            beCustomer = NEW customerBE().
            beCustomer:ReadcustomerBE("",OUTPUT DATASET dsCustomer).
            hTTCustomer = TEMP-TABLE ttCustomer:HANDLE.
            hTTCustomer:WRITE-JSON ("JsonObject",jsonObj).
            lcJSON= jsonObj:GetJsonText().
            oBody = NEW OpenEdge.Core.String(lcJSON).
                      
            ASSIGN    
                oResponse:Entity = oBody
                oResponse:ContentType   = 'application/json':u
                oResponse:ContentLength = oBody:Size.
        END.
        ELSE
           IF ENTRY(2,poRequest:PathInfo,"/") = "Orders" THEN
           DO:
            DEFINE VARIABLE beOrder AS orderBE NO-UNDO.
            DEFINE VARIABLE hTTOrder AS HANDLE NO-UNDO.
            
            beOrder = NEW orderBE().
            beOrder:ReadOrderBE("",OUTPUT DATASET dsOrder).
            hTTOrder = TEMP-TABLE ttOrder:HANDLE.
            hTTOrder:WRITE-JSON ("JsonObject",jsonObj).
            lcJSON= jsonObj:GetJsonText().
            oBody = NEW OpenEdge.Core.String(lcJSON).
                      
            ASSIGN 
                oResponse:Entity = oBody
                oResponse:ContentType   = 'application/json':u
                oResponse:ContentLength = oBody:Size.
          END.
          ELSE
          DO:
            ASSIGN
                oBody = NEW OpenEdge.Core.String(
                                 'Hello '
                               + '~r~n':u   /*CRLF */
                               + 'This is the default message by th HandleGet in webh2Handler.'
                               ).
            ASSIGN
                oResponse:Entity        = oBody
                oResponse:ContentType   = 'text/plain':u
                oResponse:ContentLength = oBody:Size.                               
         END.
       
        ASSIGN 
            oWriter = NEW WebResponseWriter(oResponse).
        oWriter:Open().
        
        oWriter:Close().
        
        RETURN 0.
		
 	END METHOD. 
 	 	   	
	
END CLASS.

Po uruchomieniu serwera PASOE możemy przetestować działanie serwisu.
Wpisujemy w przeglądarce URL dla danych z tablicy Customer: http://localhost:8810/webh2/web/Customer.

Następnie dla Orders: http://localhost:8810/webh2/web/Orders.

A na końcu testujemy domyślny WebHandler: http://localhost:8810/webh2/web/webh2.

Jeszcze wspomnę, że dodawać WebHandlery i przypisywać do nich URI można na różne sposoby.
Jednym z nich, choć niepolecanym, jest plik konfiguracyjny instancji serwera aplikcaji openedge.properties (podkatalog conf).

O wiele lepszym sposobem jest interfejs webowy OpenEdge Explorer.
Wybieramy: Progress Application Server -> oepas1 -> ABL Application: oepas1 -> ABL WebApp: webh2 -> WEB Transport Configuration

PASOE i serwisy WebHandler cz. I

Deweloperzy aplikacji webowych z wykorzystaniem serwera PASOE mają od wersji 11.6.3 nową możliwość tworzenia serwisów typu Data Object. Są to tzw. WebHandlery, które działają z wykorzystaniem warstwy transportowej WEB (Data Object WebHandler Service).

WebHandler zapewnia bardziej wydajną i prostszą warstwę komunikacyjną niż REST. Jest napisany w ABL, łatwiej dopasować go do własnych potrzeb i ma ulepszone możliwości debugowania. Podstawową zaletą WebHandlera jest to, że daje programistom pełną kontrolę nad przychodzącymi i wychodzącymi danymi.

Warstwa WEB obsługuje żądania (requests) i odpowiedzi (responses), które używają standardowych czasowników HTTP (verbs). Obejmuje to interakcje z klientami, takimi jak WebSpeed i OpenHTTP.
Jeśli chcesz zmienić domyślny adres URL, możesz dodać dodatkowe WebHandlery i zmapować je na różne adresy URL lub zmienić odwzorowania domyślnych WebHandlerów.

Dodatkowe WebHandlery można dodać w pliku openedge.properties dla instancji OEPAS i zmapować je na określone adresy URL. Na przykład:

defaultHandler=OpenEdge.Web.CompatibilityHandler
webhandler1=MyHandler:/mycustomer
webhandler2=MyHandler:/mycustomer/{custid}

OpenEdge.Web.CompatibilityHandler zapewnia kompatybilność z aplikacjami WebSpeed SpeedScript i CGI Wrapper. Jest to domyślny handler używany w instancji w środowisku programistycznym.

Tworzenie tych serwisów zilustruję dwoma przykładami zaczerpniętymi z bazy wiedzy.

Zaczynamy od założenia, że mamy instancję serwera PASOE z podłączoną bazą danych sports2000.
Tworzymy nowy projekt OpenEdge. Nadajemy mu nazwę np. webh1, wybieramy typ Server i zaznaczamy warstwę transportową WEB.

Klikamy Next, sprawdzając czy do projektu jest podłączona instancją serwera aplikacji, a w ostatnim kroku upewniamy się, że jest przyłączona nasza baza danych.

Kreator tworzy od razu klasę WebHandlera, w tym przypadku webh1Handler.cls, zawierającą 3 metody: HandleNotAllowedMethod, HandleNotImplemented oraz HandleGet. Dwie pierwsze są związane z wychwytywaniem błędów. HandleGet to główna metoda, w której można przetwarzać dane otrzymane z odpowiedzi (response).

Stworzymy teraz obiekt danych. W Project Explorerze zaznaczamy nazwę projektu i prawym klikiem myszy wybieramy New -> Business Entity.


Podajemy nazwę customerBE, klikamy Next i wybieramy z bazy tabelę customer.

W efekcie jest utworzona znana z poprzednich artykułów klasa customerBE.cls.
Do klasy tej będziemy odnosić się z WebHandlera, czyli pliku webh1Handler.cls. Teraz należy zmodyfikować metodę HandleGet aby miała postać tak jak w poniższej klasie webh1Handler.cls (można skopiować cały plik klasy).

Widać, że oprogramowanie metod wymaga znajomości programowania obiektowego i odpowiednich referencji, ale daje bardzo duże możliwości. W poniższym przykładzie w odpowiedzi HTTP pobierana jest zawartość tablicy ttCustomer (z obiektu customerBE) i wyświetlana poprzez obiekt JSON.

USING Progress.Lang.*.
USING OpenEdge.Web.WebResponseWriter.
USING OpenEdge.Net.HTTP.StatusCodeEnum.
USING OpenEdge.Web.WebHandler.
USING customerBE.*.
USING Progress.Json.ObjectModel.*.

BLOCK-LEVEL ON ERROR UNDO, THROW.

CLASS webh1Handler INHERITS WebHandler: 

	    {"customerbe.i"}
		
	/*------------------------------------------------------------------------------
            Purpose: Handler for unsupported methods. The request being serviced and
                     an optional status code is returned. A zero or null value means 
            	     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotAllowedMethod( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
        /* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */
        	
		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
	END METHOD.


	/*------------------------------------------------------------------------------
            Purpose: Handler for unknown methods. The request being serviced and an 
                     optional status code is returned. A zero or null value means 
                     this method will deal with all errors.                                                               
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
	METHOD OVERRIDE PROTECTED INTEGER HandleNotImplemented( INPUT poRequest AS OpenEdge.Web.IWebRequest ):
	
	/* Throwing an error from this method results in a 500/Internal Server Error response. 
        The web handler will attempt to log this exception.
 	    
        See the HandleGet method's comments on choosing a value to return from this method. */	

		UNDO, THROW NEW Progress.Lang.AppError("METHOD NOT IMPLEMENTED").
   	END METHOD.
 	
	
	/*------------------------------------------------------------------------------
            Purpose: Default handler for the HTTP GET method. The request being 
                     serviced and an optional status code is returned. A zero or 
                     null value means this method will deal with all errors.                                                           
            Notes:                                                                        
    ------------------------------------------------------------------------------*/
 	METHOD OVERRIDE PROTECTED INTEGER HandleGet( INPUT poRequest AS OpenEdge.Web.IWebRequest ): 	

	DEFINE VARIABLE oResponse AS OpenEdge.Net.HTTP.IHttpResponse NO-UNDO.
        DEFINE VARIABLE oWriter   AS OpenEdge.Web.WebResponseWriter  NO-UNDO.
        DEFINE VARIABLE oBody     AS OpenEdge.Core.String            NO-UNDO.
             
        DEFINE VARIABLE beCustomer AS customerBE NO-UNDO.
        DEFINE VARIABLE pcFilter AS CHAR NO-UNDO.
        DEFINE VARIABLE lcJSON AS LONGCHAR NO-UNDO.

        DEFINE VARIABLE jObj AS JsonObject.
        DEFINE VARIABLE htt AS HANDLE.
        
        jObj = NEW JsonObject().
        /* Get data from the BE */
        beCustomer = NEW customerBE().
        beCustomer:ReadcustomerBE(INPUT pcfilter,OUTPUT DATASET dsCustomer).
        htt = TEMP-TABLE ttCustomer:HANDLE.
        htt:WRITE-JSON("JsonObject", jObj).

        ASSIGN 
            oResponse            = NEW OpenEdge.Web.WebResponse()
            oResponse:StatusCode = INTEGER(StatusCodeEnum:OK)
                        .
        /* This body object can be a string or something else (JsonObject for instance) */
        lcJSON= jObj:GetJsonText().
        ASSIGN 
            oBody = NEW OpenEdge.Core.String(lcJSON).            
                              
        ASSIGN 
            oResponse:Entity        = oBody
            /* HTTP messages require a content type */
            oResponse:ContentType   = 'application/json':u
            /* ContentLength is good too */
            oResponse:ContentLength = oBody:Size
            .
        
        /* The WebResponseWriter ensures that the status line and
           all headers are writted out before the message body/entity. */
        ASSIGN 
            oWriter = NEW WebResponseWriter(oResponse).
        oWriter:Open().
        
        /* Finish writing the response message */
        oWriter:Close().
        
        /* A response of 0 means that this handler will build the entire response;
           a non-zero value is mapped to a static handler in the webapp's /static/error folder.
           The mappings are maintained in the webapps's WEB-INF/web.xml 
           A predefined set of HTTP status codes is provided in the OpenEdge.Net.HTTP.StatusCodeEnum 
           enumeration */
        RETURN 0.
		
 	END METHOD. 
 		
END CLASS.

W przeglądarce wpisujemu URL dla naszej instancji PASOE np.: http://localhost:8810/webh1/web/webh1 (lub https://localhost:8811/webh1/web/webh1 dla bezpiecznego połączenia).
W efekcie dostajemy ekran podzielony na 3 przekładki:
JSON

Raw Data

Headers

Tworzenie projektów w OE Developer Studio 11.7.2

Podczas kilku wcześniejszych artykułów pokazywałem jak utworzyć różne proste projekty w OpenEdge Developer Studio. Służyły one np. do wystawienia serwisów REST z wykorzystaniem obiektów JSDO i automatycznym generowaniem klas lub bardziej złożone z ręcznym mapowaniem parametrów. Przykłady te dotyczyły nowego serwera PASOE choć można było wykorzystać także klasyczny AppServer.

Takie tworzenie projektów odbywa się poprzez tzw. wizardy, których zadaniem jest “poprowadzenie za rękę” dewelopera, aby ułatwić mu życie. Jeśli idzie o intuicyjność wizardów, np. dla projektów typu OpenEdge, których jest kilka rodzajów, to pozostawiały one nieco do życzenia i bez znajomości zdobytej np. poprzez webinary lub dokukmentację łatwo można było popełnić błąd.

W ostatnich wersjach OpenEdge proces ten został poprawiony a jego klarowność jest zdecydowanie lepsza.

Tworząc nowy projekt typu OpenEdge od razu pojawia się okno (poniżej), w którym rodzaje projektów są pogrupowane w 3 grupy: Server, Client, General. Wybierając grupę i typ projektu w polu Description pojawia się krótki opis.


W grupie Server można zdecydować czy wykorzystany będzie serwer PASOE czy klasyczny AppServer.

Dla PASOE można dalej wybrać warstwę transportową: APSV, REST lub WEB.
Pierwsze dwie warstwy były omawiane na tym blogu. APSV służy do obsługi procedur ABL poprzez HTTP.
REST to projekt, w którym możemy wykorzystać wywołania RESTful API i ręcznie mapować wybrane pola. Jeśli zaznaczymy Create a DataObject Service, będzie to projekt z automatycznym mapowaniem struktur ProDataSet do JSDO.

WEB służy do utworzenia projektu z wykorzystaniem wywołań RESTful API przez klasę WebHandler. Ten rodzaj nie był jeszcze omawiany na blogu.

Jeśli zamiast PASOE wybierzemy klasyczny AppServer to będziemy miali do wyboru 2 rodzaje: APSV i REST.

Grupa Client to przede wszystkim projekty związane z budowaniem aplikacji desktopowej, może to być aplikacja GUI for .NET, z wykorzystaniem AppBuildera lub znakowa (ChUI). Można także budować interfejs webowy WEB UI lub klasyczny WebSpeed.

Grupa General, to podstawowy projekt oraz pozostałe z grupy OpenEdge, np. Dynamics lub (omawiany na Pizza & Beer) ABLUnit do przeprowadzania testów ABL. Można wreszcie stworzyć projekt Custom z konfiguracją dopasowaną do własnych potrzeb.

Wystawianie serwisów REST z aplikacji ABL cz.II

Po dłuższej przerwie wracamy do zagadnienia związanego z udostępnieniem serwisu typu REST, który został zdefiniowany tutaj.

Taki zdefiniowany serwis trzeba wyeksportować. W tym celu klikamy prawym przyciskiem myszy na nazwę serwisu i wybieramy Export. Eksportowanie można zrobić na 2 sposoby: jako plik WAR lub ZIP. W pierwszym przypadku jest to oddzielny kompletny serwis, w drugim, serwis będzie doinstalowany do domyślnego serwisu ROOT  (Export services incrementally). Tutaj wybieram tę drugą metodę.


Proces wdrożenia przeprowadzimy na innej maszynie, na bazie danych sportsdb (kopia bazy sports2000).

Do wdrożenia potrzebny będzie plik klasy (customer.r) oraz oczywiście definicji serwisu (CustomerServ.zip).

Na nowej maszynie mamy instalację OpenEdge 11.7 z katalogiem roboczym C:\WrkOpenEdge117 i domyślną instalacją PAS: oepas1.

Najpierw w katalogu C:\WrkOpenEdge117\oepas1\openedge umieszczamy plik customer.r. Katalog ten znajduje się na liście PROPATH serwera aplikacji. W parametrach startowych agenta podajemy nazwę bazy, tutaj: -db sportsdb -ld sports2000. Parametr -ld (logiczna nazwa bazy) jest konieczny, ponieważ plik customer.r był skompilowany dla bazy sports2000. Zarówno PROPATH jak i parametry przyłączenia bazy można zobaczyć na poniższym obrazie.

Teraz nawigujemy do katalogu C:\WrkOpenEdge117\oepas1\webapps
gdzie umieszczamy plik CustomerServ.zip.

Uruchamiamy proenv i upewniamy się, że jesteśmy w powyższym katalogu. Podajemy komendę, która wdroży serwis przyrostowy dla ROOTa:

C:\WrkOpenEdge117\oepas1\bin\deployREST.bat CustomerServ.zip ROOT

Po zrestartowaniu serwera aplikacji serwis REST jest gotowy. Możemy go teraz przetestować. W tym celu w przeglądarce Chrome otwieramy Advanced REST client. Aplikacja jest typu application/json. Podajmy żądanie GET dla pobrania wybranego rekordu (metoda ReadCustomer): localhost:8810/rest/CustomerServService/Customers/5

Odpowiedź widać poniżej: rekord o nr 5 został pobrany i wyświetlony.

Podobnie sprawdzamy działanie metody ReadAllCustomers.

Wyświetlone są wszystkie rekordy.

Serwis działa poprawnie.

Następnym razem omówię trochę zapomniane web serwisy typu SOAP, ale wywoływane przez nowy serwer aplikacji PASOE.

Wystawianie serwisów REST z aplikacji ABL cz.I

We wcześniejszych wpisach opisałem sposób tworzenia serwisów uruchamianych na serwerze aplikacji PAS (obecna nazwa to Application Server for OpenEdge), korzystających z danych strukturalnych JSDO.

Pora teraz na bardziej praktyczny przykład, opisujący wystawienie własnych serwisów REST ze zdefiniowaniem wystawianych zasobów i ich mapowaniem. W porównaniu do wcześniejszej metody będzie więcej czynności do wykonania ale dostaniemy znacznie większe pole manewru.

Na początek mamy projekt CustomerServ typu OpenEdge i prostą aplikację – klasę ABL Customer.cls z metodami dla zasobu Customer: ReadCustomer, ReadAllCustoomers, UpdateCustomer.


Ważne jest aby te metody były state-free, tzn. nie przekazywały kontekstu między wywołaniami.

Teraz prawy klik myszy na nazwie klasy Customer.cls -> Progress OpenEdge -> Define Service Interface.

Wybieramy Definition mode: REST i zaznaczamy wszystkie metody klasy, NEXT.

Do pliku klasy zostaną wstawione adnotacje. Nic tu nie zmieniamy. Finnish.

Widzimy, że pod Defined Services został zdefiniowany serwis CustomerServService oraz został dodany katalog PASOEContent dla serwera aplikacji.

Po prawej stronie otwiera się REST Resource URI Editor. Jeśli go nie ma to: prawy klik myszy Defined Service -> CustomerServService i wybieramy Edit.

Z operacjami na danych związane są tzw. czasowniki HTTP (HTTP verbs):

POST: operacja CREATE – tworzy nowy rekord

GET (domyślny): operacja RETRIEVE – zwraca rekord na bazie podanego ID

PUT: operacja UPDATE – aktualizuje istniejący rekord

DELETE: operacja DELETE – usuwa istniejący rekord.

Dodajemy 2 zasoby: /Customer (dla wszystkich rekordów Customer) oraz /Customer/{custNum}.

Dla zasobu /Customer kojarzymy verb GET z metodą ReadAllCustomers. Dla /Customer/{custNum} kojarzymy verb GET z metodą ReadCustomer, a PUT z UpdateCustomer.

Teraz trzeba zmapować parametry dla każdej operacji pomiędzy parametrami wejścia HTTP request a parametrami wejścia ABL oraz parametrami wyjścia ABL a parametrami HTTP response.

Najpierw mapujemy operacje dla ReadAllCustomers GET: input Complete URL w HTTP Request do URI  w Interface Parameters; Query String Parameters w HTTP Request do num w Interface Parameters.
Teraz output: ttAllCustomers w Interface Parameters do Body w HTTP Response.

Dla ReadCustomer GET mapujemy: input – Path Parameters custNum w HTTP Request do custNum w Interface Parameters.
Output: CustomerRecord w Interface Parameters do Body w HTTP Response.

Na koniec dla UpdateCustomer PUT mapujemy: input – Path Parameters custNum w HTTP Request do custNum w Interface Parameters oraz Body w HTTP Request do CustomerRecord w Interface Parameters.
Output: CustomerRecord w Interface Parameters do Body w HTTP Response.

Zakończyliśmy definiowanie serwisu REST.

 

Serwisy REST – porównanie różnych typów

W kilku wcześniejszych artykułach opisałem, krok po kroku, tworzenie serwisu typu REST, wykorzystującego obiekty JSDO.

Wiem od niektórych z Was, że z tymi serwisami wiążą się pewne niejasności. Nic w tym dziwnego, to nie są progressowe technologie i wiążą się o stworzone niedawno standardy.

Kilka osób pytało mnie np. o różnice między tworzeniem serwisu REST na bazie projektów: Data Object, ABL Web App czy REST (rysunek poniżej).

Pierwsze dwa typy projektów służą do tworzenia serwisów Data Object. W Progress Developer Studio for EpenEdge zaimplementowano dwa typy projektów ze względu na dwa rodzaje AppServerów, z którymi współpracują.
Projekt Data Object jest wykorzystywany do tworzenia serwisów przy użyciu klasycznego AppServera, a ABL Web App z Pacific AppServer. Do wersji OpenEdge 11.5 włącznie zamiast tych dwóch projektów był dostępny tylko projekt Mobile.

W obu projektach (Data Object, ABL Web App) można utworzyć Business Entity – obiekt danych np. Customer czy Order, z którym tworzona jest klasa obiektu oraz obiekty JSDO. Klasa zawiera gotowe metody realizujące różne funkcje na danych (Create, Read, Update, Delete). Tworzony jest także plik . json, który można użyć definiując obiekty w środowisku Rollbase czy Telerik. Jest to prosta, szybka metoda, nie wymagająca szczegółowej wiedzy dewelopera nt serwisów REST.

Inne podejście jest reprezentowane w projekcie typu REST. Projekt ten wybierają deweloperzy  gdy aplikacja wymaga bezpośredniego dostępu do kontekstu HTTP (request/response), nagłówka, cookies itp. Daje to dodatkowe możliwości ale wymaga o wiele więcej pracy. Trzeba samemu “zamapować” każdą procedurę, klasę i parametr; dodać notacje itd.

Np. dodać nowe zasoby, parametry oraz przypisać im określone czasowniki (verb) możemy w narzędziu REST Resource URI Editor.

Serwis OpenEdge – Telerik Kendo UI

W niniejszym “odcinku” pokażę jak wykorzystać utworzony wcześniej serwis REST OpenEdge jako źródło danych w produkcie Telerik Kendo UI.

Mamy więc gotowy serwis i działający serwer aplikacji PAS.

Kendo UI, to zestaw bibliotek tworzących interfejs webowy oparty na HTML5 i JavaScript.

Należy pobrać plik ZIP i rozpakować w lokalnym katalogu.
Zawiera on bibliotekę funkcji JavaScript dla obiektów JSDO oraz przykładowe pliki HTML. Każdy z takich plików zawiera linki do dalszych bibliotek na stronach Progressa i Telerika.
Pliki te wygodnie jest otwierać w Notepad++.

Otwórzmy w Notepad++ plik Our_Sample.html. W sekcje HEAD znajdują się odnośniki do zewnętrznych bibliotek.

Nas bardziej interesuje sekcja SCRIPT, a dokładniej parametry serviceURI, catalogURI oraz resourceName, które powinny mieć wartości zgodne z utworzonym serwisem.

Po edycji tych parametrów plik HTML wygląda następująco:

Klikając na nagłówek kolumny dane są sortowane wg jej wartości. Można przeciągnąć nagłówek kolumny do górnej strefy i wtedy dane będa pogrupowane. Można także edytować, kasować i tworzyć nowe rekordy.

Pozostałe pliki html prezentują dane z serwisów zewnętrznych. Np. Other_KendoUIPieChart.html

Co teraz? Chciałbym pokazać jak wykorzystać dane z naszego serwisu w Telerik Platform for OpenEdge, ale przedtem trzeba wziąć na warsztat system Rollbase. Do Telerika jednak niedługo wrócimy.

Tworzenie serwisów OpenEdge cz. II

W poprzedniej części pokazałem jak stworzyć projekt oraz serwis OpenEdge. Teraz zajmiemy się utworzeniem logiki biznesowej aby wystawić przez ten serwis dane z bazy Progress.

W widoku Project Explorer klikamy prawym klawiszem myszy na podkatalog AppServer i wybieramy New -> Business Entity.

Tworzymy nową klasę obiektu biznesowego np. Customer. Pamiętajmy, że w nazwie rozróżnia się wielkość liter. Można opcjonalnie wypełnić pola Description i Purpose. Klikamy Next.

Ekran Select a schema file. Wybieramy aktywne połączenie z bazą danych oraz tabelę Customer. (W tym miejscu możemy zamiast połączenia z bazą wskazać przygotowany wcześniej plik schematu, w którym będzie zdefiniowany ProDataSet z kilkoma tabelami). Zauważmy listę operacji, jakie zostaną zdefiniowane w klasie. Domyślnie są to CRUD czyli: Create, Read, Update, Delete i takie zostawiamy.

Klikamy Finish.

Można zauważyć, że w projekcie pojawiły się nowe zasoby (podkatalog AppServer): plik klasy Customer.cls, plik customer.i z definicjami ProDataSeta dsCustomer oraz tablicy tymczasowej ttCustomer.

Plik Customer.cls zawiera gotowe metody dla operacji CRUD. Dane będą przekazywane między bazą OpenEdge a ProDataSetem. Możemy modyfikować te metody dla naszych potrzeb. Teraz jednak zostawiamy je w stanie domyślnym.

Teraz nowy obiekt biznesowy trzeba dodać do serwisu. Rozwijamy Defined Services i prawym klawiszem klikamy na Edit.

Ekran Edit an ABL Service. Nic tu nie zmieniamy. Next.

Ekran Create a Data Object service. Zaznaczamy zdefiniowane wcześniej zasoby, czyli klasę Customer. Zanotujmy składnię URI, przez którą będzie dostęp do naszych zasobów OpenEdge w serwisie REST. Finish.

W podkatalogu static został wygenerowany plik mobile6.json zawierający opis serwisu. Zajrzymy do niego za chwilę.

Otwieramy widok Servers i uruchamiamy naszą instancję appservera oepas1 . Może to potrwać kilka minut.

Po uruchomieniu appservera PAS wpisujemy w przeglądarce www link:

http://localhost:8810/mobile6/rest/mobile6Service/Customer

Widać, że udostępniliśmy tabelę Customer poprzez serwis REST.

Jeśli chcemy podejrzeć plik json, możemy go odszukać i otworzyć na dysku lub wpisać poniższy link:

http://localhost:8810/mobile6/static/mobile6Service.json

Następnym razem pokażę prosty przykład jak wykorzystać gotowy serwis z technologią Telerik.

Tworzenie serwisów OpenEdge cz. I

Chcę teraz napisać o tworzeniu serwisów typu REST, które są bardzo przydatne w integracji danych pochodzących z baz OE z nowym front-endem np. Rollbase, Telerik Kendo UI czy z urządzeniami mobilnymi.

Wspomniałem o tym w zeszłym miesiącu w artykule o JSDO.

Serwisy tworzymy w Progress Developer Studio for OpenEdge. Zakładam, że mamy skonfigurowane środowisko: w zakładce Servers widzimy : Pacific AppServer (domyślna nazwa oepas1)

oraz w Preferencjach mamy zdefiniowane połączenie z bazą danych sports (kopia bazy sports2000).

Gdyby były problemy z dojściem do tego miejsca, to bardzo proszę o wiadomość. Opis jest tutaj.

Proces tworzenia nieznacznie różni się od tego miejsca w OE 11.5 od OE 11.6. Najpierw zajmiemy się wersją OE 11.5.


Tworzymy nowy projekt OpenEdge (nazwa np. mobile5), wybieramy typ Mobile. Klikamy Next.

Pojawia się ekran Select AVM and Layout Options. Nic tu nie zmieniamy, klikamy Next.

Następny ekran: Define AppServer content module. Zaznaczamy, który serwer aplikacji będzie obsługiwał nasz serwis. Tutaj wybieramy PAS, czyli oepas1. (Gdybyśmy chcieli użyć tradycyjnego OE AppServera, to moglibyśmy wybrać restbroker1).

Zaznaczamy Publish changes immediately i Next.

Ekran Create a Mobile service. Upewniamy się, że zaznaczona jest opcja Create a Mobile service oraz jako Supported servers wybrany oepas1. Klikamy Next.

Ekran Create a Mobile App. Ponieważ nie budujemy aplikacji mobilnej, a jedynie serwis, odznaczamy tę opcję. Next.

Ekran Define PROPATH. Nic nie zmieniamy. Next.

Ekran Select database connections. Dodajemy do projektu istniejące połączenie z bazą danych (zdefiniowane wcześniej). Klikamy Next.

Ostatni ekran: Static Web Pages, zawiarający konfigurację folderów serwisu. Nic tu nie zmieniamy. Klikamy Finish. Tworzony jest nowy projekt.

Po utworzeniu powinniśmy zobaczyć zasoby projektu jak poniżej.


Teraz zrobimy to samo dla OE 11.6.

Tworzymy nowy projekt OpenEdge (nazwa np. mobile6), wybieramy typ ABL Web App (dla klasycznego AppServera wybieramy typ Data Object). Klikamy Next.

Ekran Provide ABL Web App deploy details.

Zaznaczamy Web Application: Deploy as WebApp, Sopported servers: oepas1 oraz Publish changes immediately. Klikamy Next.

Na ekranie Ceate an ABL Service zaznaczamy typ serwisu: Data Object (Annotated RPC) i klikamy Next.

Ekran Select AVM and Layout Options. Nic tu nie zmieniamy, klikamy Next.

Kolejny ekran Define PROPATH. Nic nie zmieniamy. Next.

Ekran Select database connections. Dodajemy do projektu istniejące połączenie z bazą danych. Klikamy Finish i czekamy aż serwis zostanie utworzony.

W drugiej części zajmiemy się wystawianiem danych z bazy OpenEdge poprzez utworzony serwis.