%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% %% :Session Bean %% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \subsection{Session Bean} Session bean je druhým ze tří typů objektů, které se podílejí na chodu EJB. Session beany jsou určeny pro manipulaci s~enity beany. Každý session bean představuje objekt, který umí něco zařídit. V~řešeném příkladu Piggy Bank bude určitě session bean umožňující vložit a~vybrat peníze z~účtu. Provede to tak, že získá instanci účtu (entity bean) a~nastaví hodnotu zbytku na novou hodnotu. V~reálné aplikaci by se session beanů vyskytovalo více. \subsubsection{Bezestavový session bean} Session beanů existují dva typy --- bezestavový session bean a~stavový session bean. Stavem se zde myslí stejný princip jako u~protokolu HTTP, tedy uchovávání kontextu komunikace mezi jednotlivými požadavky. Bezestavový session bean je jednodušší, zmíníme ho tedy jako první. Může jím být jakákoliv třída v~jazyce Java doplněná o~anotaci \texttt{@Stateles}, která nepracuje s~atributy ve smyslu ukládání a~čtení. Vzpomeňte si na princip požadavku a~odpovědi z~kapitoly \ref{HTTP} Session bean pracuje přesně tímto způsobem, tedy přijme požadavek (v~tomto případě volání metody), vyřídí ho a~tím jeho úloha končí. V~aplikaci Piggy Bank budeme potřebovat bean reprezentující bankovního poradce. Ten vám umožní otevřít si u~banky účet. Jelikož to bude pouze jednorázová akce, hodí se pro implementaci bezestavový session bean. Příklad ukazuje kostru beanu. \begin{Verbatim} @Stateles public class BankConsultantBean \{ public void register(Client client) \{ \} \} \end{Verbatim} \subsubsection{Lokální a~vzdálené rozhraní} Každý session bean musí implementovat lokální nebo vzdálené rozhraní. Jedná se o~obyčejné rozhraní v~jazyce Java, doplněné buď anotací \texttt{@Local} (pro deklaraci lokálního rozhraní) nebo anotací \texttt{@Remote} (pro deklaraci vzdáleného rozhraní). V~rozhraní jsou deklarovány (všechny) metody poskytované session beanem. Jelikož každý session bean \emph{musí} mít své rozhraní, vzniká problém s~vymýšlením názvů. Standardně se tedy lokální rozhraní pro session bean s~názvem \texttt{A} pojmenovávají \texttt{ALocal}, obdobně vzdálené rozhraní jako \texttt{ARemote}. Příklad ukazuje vzdálené rozhraní pro session bean \texttt{BankConsultantBean}. \begin{Verbatim} @Remote public class BankConsultantRemote \{ public void register(Client client); \} \end{Verbatim} \fcolorbox{boxout}{boxin}{ \parbox{0.98\linewidth}{ Jeden session bean může implementovat jak lokální, tak vzdálené rozhraní najednou. Je však na pováženou, jestli je to doopravdy potřeba. }} Lokální rozhraní je obdobné, pouze stačí vyměnit anotaci \texttt{@Remote} za anotaci \texttt{@Local} (a~změnit název rozhraní, pokud používáme konvenci zmíněnou výše). Session beany s~lokálním rozhraním jsou určené pro volání v~rámci jednoho serveru. Objektové parametry i~návratové hodnoty se předávají pomocí reference. To se hodí v~případě, že jeden session bean volá jiný. Naproti tomu session beany se vzdáleným rozhraním lze volat ze vzdáleného počítače. Všechny parametry a~návratové hodnoty se předávají kopií. \fcolorbox{boxout}{boxin}{ \parbox{0.98\linewidth}{ Na rozdíl od běžné desktopové aplikace není rozdíl mezi předávání objektu odkazem a~kopií tak citelný. V~podstatě se jedná pouze o~problém rychlosti, kdy časté kopírování větších objektů může program zpomalit. U~EJB aplikací, které jsou určeny k~využívání velkým počtem klientů naráz by to ale mohl být znatelný problém. }} \subsubsection{Entity Manager} \texttt{EntityManager} je třída, jejíž instance je schopná vyhledávat entity beany z~celé aplikace. Její použití je jednoduché. Stačí uvnitř některého session beanu deklarovat atribut typu \texttt{EntityManager} a~poté ho doplnit anotací \texttt{@PersistenceContext}. EJB kontejner už sám atribut nastaví na správnou hodnotu při nasazení aplikace. V~těle jakékoliv metody session beanu pak můžeme na instanci třídy \texttt{EntityManager} zavolat metodu \texttt{persist}. Ta bere jako parametr libovolný entity bean, který uloží do databáze. Příklad ukazuje tělo metody \texttt{register} session beanu \texttt{BankConsultantBean}. \begin{Verbatim} @Stateles public class BankConsultantBean \{ \codeHighlight{@PersistenceContext} \codeHighlight{private EntityManager manager;} public void register(Client client) \{ \codeHighlight{manager.persist(client);} \} \} \end{Verbatim} \subsubsection{Stavový session bean} Stavový session bean se od bezestavového liší tím, že si pamatuje kontext rozhovoru mezi serverem a~klientem. Klient tedy dostane přiřazen konkrétní instanci session beanu a~na té volá její metody. To má význam hlavně pokud volání nějaké metody nastaví hodnotu atributu session beanu, která se pak vyzvedne v~těle jiné metody. Po implementační stránce za nás stavovost session beanu zajišťuje EJB kontejner. Jediné, co musí udělat programátor je uvést anotaci \texttt{@Stateful} na třídě session beanu. Anotace \texttt{@Stateless} a~\texttt{@Stateful} se pochopitelně navzájem vylučují. V~aplikaci Piggy Bank zavedeme nový session bean reprezentující bankovní přepážku. Pro implementaci použijeme stavový session bean, aby mohl klient provádět více akcí (výběry, vklady, ...) po sobě bez nutnosti se znovu přihlašovat. Ve stavu tedy bude uložen entity bean reprezentující klienta. Příklad ukazuje implementaci tohoto session beanu. Rozhraní je jednoduché, proto zde nebude uvedeno. Pro zjednodušení neuvažujeme, že by klient chtěl vybírat z~cizího účtu. Není tedy uvedena kontrola, jestli účet skutečně patří tomuto klientovi. \begin{Verbatim} @Stateful public class BankDeskBean implements BankDeskRemote \{ @PersistenceContext private EntityManager manager; private Client client; public void setClient(Client client) \{ this.client = client; \} public double getBalance(Account account) \{ return account.getBalance(); \} 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); \} public void withdraw(Account account, double amount) \{ if (amount < 0) \{ throw new DepositException("Cannot withdraw less than zero."); \} if (amount > account.getBalance()) \{ throw new DepositException( "Cannot withdraw more than current balance."); \} account.setBalance(account.getBalance() - amount); manager.persist(client); \} \} \end{Verbatim} Všimněte si volání metody \texttt{persist} na konci metod pro výběr a~vklad. To je nezbytné pro okamžité zapsání nového stavu účtu do databáze. Náš návrh etity beanů byl klient, který má namapováno několik účtů. Směr relace šel od klienta k~účtu, tedy třída účtu neví, který klient ji vlastní. Kdybychom uložili pouze entity bean účtu, nebyl by v~databázi přiřazen k~žádnému klientovi. Z~tohoto důvodu ukládáme entity bean klienta. \subsubsection{Přístup klienta k session beanu} Oba session beany máme navržené, zbývá vytvořit klientský kód který dokáže jejich metody zavolat. Nejjednodušším klientem je servlet nebo JSP stránka. Nejsložitější částí klientského kódu je získat od EJB kontejneru instanci session beanu, na které budeme volat metody. Potřebujeme instanci třídy \texttt{InitialContext} z~balíčku \texttt{javax.naming}, kterou získáme zavoláním konstruktoru. Pokud je klientský kód nasazený na serveru (náš bude), není potřeba další konfigurace. Jakmile máme objekt kontextu, zavoláme na něm metodu \texttt{lookup}. Jako parametr jí předáme identifikátor session beanu. Na serveru JBoss je ve tvaru: \begin{Verbatim} [název EAR souboru]/[název session beanu]/local \end{Verbatim} Název EAR souboru uvádíme bez přípony, název třídy session beanu zase bez kvantifikace balíčku. Poslední položka je konstanta \texttt{local}. Po zavolání metody \texttt{lookup} prohledá EJB kontejner EAR soubor a~pokusí se session bean najít. Podrobnější popis pravidel vyhledávání najdete v~\cite{Enterprise JavaBeans 3.0}, pokud ale navrhujeme aplikaci racionálně (např. nepoužijeme dva stejné názvy tříd pro třídy v~různých balíčcích ap.) není potřeba detaily znát. Jakmile EJB kontejner třídu nalezne, vyrobí její instanci a~vrátí na ni odkaz jako na typ \texttt{Object}. Technologie JNDI, kterou EJB kontejner pro vyhledávání používá byla vytvořena ještě před uvedením šablon do jazyka Java, v~současné době tedy neexistuje jiný způsob než přetypování získaného odkazu. Příklad ukazuje, jak získat instanci session beanu \texttt{BankConsultantBean}. \begin{Verbatim} InitialContext jndi = new InitialContext(); Object reference = jndi.lookup("PiggyEJB/BankConsultantBean/local"); BankConsultantLocal consultant = (BankConsultantLocal) reference; \end{Verbatim} \fcolorbox{boxout}{boxin}{ \parbox{0.98\linewidth}{ Publikace \cite{Enterprise JavaBeans 3.0}, ale i~jiné online zdroje o~serveru JBoss uvádí identifikátor session beanu v~podobě \texttt{ejb/[název rozhraní]}, tedy například \texttt{ejb/BankConsultantLocal}. Na serveru JBoss ve verzi 6.0 však toto už neplatí a~je nezbytné použít identifikátor z~příkladu výše. V~opačném případě se aplikaci ani nepodaří nasadit. }} Se získanou instancí session beanu pracujeme jako s~jakoukoliv jiným objektem v~jazyku Java. Pouze je třeba mít na paměti, že povaha EJB obecně neumožňuje předávat tuto instanci jako parametr jinam nebo ji skladovat po delší dobu v~nějakém atributu. Příklad rozšíříme o~zaregistrování nového uživatele do aplikace. Pro jednoduchost jsou jméno a~příjmení uživatele uvedeny jako konstantní řetězce. \begin{Verbatim} InitialContext jndi = new InitialContext(); Object reference = jndi.lookup("PiggyEJB/BankConsultantBean/local"); BankConsultantLocal consultant = (BankConsultantLocal) reference; \codeHighlight{Client client = new Client();} \codeHighlight{client.setFirstName("Radovan");} \codeHighlight{client.setSurname("Netuka");} \codeHighlight{consultant.register(client);} \end{Verbatim} \subsubsection{Alternativní získání session beanu} Pokud se vám nelíbí možnost získání instance session beanu pomocí metody \texttt{lookup}, existuje ještě další možnost. Tou je deklarování session beanu jako atributu klientské třídy (typ rozhraní beanu) a~následné doplnění tohoto atributu anotací \texttt{@EJB}. Tento přístup se hodí hlavně, pokud je klientským kódem jiný session bean anebo servlet. Příklad ukazuje, jak získat instanci session beanu pomocí anotace \texttt{@EJB}. Reference \begin{Verbatim} public class RegisterClientServlet extends HttpServlet \{ \codeHighlight{@EJB} \codeHighlight{private BankConsultantLocal consultant;} @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException \{ Client client = new Client(); client.setFirstName("Radovan"); client.setSurname("Netuka"); consultant.register(client); \} \} \end{Verbatim} \fcolorbox{boxout}{boxin}{ \parbox{0.98\linewidth}{ V~knize \cite{Enterprise JavaBeans 3.0} uvádějí, že anotaci \texttt{@EJB} lze použít i~klientském kódu JSP stránky. To ale bohužel nefunguje a~je nutné použít metodu \texttt{lookup}. }}