%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% :Message Driven Bean %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Message Driven Bean} Posledním typem beanu je \emph{message driven bean}. Má za úkol přijímat zprávy a~reagovat na ně. To se hodí, pokud potřebujeme reagovat na nějaké klíčové události jako je například uskutečnění objednávky. Message driven beany najdou také uplatnění pokud potřebujeme sledovat průběh aplikace kvůli vytíženosti nebo jej archivovat kvůli auditu. V~dalším textu si ukážeme práci s~message driven beany implementovanými pomocí JMS (Java Message Service). Alternativní implementace jsou zmíněny v~\cite{Enterprise JavaBeans 3.0}, nejsou však příliš rozšířené. \subsubsection{Implementace beanu} Implementace message driven beanu je jednoduchá. Stačí vytvořit třídu která bude implementovat rozhraní \texttt{MessageListener} z~balíčku \texttt{javax.jms}, spolu s~metodou \texttt{onMessage}. Tato metoda bude zavolána vždy když beanu přijde nějaká zpráva. Co bude metoda dělat je čistě na nás. Zároveň musíme třídu doplnit anotací \texttt{@MessageDriven}, aby EJB kontejner mohl jednoznačně poznat, že se jedná právě o~message driven bean. Příklad ukazuje kostru třídy \texttt{LoggerBean}, která bude sloužit pro logování výběrů a~vkladů v~aplikaci Piggy Bank. \begin{Verbatim} @MessageDriven(mappedName = "jms/LoggerBean") public class LoggerBean implements MessageListener \{ \} \end{Verbatim} \subsubsection{Fronta a~téma} JMS nabízí dva různé modely pro posílání a~vyzvedávání zpráv. Jsou jimi fronta (queue) a~téma (topic). Fronta je model, ve kterém se příchozí zprávy řadí do fronty a~případný zájemce je odsud vytahuje. Pouze jeden zájemce si může zprávu přečíst. Zpráva je po vytažení z~fronty a~přečtení zahozena. Naproti tomu model tématu ukládá zprávy tak, že si je může přečíst i~více zájemců. Navíc jsou zprávy v~tématu archivovány. Tento model je inspirovaný internetovými diskusními fóry. Pro náš logovací bean zvolíme model fronty, jelikož po přijetí zprávu ihned zalogujeme a~nadále ji nebudeme potřebovat. Zároveň nám stačí pouze jeden bean pro tuto činnost. Příklad ukazuje, jak doplnit anotaci \texttt{@MessageDriven} o~konfigurační údaje. \begin{Verbatim} @MessageDriven(mappedName = "jms/LoggingMessage", \codeHighlight{activationConfig = \{} \codeHighlight{@ActivationConfigProperty(propertyName = "acknowledgeMode",} \codeHighlight{propertyValue = "Auto-acknowledge"),} \codeHighlight{@ActivationConfigProperty(propertyName = "destinationType",} \codeHighlight{propertyValue = "javax.jms.Queue")} \codeHighlight{\})} public class LoggerBean implements MessageListener \{ \} \end{Verbatim} Nejdůležitějším údajem je zde parametr \texttt{mappedName} v~anotaci \texttt{@MessageDriven}, který uvádí název fronty, ze které budeme vyzvedávat zprávy. Zde jsme použili jméno \texttt{jms/LoggingMessage}. Prefix \texttt{jms} je nepovinný, jedná se ale o~konvenci. \subsubsection{Odesílatel zpráv} Ještě než implementujeme tělo metody \texttt{onMessage}, udělejme malou odbočku, abychom si vysvětlili odesílání zprá a~jejich typy. Odesílatelem je většinou nějaký session bean z~aplikace, který chce oznámit, že něco udělal. Náš \texttt{BankDeskBean} naprogramovaný dříve vylepšeme o~funkci odesílat zprávy o~vkladech a~výběrech z~účtu. K~tomu budeme potřebovat rozšířit třídu o~dva nové atributy. Atribut typu \texttt{ConnectionFactory} a~atribut typu \texttt{Queue} (oba typy jsou z~balíčku \texttt{javax.jms}). Tyto atributy budeme také muset doplnit anotací \texttt{@Resource} (z~balíčku \texttt{javax.annotation}), která EJB kontejneru řekne jaké hodnoty jim za běhu nastavit. Pro zjednušení uveďme pouze část třídy \texttt{BankDeskBean}. \begin{Verbatim} @Stateful public class BankDeskBean implements BankDeskLocal \{ @Resource(mappedName="ConnectionFactory") private ConnectionFactory connectionFactory; @Resource(mappedName=""jms/LoggingMessage") private Queue queue; public void deposit(Account account, double amount) \{ if (amount <= 0) \{ throw new DepositException("Cannot deposit less or equal zero."); \} account.setBalance(account.getBalance() + amount); manager.persist(client); \codeHighlight{try \{} \codeHighlight{ Connection connection = connectionFactory.createConnection();} \codeHighlight{ Session session = connection.createSession(false, 0);} \codeHighlight{ MessageProducer producer = session.createProducer(queue);} \codeHighlight{\}} \codeHighlight{catch (JMSException ex) \{} \codeHighlight{ ex.printStackTrace();} \codeHighlight{\}} \} \} \end{Verbatim} V~atributu \texttt{connectionFactory} bude uložena instance tovární třídy vyrábějící objekty typu \texttt{Connection} (návrhový vzor Tovární třída je popsán v~\cite{Navrhove vzory}). U~tohoto atributu jsme v~anotaci specifikovali jméno jako \texttt{ConnectionFactory}. Jedná se o~malý trik, jelikož na serveru JBoss je továrna s~tímto jménem již vytvořena a~nemusíme proto nic více zařizovat. Pokud byste ale chtěli používat speciálně vytvořenou továrnu, bylo by nutné ji nejpre vytvořit na samotném serveru přes administrátorské rozhraní. Atribut \texttt{queue} bude obsahovat odkaz na samotnou frontu, do které budeme zprávy zasílat. Jeho anotace \texttt{@Resource} má v~parametru \texttt{mappedName} uvedený název této fronty. Ten musí být shodný s~názvem uvedeným v~message driven beanu. Nastavení hodnot obou atributů zařizuje EJB kontejner. \subsubsection{Typy zpráv} EJB 3.0 umožňuje zasílat tři základní typy zpráv. Textové zprávy (třída \texttt{TextMessage}) obsahují informativní text pro příjemce. Mapovací zprávy (třída \texttt{MapMessage}) obsahuje tabulku s~daty (typ \texttt{Map} v~ze standardní knihovny Javy). Ta se hodí pro posílání pojmenovaných parametrů. Posledním standardním typem je objektová zpráva (třída \texttt{ObjectMessage}) určená pro posílání celých objektů --- entity beanů nebo jiných. Typ zprávy závisí na dohodě mezi odesílatelem a~příjemcem. Ukažme si nyní, jak vytvořit zprávu typu \texttt{MapMessage}, do které uložíme jméno a~příjmení klienta a~také částku, kterou si vybral. Zprávu také hned odešleme. \begin{Verbatim} @Stateful public class BankDeskBean implements BankDeskLocal \{ @Resource(mappedName="ConnectionFactory") private ConnectionFactory connectionFactory; @Resource(mappedName=""jms/LoggingMessage") private Queue queue; public void deposit(Account account, double amount) \{ if (amount <= 0) \{ throw new DepositException("Cannot deposit less or equal zero."); \} account.setBalance(account.getBalance() + amount); manager.persist(client); try \{ Connection connection = connectionFactory.createConnection(); Session session = connection.createSession(false, 0); MessageProducer producer = session.createProducer(queue); \codeHighlight{MapMessage message = session.createMapMessage();} \codeHighlight{message.setString("firstName", client.getFirstName());} \codeHighlight{message.setString("surname", client.getSurname());} \codeHighlight{message.setDouble("amount", amount);} \codeHighlight{producer.send(message);} \codeHighlight{producer.close();} \codeHighlight{connection.close();} \} catch (JMSException ex) \{ ex.printStackTrace(); \} \} \} \end{Verbatim} Pozor na metodu \texttt{setObject} u~instance třídy \texttt{MapMessage}. Podle názvu a~parametru typu \texttt{Object} by mohla vyzívat k~uložení libovolného objektu. ato metodaovšem funguje pouze pro obalovací třídy primitivních datových typů (např. \texttt{Integer} pro \texttt{int}). \texttt{MapMessage} dovoluje posílat pouze primitivní datové typy a~řetězce. \subsubsection{Metoda onMessage} Teď, když víme jak zprávy odesílat a~jaké jsou jejich možné typy, se vraťme k~message driven beanu \texttt{LoggerBean}. Po té, co je do fronty uložena zpráva, zavolá EJB kontejner u~message driven beanu metodu \texttt{onMessage} a~jako parametr jí předá zprávu z~fronty. Metoda \texttt{onMessage} bere jako parametr společného předka, typ \texttt{Message}, v~těle metody proto musíme zprávu přetypovat. Jiné řešení bohužel v~současné verzi EJB neexistuje. Příklad ukazuje, jak ze zprávy získat údaje o~klientovi a~o~částce, kterou vložil. Třída \texttt{TransactionLogger}, kterou příklad používá, je naše vlastní třída pro logování výběrů a~vkladů. \begin{Verbatim} @MessageDriven(mappedName = "jms/LoggingMessage", activationConfig = \{ @ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"), @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue") \}) public class LoggerBean implements MessageListener \{ \codeHighlight{public void onMessage() \{} \codeHighlight{ try \{} \codeHighlight{ MapMessage map = (MapMessage) message;} \codeHighlight{ String firstName = map.getString("firstName");} \codeHighlight{ String surname = map.getString("surname");} \codeHighlight{ double amount = map.getDouble("amount");} \codeHighlight{ TransactionLogger logger = new TransactionLogger();} \codeHighlight{ logger.log(firstName, surname, amount);} \codeHighlight{ \}} \codeHighlight{ catch(ClassCastException ex) \{} \codeHighlight{ ex.printStackTrace();} \codeHighlight{ \}} \codeHighlight{ catch(JMSException ex) \{} \codeHighlight{ ex.printStackTrace();} \codeHighlight{ \}} \codeHighlight{\}} \} \end{Verbatim}