O Java Message Service

V tomto príklade vytvoríme malú webovú aplikáciu - kalkulačku, využitím technológie JMS. Na strane klienta používateľ zadá dve čísla A, B a vyberie operáciu +,-,*,/. Tieto údaje sa odošlú na výpočet na server. Server požiadavku spracuje, vypočíta výsledok a počká toľko sekúnd, aká je hodnota výsledku, predtým, ako ho pošle naspäť používateľovi, aby sa mu zobrazil na webovej stránke. Systém je navrhnutý tak, že jeden server naraz spracováva požiadavky aj viacerých používateľov. Server a webová kalkulačka, ktorá mu príklady odosiela, sú dve samostatné Java EE 7 webové aplikácie, ktoré spolu komunikujú cez JMS. JMS je vôbec určené práve na komunikáciu viacerých aplikácii v prostredí Java EE - tejto komunikácie sa ale, prípadne, môžu zúčastňovať aj klasické Java SE aplikácie, keďže na použitie klientskej časti JMS API stačí priložiť vhodné JAR súbory. Keďže v našom príklade chceme, aby sa výsledok zobrazil používateľovi okamžite, keď je k dispozícii, musíme použiť nejakú dynamickú technológiu - Ajax, alebo Websocket, vybrali sme si Websocket, vychádzajúc z predchádzajúceho prikladu.

Na vytvorenie front-endu použijeme xhtml stránky s JSF 2.2 komponentami podporenými niekoľkými beanmi na strane servera. Celková architektúra aj s interakciami je znázornená na nasledujúcom obrázku:



Výpočet realizuje webová aplikácia JMSComputeServer, ktorá obsahuje aj používateľské rozhranie pre "admina" na zisťovanie stavu serverovskej aplikácie. Táto aplikácia sa zaregistruje na odoberanie správ doručených do JMS schránky s JNDI menom "jms/topic". Dosiahne to pomocou anotácie @MessageDriven - čiže trieda Calculator je tzv. MessageDriven Bean, čo znamená, že pri doručení JMS správy do schránky sa automaticky volá jej metóda onMessage(). Takýto message driven bean môže Glassfish podľa potreby vytvoriť ku každej správe nový, ale dobrá správa je, že v ňom funguje dependency injection (hoci iba na aplikačné beany - údajne aj na requestové, ale to je aj tak takmer zbytočné, lebo po dobu trvania requestu aj tak väčšinou žiadna správa nestihne do schránky prísť. treba dať ale pozor na to, že session beany do message driven beanov byť injektnuté nemôžu).

Serverovský message driven bean teda po doručení správy pošle požiadavku ďalej na applikačný bean ComputeServer, ktorý požiadavku spracuje v lokálnych premenných svojej metódy compute() - čiže takto môže bežať naraz aj viacero volaní tejto metódy z rôznych Calculator-ov, ak server práve spracováva viacero požiadaviek zaslaných rôznymi klientami. Po dokončení výpočtu serverovský aplikačný bean pošle cez správu do rovnakého JMS topicu výsledok späť.

Aby sme odlíšili správy posielané smerom na server a smerom zo servera, použijeme nami zadefinovanú property JMS správy, ktorú si nazveme Direction a bude mať buď hodnotu "ToServer" alebo "ToClient". Tieto "properties" sa pridajú do hlavičky JMS správy, takže náš komunikačný protokol nemusíme zaťažovať posieľaním týchto údajov priamo v tele správy. A takto oba message driven beany (serverovský aj klientský) môžu využiť filter na správy priamo v anotáciach, ktoré ich definujú a budú im chodiť len správy, ktoré idú požadovaným smerom, hoci využívajú ten istý JMS Topic.

O niečo zložitejšia je situácia vo webovej aplikácii jmsdemo, ktorá používateľom umožňuje zasieľať (každému naraz len jednu) výpočtové úlohy na server. Odoslanie požiadavky je jednoduché: sessionovský JSF managed bean typu @Named jmsdemo má priamo namapované polia formulára na svoje lokálne premenné, takže pri http requeste vyvolanom stlačením tlačidla compute na webovej stránke tieto údaje pre jednoduchosť naformátuje do jedného Stringu (dal by sa pochopiteľne použiť aj objekt nami zadefinovanej triedy - avšak zdieľať serializovateľné triedy medzi viacerými aplikáciami je zbytočná nadpráca), zabalí do JMS správy a pošle ju do JMS Topicu jms/topic (tento JMS Topic ako aj zodpovedajúcu Topic Factory je potrebné najskôr vytvoriť v administrátorskej konzole servera Glassfish).

Na získavanie výsledkov zo servera používa - podobne ako server svoj vlastný message driven bean ResultReader. Ten však sám od seba vôbec netuší, ktorému klientovi má odpoveď poslať, keďže naraz môže byť spracovávaných viacero výpočtov. Preto urobíme dve veci:
  1. využijeme mechanizmus JMS správ, kde je možné nastaviť v hlavičke JMS správy identifikátor správy, na ktorú daná správa odpovedá (každá JMS správa má svoj unikátny identifikátor, o čo sa nám postará Glassfish, čiže odpovedajúca správa bude niesť jednak svoj identifikátor a jednak identifikátor správy na ktorú odpovedá). Konkrétne ide o property v hlavičke JMS správy s názvom JMSCorrelationID.
  2. zavedieme mapu so zoznamom identifikátorov odoslaných správ a referencií na zodpovedajúce otvorené spojenia s klientami.

Po doručení správy zo servera z nej vytiahneme correlation ID a v mape pohľadáme referenciu na websocket, ktorý bol vytvorený v tej session, v ktorej táto správa odišla. Ten websocket požiadame, aby do webbroswera, ktorý ho vytvoril pri načítaní webovej stránky (čo sa udialo až po odoslaní požiadavky na výpočet), poslal odpoveď. Tam ju odchytí Javascriptový websocketový onMessage handler, ktorý bol zaregistrovaný po vytvorení websocketu. Tento handler následne dynamicky zobrazí prijatú hodnotu vo výstupnom texte result. Priebežne sa na webovej stránke aktualizuje pole status, aby bol používateľ priebežne informovaný o stave výpoctu. Nový websocket teda vzniká vždy v okamihu, keď sa vráti http response na odoslanú požiadavku na výpočet a teda nastane javascriptová udalosť window.onload.

Tento príklad nám zároveň slúži na pochopenie princípu dependency injection, s ktorým sa v Java EE aplikáciach stretnávame veľmi často. Ide teda o automatické aktualizovanie referencie na iný java bean, pričom jeho doba platnosti sa môže odlišovať, z čoho jasne vyplýva, že bez tohoto mechanizmu, ktorý Java EE zabezpečuje automaticky, by nám obyčajná referencia na daný objekt mohla veľmi ľahko prestať fungovať.

Na záver treba dodať, že v tomto príklade sme z technológie JMS využili len jednu časť: komunikáciu typu Topic/subscribe - konkrétne non-durable (čiže ak subscriber nie je spustený, správy sa stratia), avšak so zabezpečením proti duplicite a strácaniu správ. Okrem toho existujú mechanizmy na doručovanie správ v režime peer-to-peer cez fronty, durable schránky a správy z fronty je možné čítať blokujúcimi alebo neblokujúcimi volaniami read(). O tom všetkom a ešte ďaleko viac sa možno dočítať v oficiálnom Java EE 7 tutoriáli, kapitolách 45 a 46.

Program sa dá vyskúšať tu: http://kempelen.ii.fmph.uniba.sk:8080/jmsdemo/. Môžete si ho otvoriť naraz z rôznych prehliadačov alebo rôznych sessions prehliadača a uvidíte, že výsledok sa doručí len na správne miesto. Počet vyrátaných príkladov server zobrazuje na adrese: http://kempelen.ii.fmph.uniba.sk:8080/calcserv/

Pozor - aktuálna verzia Glassfish má pokazenú admin konzolu a nedajú sa ručne vytvárať JMS resources, treba použiť program asadmin - pozri Example 17.5 v dokumentácii: Administering JMS Connection Factories and Destinations.