DPKOM_8 Session beany
1
Obsah přednášky • • • • • • •
Úvod Bezstavové session beany SessionContext Životní cyklus bezstavových session beanů Stavové session beany Životní cyklus stavových session beanů Stavové session beany a rozšířený perzistentní kontext • Vnořené stavové session beany 2
Úvod • Session beany zaplňují mezeru zanechanou entitními beany. • Session beany jsou užitečné pro popis interakcí mezi ostatními beany (taskflow) a pro implementaci konkrétních úloh. • Mohou být využity ke čtení, aktualizaci a vkládání dat v business procesu. • Např. session bean může být použit k udržování (řízení) seznamu všech volných kabin.
3
Úvod • Použítí session beanů & použití entitní beanů. – Základním pravidlem je, že entitní beany by měly poskytovat bezpečné a konzistentní rozhraní pro množinu sdílených dat, která definuje koncept. – Session beany obsahují business logiku a entitní beany model perzistentních dat. – Session beany často řídí interakce mezi entitními beany.
4
Bezstavové session beany • Výhodou těchto session beanů je, že se mohou prohazovat (vyměňovat) mezi EJB objekty, protože nejsou odkázány na údržbu pouze jednoho klienta a neudržují tzv. konverzační stav. • Jakmile dokončí obsluhu vyvolání metody, mohou pokračovat v obsluze dalšího EJB objektu (klienta). • Protože neudržují konverzační stav, nevyžadují pasivaci a aktivaci. • Krátce řečeno bezstavové session beany jsou lehké a rychlé. 5
Bezstavové session beany • Bezstavové session beany si nemusejí pamatovat nic z předchozího vyvolání metody. • Jedinou výjimkou je informace získaná ze SessionContext a z JNDI ENC nebo odkazů na prostředí, které jsou přímo injektovány do beanu. • Bezstavové session beany jsou verzí tradičních EJB aplikací transakčního zpracování, které jsou vykonávány prostřednictvím volání procedur. • Procedura se vykonává od začátku do konce a pak teprve vrací výsledek. Po té, co procedura skončí je všechno zapomenuto. 6
Bezstavové session beany • Tato omezení neznamenají, že session bean nemůže mít instanční proměnné, které udržují (zaznamenávají) nějaký druh interního stavu. • Instanční proměnná může např. zaznamenávat počet vyvolání daného beanu, nebo ukládat data pro ladění. • Dalším příkladem mohou být odkazy na živé zdroje např. URL, verifikace kreditní karty prostřednictvím jiného EJB.
7
Bezstavové session beany • Vše potřebné v této souvislosti může být získáno prostřednictvím JNDI ENC nebo injektovaných polí. • Avšak je důležité si zapamatovat, že tento stav klient nikdy neuvidí. • Klient nikdy nemůže předpokládat, že jeho požadavek bude obsluhovat stejný bean. • Stejně tak klient neví, kdy se provádí výstup pro ladění.
8
Příklad – proces placení ProcessPayment
• Bean TravelAgent má metodu bookPassage(), která používá bean ProcessPayment. • ProcessPayment je bezestavový session bean, který je používán beanem TravelAgent k tomu, aby zákazník zaplatil poplatek za plavbu. • Session bean ProcesPayment bude také využíván k jakékoli platbě na lodi např. za nákupy dárků, nebo v butiku a další podobné služby. • Proto logika beanu bude muset vyhovovat všem těmto požadavkům. 9
Příklad – proces placení Process Payment
• Platby jsou zaznamenávány ve spaciální tabulce databáze s názvem PAYMENT. • Data této tabulky jsou dávkově zpracovávaná pro účely účetnictví a nejsou normálně použita mimo tuto oblast. • Pro placení mohou být použity kreditní karty, šeky nebo hotovost.
10
CREATE TABLE PAYMENT ( customer_id amount type check_bar_code check_number credit_number credit_exp_date DATE )
Poznámky INTEGER, DECIMAL(8,2), CHAR(10), CHAR(50), INTEGER, CHAR(20),
Tabulka PAYMENT
Business rozhraní : ProcessPayment • Business rozhraní (local/remote) pro uvedený bean bude mít následující metody: byCredit(), byCheck(), byCash(). • Business rozhraní může být buď vzdálené nebo lokální, ale ne obě současně. – Vzdálená business rozhraní vyvolávají klienti na síti (z jiného JVM).
• Když je klientem vyvolána metoda session beanu vzdáleného rozhraní, hodnoty parametrů a návratová hodnota jsou kopírovány. – Tato sémantika volání je známá pod označením call-by-value (volání hodnotou). 12
Business rozhraní : ProcessPayment • Lokální rozhraní jsou přístupná pouze v rámci stejného JVM jako session bean. – Tehdy parametry nejsou kopírovány, ale využívá se sémantiky call-by-reference.
• Session bean ProcessPayment bude potřebovat vytvořit obě rozhraní. • Obě rozhraní budou publikována stejným API. – Pro zpřehlednění návrhu budou uvedené rozhraní „podrozhraními“ bázového rozhraní s názvem com.titan.processpayment.ProcessPayment: 13
package com.titan. com.titan.processpayment .titan.processpayment; processpayment; import com.titan. com.titan.domain .titan.domain.*; domain.*; public interface ProcessPayment { public boolean byCheck( byCheck(Customer customer, customer, CheckDO check, check, double amount) amount) throws PaymentException; PaymentException; public boolean byCash( byCash(Customer customer, customer, double amount) amount) throws PaymentException; PaymentException; public boolean byCredit byCredit( (Customer customer, customer, CreditCardDO card, double amount) amount) throws PaymentException; PaymentException; }
Poznámky
Business rozhraní : ProcessPayment • Specifikace EJB dovoluje deklarovat vzdálené a lokální rozhraní za předpokladu deklarování stejných metod, což je splněno. • Všechny metody vrací booleovskou hodnotu informující, zda byla platba úspěšná. • Výjimka je vyhozena např. v případě neplatné karty z hlediska exspirace. • Nic z rozhraní ProcessPayment není specifikováno v rezervačním systému. • Je to z toho důvodu, že ProcessPayment se může využívat i v jiných typech plateb (mimo rezervace). 15
package com.titan. com.titan.processpayment .titan.processpayment; processpayment; import javax. javax.ejb. ejb.Remote; Remote; @Remote public interface ProcessPaymentRemote extends ProcessPayment { } package com.titan. com.titan.processpayment .titan.processpayment; processpayment; import javax. javax.ejb. ejb.Local; Local; @Local public interface ProcessPaymentLocal extends ProcessPayment { }
Poznámky
Entity a parametry • Každá z definovaných metod používá jako parametr entitní bean Customer. • Entitní beany jsou zpracovávány jako standardní javovské objekty, je proto nutná implementace java.io.Serializable nebo Externalizable. • To je důležité, protože bean ProcessPayment zpřístupňuje vnitřní stav entity Customer. • Pokud by každé volání metody get entity Customer šlo přes síť, bylo by to velmi nákladné.
17
Doménové objekty: třídy CrediCardDO a CheckDO • Business rozhraní Bean ProcessPayment využívá obě třídy, CreditDO a CheckDO:
18
/* CreditCardDO. CreditCardDO.java */ package com.titan. com.titan.processpayment .titan.processpayment; processpayment; import java. java.util. util.Date; Date; public class CreditCardDO implements java. java.io. io.Serializable { final static public String MASTER_CARD = "MASTER_CARD"; final static public String VISA = "VISA"; final static public String AMERICAN_EXPRESS = "AMERICAN_EXPRESS"; final static public String DISCOVER = "DISCOVER"; final static public String DINERS_CARD = "DINERS_CARD"; public String number; number; public Date expiration; expiration; public String type; public CreditCardDO( CreditCardDO(String number, number, Date expiration, expiration, String type) { this. this.number = number; number; this. this.expiration = expiration; expiration; this.type this.type = type; } }
Poznámky
/* CheckDO. CheckDO.java */ package com.titan. com.titan.processpayment .titan.processpayment; processpayment; public class CheckDO implements java. java.io. io.Serializable { public String checkBarCode; checkBarCode; public int checkNumber; checkNumber; public CheckDO (String barCode, barCode, int number) number) { this. this.checkBarCode = barCode; barCode; this. this.checkNumber = number; number; } }
Poznámky Objekty od uvedených tříd jsou doménové objekty. Jsou to serializovatelné javoské třídy ne enterprise beany. Poskytují vhodný mechanismus pro transport dat.
Výjimka aplikace PaymentException
• Libovolné vzdálené nebo lokální rozhraní může vyhodit aplikační výjimku. • Aplikační výjimka by měla popisovat problém business logiky. • Je důležité porozumět jaké výjimky by se měly použít a kdy je použít. • Např. výjimky javax.naming.NamingException a javax.sql.SQLException nemají nic co dočinění s business procesy.
21
Výjimka aplikace PaymentException
• Výjimka PayableException popisuje konkrétní business problém, který je pravděpodobně opravitelný (řešitelný). • Kontejner EJB ošetří každou výjimku, která není podvýjimkou RuntimeException jako výjimku aplikace. • Deklarace PaymentException:
22
package com.titan. com.titan.processpayment .titan.processpayment; processpayment; public class PaymentException extends java. java.lang. lang.Exception { public PaymentException() PaymentException() { super(); } public PaymentException( PaymentException(String msg) msg) { super(msg super(msg); msg); } }
Poznámky
Výjimka aplikace PaymentException
• Aplikační výjimka je šířena k volajícímu klientovi. • Jakákoli instanční proměnná zahrnutá do výjimky by měla být serializovatelná. Neaplikační výjimky jsou vždy sbaleny v EJBException. • To znamená, že jakákoli výjimka, kterou vyhodíte a která je typu RuntimeException nebo její potomek, bude zachycena kontejnerem EJB a zabalena do EJBException.
24
Výjimka aplikace PaymentException
• Všechny výjimky vyhozené rozhraním Java Persistence jsou výjimky typu RuntimeException. • Chování výjimky může být deklarováno explicitně s použitím @javax.ejb.ApplicationException a pomocí XML
.
25
package com.titan. com.titan.processpayment .titan.processpayment; processpayment;
Poznámky
import com.titan. com.titan.domain .titan.domain.*; domain.*; import java. java.sql.*; sql.*; import import import import
javax. javax.ejb.*; ejb.*; javax. javax.annotation. annotation.Resource; Resource; javax. javax.sql. sql.DataSource; DataSource; javax. javax.ejb. ejb.EJBException; EJBException;
@Stateless public class ProcessPaymentBean implements ProcessPaymentRemote, ProcessPaymentLocal { final public static String CASH = "CASH"; final public static String CREDIT = "CREDIT"; final public static String CHECK = "CHECK"; @Resource( Resource(mappedName=" mappedName="java ="java:/ java:/titanDB :/titanDB") titanDB") DataSource dataSource; dataSource; @Resource( Resource(name="min") name="min") int minCheckNumber = 100; public boolean byCash( byCash(Customer customer, customer, double amount) amount) throws PaymentException { return process( process(customer. customer.getId(), getId(), amount, amount, CASH, null, null, -1, null, null, null); null); }
Třída ProcessPaymentBean bezstavový session bean
public boolean byCheck( byCheck(Customer customer, customer, CheckDO check, check, double amount) amount) throws PaymentException { if (check. check.checkNumber > minCheckNumber) minCheckNumber) { return process( process(customer. customer.getId(), getId(), amount, amount, CHECK, check. check.checkBarCode, checkBarCode, check. check.checkNumber, checkNumber, null, null, null); null); } else { throw new PaymentException(" PaymentException("Check ("Check number is too low. low. Must be at least "+minCheckNumber "+minCheckNumber); minCheckNumber); } } public boolean byCredit( byCredit(Customer customer, customer, CreditCardDO card, double amount) amount) throws PaymentException { if (card. card.expiration. expiration.before( before(new java. java.util. util.Date())) Date())) { throw new PaymentException(" PaymentException("Expiration ("Expiration date has passed"); passed"); } else { return process( process(customer. customer.getId(), getId(), amount, amount, CREDIT, null, null, -1, card. card.number, number, new java. java.sql. sql.Date( Date(card. card.expiration. expiration.getTime())); getTime())); } }
Poznámky
private boolean process( process(int customerID, customerID, double amount, amount, String type, String checkBarCode, checkBarCode, int checkNumber, checkNumber, String creditNumber, creditNumber, java. java.sql. sql.Date creditExpDate) creditExpDate) throws PaymentException { Connection con = null; null; PreparedStatement ps = null; null; try { con = dataSource. dataSource.getConnection(); getConnection(); ps = con. con.prepareStatement ("INSERT INTO payment (customer_id, customer_id, amount, amount, type,"+ "check_bar_ check_bar_code _bar_code, code,check_ check_number, number,credit_ credit_number,"+ number,"+ "credit_exp_ credit_exp_date _exp_date) date) VALUES (?,?,?,?,?,?,?)"); ps. ps.setInt(1, setInt(1,customerID (1,customerID); customerID); ps. ps.setDouble(2, setDouble(2,amount (2,amount); amount); ps. ps.setString(3,type); setString(3,type); ps. ps.setString(4, setString(4,checkBarCode (4,checkBarCode); checkBarCode); ps. ps.setInt(5, setInt(5,checkNumber (5,checkNumber); checkNumber); ps. ps.setString(6, setString(6,creditNumber (6,creditNumber); creditNumber); ps. ps.setDate(7, setDate(7,creditExpDate (7,creditExpDate); creditExpDate); int retVal = ps. ps.executeUpdate(); executeUpdate(); if (retVal!=1) retVal!=1) { throw new EJBException(" EJBException("Payment failed"); ("Payment insert failed"); } return true; true; }
Poznámky
catch( catch(SQLException sql) sql) { throw new EJBException( EJBException(sql); sql); } finally { try { if (ps != null) null) ps. ps.close(); close(); if (con!= con!= null) null) con. con.close(); close(); } catch( catch(SQLException se) { se.printStackTrace se.printStackTrace(); printStackTrace(); } } } }
Poznámky
package javax. javax.ejb; ejb; @Target(TYPE) Target(TYPE) @Retention @Retention(RUNTIME) Retention(RUNTIME) public @interface Stateless { String name() default “”; “”; }
Atribut name() identifikuje EJB jméno session beanu. Defóltní jméno pro uvedenou třídu beanu bude ProcessPaymentBean. Třída bean identifikuje svoje vzdálené a lokální rozhraní implementací rozhraní ProcessPaymentRemote a ProcessPaymentLocal. Když je bean rozmisťovaný, kontejner se podívá na rozhraní třídy bean a zkontroluje jestli jsou anotované (opatřené komentářem) @javax. javax.ejb. ejb.Local nebo @javax. javax.ejb. ejb.Remote. Remote Tato introspekce (nahlédnutí dovnitř) určí vzdálené a lokální rozhraní třídy bean. Alternativně třída bean nemusí implementovat žádné rozhraní a anotace @Local a @Remote mohou být použity přímo ve třídě.
Poznámky Třída bean je opatřena anotacemi s využitím anotací @javax.ejb.Stateless:
@Stateless @Local( Local(ProcessPaymentLocal. ProcessPaymentLocal.class) class) @Remote( Remote(ProcessPaymentRemote. ProcessPaymentRemote.class) class) public class ProcessPaymentBean { final final final
public public public
static static static
String String String
CASH = "CASH"; CREDIT = "CREDIT"; CHECK = "CHECK";
Když se uvedená deklarace použije ve třídě bean, anotace @Local a @Remote vezmou pole rozhraní tříd. Toto není doporučeno dokud nemusíte, protože implementace business rozhraní přímo vyžaduje smlouvu (kontrakt) mezi třídou bean a těmito rozhraními.
Poznámky
Třída ProcessPayment Bean • Tři deklarované metody (možnosti) placení volají metodu process(), která provádí přidání platby do databáze. • Tento způsob redukuje chyby programátora. • Metoda process() nevyužívá entitní bean, ale přímo zapisuje data do tabulky PAYMENT s použitím JDBC.
32
Zpřístupnění vlastností prostředí (injection)
• Položky datasource a minCheckNumber jsou příklady session položek, které je inicializované prostředím EJB prostředí. • Každý kontejner EJB má svůj vlastní interní registr, kde ukládá konfigurační hodnoty a odkazy na externí zdroje a služby. • Tento registr se jmenuje Enterprise Naming Context (ENC). • Takové instanční proměnné jsou opatřeny anotací @javax.annotation.Resource. 33
Zpřístupnění vlastností prostředí (injection)
• Ta říká kontejneru, že když je vytvářena instance od třídy bean, atributy opatřeny uvedenou anotací musí být inicializovány hodnotami uvedenými v ENC. • Když je kontejner EJB rozmístěn, ENC je šířen s metadaty v anotaci jako @Resource a s informacemi uloženými v libovolném popisovači rozmístění EJB XML.
34
Zpřístupnění vlastností prostředí (injection)
• Například anotace @Resource která označuje zdrojové datové pole obsahuje atribut mappedName(), který identifikuje externí datový zdroj JDBC, který by měl být mapován do ENC. • Anotace @Resource pro položku minCheckNumber identifikuje jmennou hodnotu, která je použita k externí inicializaci uvedeného pole (položky). • Právě tyto hodnoty mohou být nakonfigurovány s využitím popisovače rozmístění EJB XML: 35
<ejbejb-jar xmlns="http:// xmlns="http://java ="http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee" javaee" xmlns: xmlns:xsi="http://www.w3. xsi="http://www.w3.org ="http://www.w3.org/2001/ org/2001/XMLSchema /2001/XMLSchemaXMLSchema-instance" xsi: xsi:schemaLocation="http:// schemaLocation="http://java ="http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee http://java http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee/ javaee/ejbejbjar_3_0.xsd jar_3_0.xsd" xsd" version="3.0"> version="3.0"> <enterpriseenterprise-beans> beans> <session> <ejbejb-name> name>ProcessPaymentBean ProcessPaymentBean name> <envenv-entry> entry> <envenv-entryentry-name>min name>minmin name> <envenv-entryentry-type>java type>java. java.lang. lang.Integer Integer <envenv-entryentry-value>250 value>250250 value> entry> beans>
Poznámky V uvedené popisovači je jméno min s hodnotou 250, která je využita jako hraniční hodnota.
version="1.0"?> <ejbejb-jar xmlns="http:// xmlns="http://java ="http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee" javaee" xmlns: xmlns:xsi="http://www.w3. xsi="http://www.w3.org ="http://www.w3.org/2001/ org/2001/XMLSchema /2001/XMLSchemaXMLSchema-instance" xsi: xsi:schemaLocation="http:// schemaLocation="http://java ="http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee http://java http://java. java.sun. sun.com/ com/xml/ xml/ns/ ns/javaee/ javaee/ejbejb-jar_3_0.xsd jar_3_0.xsd" xsd" version="3.0"> version="3.0"> <enterpriseenterprise-beans> beans> <session> <ejbejb-name> name>ProcessPaymentBean ProcessPaymentBean name> remote>com.titan. com.titan.processpayment .titan.processpayment. processpayment.ProcessPaymentRemote ProcessPaymentRemote remote> local>com.titan. com.titan.processpayment .titan.processpayment. processpayment.ProcessPaymentLocal ProcessPaymentLocal local> <ejbejb-class> class>com.titan. com.titan.processpayment .titan.processpayment. processpayment.ProcessPaymentBean ProcessPaymentBean class> <session<session-type>Stateless type>Stateless <envenv-entry> entry> <envenv-entryentry-name>min name>minmin name> <envenv-entryentry-type>java type>java. java.lang. lang.Integer Integer <envenv-entryentry-value>10 value>1010 value> target> class> com.titan. com.titan.processpayment .titan.processpayment. processpayment.ProcessPaymentBean class> injection-target name>minCheckNumber minCheckNumber target> entry>
Poznámky XML popisovač rozmístění Tento popisovač je alternativou k anotacím a rozšiřuje metadata, která nemusíte deklarovat v anotacích. Uvedený popisovač rozmístění, který poskytuje kompletní alternaci pro EJB ProcesPayment
ref> name>theDatasource theDatasource name> javax type>javax. javax.sql. sql.DataSource DataSource auth>Container Container auth> <mappedmapped-name> name>java:/ java:/DefaultDS :/DefaultDS DefaultDS name> target> class> com.titan. com.titan.processpayment .titan.processpayment. processpayment.ProcessPaymentBean class> name>dataSource dataSource name> target> ref> beans>
Poznámky XML poskytuje jednu zajímavost, kterou je použití RuntimeException místo EJBException ve třídě beanu. Je to způsobeno tím, že javovský kód nemá žádný odkaz na specifikace EJB.
Session kontext SessionContext
• Rozhraní javax.ejb.SessionContext poskytuje pohled do prostředí kontejneru EJB. • Objekt SessionContext může být použitý jako rozhraní instance beanu ke kontejneru EJB k získání informací o kontextu volání metod a poskytuje rychlý přístup k různým službám EJB. • Odkaz na SessionContext získá session bean prostřednictvím anotace @Resource. @Stateless public class ProcessPaymentBean implements ProcessPayment { @Resource SessionContext ctx; ... } 39
SessionContext dovoluje získat informace jako aktuální uživatel, který vyvolal session bean, nebo vyhledat vstupy uvnitř ENC EJB. Rozhraní javax.ejb.SessionContext: public interface javax. javax.ejb. ejb.SessionContext extends javax. javax.ejb. ejb.EJBContext { EJBLocalObject getEJBLocalObject() getEJBLocalObject() throws IllegalStateException EJBObject getEJBObject() getEJBObject() throws IllegalStateException ; MessageContext getMessageContext() getMessageContext() throws IllegalStateException; IllegalStateException; getBusinessObject( getBusinessObject(Class Class businessInterface) businessInterface) throws IllegalStateException; IllegalStateException; Class getInvokedBusinessInterface(); getInvokedBusinessInterface(); }
Metody getEJBObject() a getEJBLocalObject() jsou metody EJB 2.1 a vyhazují při vyvolání výjimku. Metoda SessionContext.getBusinessObject() vrací odkaz aktuální bean, který může být vyvolaný jiným klientem. Tato reference je stejná jako reference v Javě. Parametr businessInterface musí být lokální nebo vzdálené rozhraní, takže kontejner ví, zda má vytvořit lokální nebo vzdálený odkaz na bean. Metoda getBusinessObject() dovoluje instanci beanu získat odkaz sama na sebe a ten předat jinému beanu:
Poznámky
@Stateless public class A_Bean A_Bean implements A_BeanRemove A_BeanRemove { @Resource private SessionContext context; context; public void someMethod() someMethod() { B_BeanRemote B_BeanRemote b = . . . // get a remote reference to B_Bean B_Bean. Bean. A_BeanRemote A_BeanRemote myself = getBusinessObject(A_ getBusinessObject(A_BeanRemote (A_BeanRemote. BeanRemote.class); class); b.aMethod b.aMethod( myself); aMethod(myself); } }
Pro instanci beanu je nepřípustné, aby předala referenci this jinému beanu; místo toho předává odkaz svého vzdáleného nebo lokálního EJB objektu, který instance beanu dostane z jejího
SessionContextu. Metoda SessionContext.getInvokedBusinessInterface() dovoluje stanovit, zda váš bean byl vyvolán prostřednictvím vzdáleného, lokálního nebo web service rozhraní. Vrací vyvolané business rozhraní jako třídu.
Poznámky
EJBContext • Třída SessionContext je podtřídou třídy javax.ejb.EJBContext. • Třída EJBContext definuje některé užitečné metody, které poskytují užitečné informace beanu za jeho běhu. • Definice rozhraní EJBContext:
42
package javax. javax.ejb; ejb; public interface EJBContext { public Object lookup( lookup(String name); // EJB 2.1 only> only> TimerService public TimerService getTimerService() getTimerService() throws java. java.lang. lang.IllegalStateException; IllegalStateException; // security methods public java. java.security. security.Principal getCallerPrincipal(); getCallerPrincipal(); public boolean isCallerInRole( isCallerInRole(java. java.lang. lang.String roleName); roleName); // transaction methods public javax. javax.transaction. transaction.UserTransaction getUserTransaction() getUserTransaction() throws java. java.lang. lang.IllegalStateException ; public boolean getRollbackOnly() getRollbackOnly() throws java. java.lang. lang.IllegalStateException; IllegalStateException; public vois setRollbackOnly() setRollbackOnly() throws java. java.lang. lang.IllegalStateException; IllegalStateException; // deprecated and obsolete methods public java. java.security.Identity security.Identity getCallerIdentity(); getCallerIdentity(); public Boolean isCallerInRole( isCallerInRole(java. java.security.Identity security.Identity role); public java. java.util. util.Properties getEnvironment(); getEnvironment(); public EJBHome getEJBHome() throws java. java.lang. lang.IllegalStateException ; public EJBLocalHome getEJBLocalHome() getEJBLocalHome() throws java. java.lang. lang.IllegalStateException; IllegalStateException; }
Poznámky
EJBContext • Metoda EJBContext.lookup() dovoluje vyhledat vstupy v ENC. • Metoda EJBContext.getTimerService() vrací odkaz na Timer Service kontejneru, která dovoluje bezstavovému beanu nastavit oznámení o časových událostech. Timer Service může být také injektována s použitím anotace @Resource. • Metoda EJBContext.getCallerPrincipal() se používá k získání objektu java.security.Principal, který reprezentuje klienta, který aktuálně přistupuje k beanu. 44
EJBContext • Objekt Principal může být např. využitý k identifikaci klienta, který dělá aktualizace: @Stateless public class BankBean implements Bank { @Resource SessionCintext context; ... public void withdraw(int acid, double amount) throws AccessDeniedException { String modifiedBy = principal.getName(); ... } } 45
@Stateless public class BankBean implements Bank { @Resource SessionContext context; context; public void withdraw( withdraw(int acid, acid, double amount) amount) throws AccessDeniedException { if( if(amount > 10000) { boolean inManager = context. context.isCallerInRole( isCallerInRole(“Manager” Manager”); if(! if(!isManager (!isManager) isManager) { // Only Managers can withdraw more than 10k. throw new AccessDeniedException(); AccessDeniedException(); } } } }
Poznámky Metoda EJBContext.isCallerI nRole() říká, zda-li klient přistupující k beanu je členem specifické role identifikované jménem role. Viz příklad: Některé uváděné metody jsou k verzi EJB 2.1 a jsou již zastaralé. Např. metody getEJBHome() a getEJBLocalHome() způsobí vyhození výjimky RuntimeException.
Životní cyklus bezstavových session beanů • Životní cyklus bezstavového beanu má pouze dva stavy: Does Not Exit a Metod-Ready Pool. – Stav Metod-Ready Pool je společná oblast instancí objektů bezstavových beanů, které nejsou v použití. – Stav Does Not Exit reprezentuje stav, kdy z beanu ještě nebyly vytvořeny instance.
• Když je server EJB poprvé spuštěn, může si vytvořit několik instancí bezstavových beanů (záleží na implementaci), při nedostatku instancí, může server vytvořit další instance a přidat je do společné oblasti. 47
Přechod do stavu Metod-Ready Pool • Při přechodu ze stavu Does Not Exit do stavu Metod-Ready Pool jsou provedeny tři operace: 1. je vytvořena instance beanu vyvoláním metody Class.newInstance() 2. kontejner injektuje zdroje, které metadata beanu požadují prostřednictvím anotací nebo XML popisovače rozmístění. 3. kontejner EJB bude posílat (vysílat) událost postconstruction.
48
Přechod do stavu Metod-Ready Pool • Třída beanu se může registrovat pro tyto události anotací metody s @javax.annotation.PostConstruct. • Tato anotační metoda zpětného volání je volána kontejnerem poté, co je vytvořena instance beanu. • Může mít libovolné jméno, musí vracet void, nesmí mít žádné parametry a vyhazovat výjimku no check.
49
Přechod do stavu Metod-Ready Pool • Třída bean může definovat pouze jednu metodu @PostConstruct: @Stateless public class MyBean implements MyLocal { @PostConstruct public void myInit() { }
• Bezstavové session beany mohou udržovat otevřená spojení na zdroje po celý jejich životní cyklus. • Metoda @PreDestroy by měla zavřít otevřené zdroje před tím, než je instance bezstavového session beanu vyklizena z operační paměti.
50
Život ve stavu Method-Ready Pool • V tomto stavu je instance připravena obsloužit požadavky klienta. • Když klient vyvolá business metodu EJB objektu, volání metody je delegováno na libovolnou volnou instanci ve společné oblasti Method-Ready Pool. • Zatímco instance vykonává požadavek, není dostupná jiným EJB objektům. • Ihned jak instance skončí, je okamžitě dostupná libovolnému EJB objektu, který ji potřebuje. • Instance bezstavových session beanů jsou přiřazeny objektu EJB pouze po dobu trvání volání jedné metody. 51
Život ve stavu Method-Ready Pool • Když je instance vybrána ze společné oblasti, její ContextSession se mění tak, aby odrážel kontext objektu EJB a klienta vyvolávající metodu. • Instance beanu mohou být začleněny do transakčního rozsahu klientské žádosti a mohou přistupovat k informacím SessionContextu, které jsou specifické pro dotaz klienta. • Např. bezpečnostní a transakční metody. • Poté co instance ukončí obsluhu klienta, je odloučena (oddělena) od objektu EJB a vrací se do společné oblasti připravených metod. 52
Život ve stavu Method-Ready Pool • Klienti, kteří potřebují vzdálený, nebo lokální odkaz na bezstavový session bean, začnou s injektovaným odkazem nebo vyhledáním bezstavového beanu v JNDI. • Vrácená reference nezpůsobí, že instance session beanu bude vytvořena, nebo vytažena ze společné oblasti, dokud na ni není vyvolána metoda. • Metoda PostConstruct je vyvolána pouze jednou a to bezprostředně po vytvoření instance.
53
Přechody mimo společnou oblast: ukončení života instance bezstavového beanu • Instance beanu opouští společnou oblast MetodReady Pool do stavu Does Not Exit, když je server již dále nepotřebuje, nebo když se server rozhodne zmenšit počet instancí bezstavových session beanů.
54
Stavové session beany • Každý stavový session bean (jeho instance) je předurčen pouze pro jednoho klienta po celou dobu života. • Zastupuje klienta jako agent klienta. • Udržuje tzv. konverzační stav v datových atributech třídy beanu. • To umožňuje, aby byly závislé na předchozích voláních jiných metod. • Instance beanu udržuje „specifická data klienta“.
55
Stavové session beany • Stavové session beany umožňují zapouzdřit část business logiky a konverzační stav klienta a přesunout to na server. • To dělá aplikaci klienta „tenší“ (thin) a systém jako celek je lépe řiditelný (ovladatelný). • Stavový session bean je takto jako klientův agent, který vykoná (zařídí) řadu úloh (taskflow) a interakcí s dalšími beany. • Takto zapouzdřuje řízení toku úloh a představuje jednoduché rozhraní, které ukrývá detaily vzájemně závislých operací nad DB a jinými beany. 56
Nastavení TravelAgent – entita Reservation • TravelAgent používá následující entity: Cabin, Cruise, Reservetion, Customer. • Koordinuje interakce těchto entitních beanů, aby zabukoval pasažera na plavbu (cruise). • Bude provedena modifikace entity Reservation, aby se její další vazby na entity provedly najednou. • Bude definován další konstruktor, který to bude provádět.
57
@Entity public class Reservation implements java. java.io. io.Serializable { private int id; private Date date; date; private double amountPaid; amountPaid; private Cruise cruise; cruise; private Set Cabin> cabins = new HashSet< HashSet(); Cabin>(); private Set Customer> customers = new HashSet< HashSet(); Customer>(); public Reservation() Reservation() {} public Reservation Reservation( (Customer customer, customer, Cruise cruise, cruise, Cabin cabin, cabin, double price, Date dateBooked) dateBooked) { setAmountPaid( setAmountPaid(price); price); setDate( setDate(dateBooked); dateBooked); setCruise(cruise); setCruise(cruise); Set cabins = new HashSet(); HashSet(); cabins. cabins.add( add(cabin); cabin); this. this.setCabins( setCabins(cabins); cabins); Set customers = new HashSet(); HashSet(); customers. customers.add( add(customer); customer); this. this.setCustomers( setCustomers(customers); customers); }
Poznámky Volání konstruktoru umožní se vyhnout mnoha dalším volání set metod.
TravelAgent • Cílem tohoto stavového beanu je vytvořit rezervaci pro danou plavbu. • Bude vytvořeno pouze vzdálené rozhraní – lokální rozhraní je podobné a nebude se aktuálně využívat.
59
package com.titan. com.titan.travelagent .titan.travelagent; travelagent; import import import import
com.titan. com.titan.processpayment .titan.processpayment. processpayment.CreditCardDO; CreditCardDO; javax. javax.ejb. ejb.Remote; Remote; com.titan. com.titan.domain .titan.domain. domain.Customer; Customer; com.titan. com.titan.domain .titan.domain. domain.Address; Address;
@Remote public interface TravelAgentRemote { public Customer findOrCreateCustomer( findOrCreateCustomer(String first, first, String last); last); public void updateAddress( updateAddress(Address addr); addr); public void setCruiseID( setCruiseID(int cruise); public void setCabinID( setCabinID(int cabin); cabin); public TicketDO bookPassage( bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState; IncompleteConversationalState; } // Výjimka znamená znamená, že metoda nemá nemá dostatek potř potřebných informací informací public class IncompleteConversationalState extends java. java.lang. lang.Exception { public IncompleteConversationalState () { super (); } public IncompleteConversationalState (String msg) msg) { super (msg (msg); msg); } }
Poznámky Vzdálené rozhrani TravelAgent session bean Bean potřebuje znát cruise, cabin, customer k vytvoření rezervace. Identifikace klienta je podle jména a přijmení. Po vyhledání (určení) customer, cruise, cabin, provede metoda bookPassage() rezervaci.
TicketDO – doménový objekt • Třída TicketDO je definovaná jako objekt předávaný hodnotou (pass-by-value object). • Tato třída umožňuje oproti jiným možnostem, zaslat klientovi společně všechny informace, které je třeba.
61
package com.titan. com.titan.travelagent .titan.travelagent; travelagent; import com.titan. com.titan.domain .titan.domain.Cruise; domain.Cruise; import com.titan. com.titan.domain .titan.domain. domain.Cabin; Cabin; import com.titan. com.titan.domain .titan.domain. domain.Customer; Customer; public class TicketDO implements java. java.io. io.Serializable { public int customerID; customerID; public int cruiseID; cruiseID; public int cabinID; cabinID; public double price; public String description; description; public TicketDO( TicketDO(Customer customer, customer, Cruise cruise, cruise, Cabin cabin, cabin, double price) { description = customer. customer.getFirstName( getFirstName( )+ " " + customer. customer.getLastName( getLastName( ) + " has been booked for the " + cruise.getName cruise.getName( getName( ) + " cruise on ship " + cruise.getShip cruise.getShip( getShip( ).getName ).getName( getName( ) + ".\ ".\n" + " Your accommodations include " + cabin. cabin.getName( getName( ) + " a " + cabin. cabin.getBedCount( getBedCount( ) + " bed cabin on deck level " + cabin. cabin.getDeckLevel( getDeckLevel( ) + ".\ ".\n Total charge = " + price; customerID = customer. customer.getId(); getId(); cruiseID = cruise.getId cruise.getId(); getId(); cabinID = cabin. cabin.getId(); getId(); this.price this.price = price; } public String toString( toString( ) { return description; description; } }
Poznámky
Klientův pohled • TravelAgent je využívaný aplikací v Javě GUI poli, do kterých se zapíší požadavky zákazníka (customer) na plavbu a kabinu. Context jndi = getInitialContext(); Object ref = jndi.lookup(“TravelAgentBean/remote”) TravelAgentRemote agent = (TravelAgentRemote) PortableRemotaObject.narrow(ref, TravelAgentRemote.class);
• Kód vyhledá TravelAgent bean v JNDI. • Při každém vyhledání stavového session beanu se vytvoří nová instance agent. 63
Klientův pohled Customer cust = agent.findOrCreateCustomer(textField_firstName.getText(), testField_lastName.getText());
• Změny adresy provede rovněž agent Address updatedAddress = new Address(textField_street.getText(), … ); agent.updateAddress(updatedAddress);
• Dále je třeba určit cruise a cabin Integer cruise_id = new Integer(textField_cruiseNumber.getText()); Integer cabin_id = new Integer(textField_cabinNumber.getText()); agent.setCruiseID(cruise_id); agent.setCabinID(cabin_id); 64
Poznámky String cardNumber = textField_ textField_cardNumber. cardNumber.getText(); getText(); Date date = dateFormatter. dateFormatter.parse( parse(textField_ textField_cardExpiration. cardExpiration.gettext()); gettext()); String cardBrand = textField_ textField_cardBrand. cardBrand.getText()); getText()); CreditCardDO card = new CreditCardDO9cardNumber, date, date, cardBrand); cardBrand); double price = double.valueOf double.valueOf( valueOf(textField_ textField_cruisePrice. cruisePrice.getText()). getText()).doubleValue ()).doubleValue(); doubleValue(); TicketDO ticket = agent.bookPassage(card, agent.bookPassage(card, price); Printingservice.print(Ticket); Printingservice.print(Ticket);
Na konci procesu rezervace agent doplní credit card a price pro dokončení transakce:
package com.titan. com.titan.travelagent .titan.travelagent; travelagent; import import import import import import
com.titan. com.titan.processpayment .titan.processpayment.*; processpayment.*; com.titan. com.titan.domain .titan.domain.*; domain.*; javax. javax.ejb.*; ejb.*; javax.persistence.*; javax.persistence.*; javax. javax.annotation.EJB; annotation.EJB; java. java.util. util.Date; Date;
@Stateful public class TravelAgentBean implements TravelAgentRemote { @PersistenceContext( PersistenceContext(unitName="titan") unitName="titan") private EntityManager entityManager; entityManager; @EJB private ProcessPaymentLocal processPayment; processPayment; private Customer customer; customer; private Cruise cruise; cruise; private Cabin cabin; cabin; public Customer findOrCreateCustomer( findOrCreateCustomer(String first, first, String last) last) { try { Query q = entityManager. entityManager.createQuery(" createQuery("from ("from Customer c where c.firstName c.firstName = :first :first and c.lastName c.lastName = :last :last"); last"); q.setParameter q.setParameter(" setParameter("first ("first", first", first); first); q.setParameter q.setParameter(" setParameter("last ("last", last", last); last); this. this.customer = (Customer (Customer)q. Customer)q.getSingleResult )q.getSingleResult(); getSingleResult();
Poznámky TravelAgent bean upravený bean
} catch (NoResultException notFound) notFound) { this. this.customer = new Customer(); Customer(); this. this.customer. customer.setFirstName( setFirstName(first); first); this. this.customer. customer.setLastName( setLastName(last); last); entityManager. entityManager.persist( persist(this. this.customer); customer); } return this. this.customer; customer; } public void updateAddress( updateAddress(Address addr) addr) { this. this.customer. customer.setAddress( setAddress(addr); addr); this. this.customer = entityManager. entityManager.merge( merge(customer); customer); } public void setCabinID( setCabinID(int cabinID) cabinID) { this. this.cabin = entityManager. entityManager.find( find(Cabin. Cabin.class, class, cabinID); cabinID); if (cabin == null) null) throw new NoResultException(" NoResultException("Cabin ("Cabin not found"); found"); } public void setCruiseID( setCruiseID(int cruiseID) cruiseID) { this.cruise this.cruise = entityManager. entityManager.find(Cruise. find(Cruise.class (Cruise.class, class, cruiseID); cruiseID); if (cruise == null) null) throw new NoResultException("Cruise NoResultException("Cruise not found"); found"); }
Poznámky
@Remove public TicketDO bookPassage( bookPassage(CreditCardDO card, double price) throws IncompleteConversationalState { if (customer == null || cruise == null || cabin == null null) ) { throw new IncompleteConversationalState( IncompleteConversationalState( ); } try { Reservation reservation = new Reservation( Reservation( customer, customer, cruise, cabin, cabin, price, new Date( Date( )); entityManager. entityManager.persist( persist(reservation); reservation); processPayment. processPayment.byCredit( byCredit(customer, customer, card, price); TicketDO ticket = new TicketDO( cabin, , TicketDO(customer, customer, cruise, cabin price); return ticket; ticket; } catch( catch(Exception e) { throw new EJBException(e); EJBException(e); } } }
Poznámky Metoda představuje koncept taskflow.
Klientův pohled • Anotace @Remote u metody bookPassage() říká EJB kontejneru, že když je metoda dokončena, již dále nepotřebuje session bean. Kontejner proto odstraní session bean z kontejneru.
69
Životní cyklus stavových session beanů • Stavové session beany nepoužívají instance pooling (sdílení instancí). • Pokud instance stavových session beanů zahálejí, jsou „vypovězeny“ z paměti. • Z toho důvodu, musí být každá instance stavového session beanu nejdříve pasivována, před tím, než je „vypovězena“ z paměti. • Pasivace zabezpečí uchování konverzačního stavu instance. • Následně pak musí být aktivovaná – konverzační stav je získán zpět. 70
Životní cyklus stavových session beanů instance throws system exception
Does Not Exist
Class.newInstance() injections @PostConstruct
@Predestroy
Method-Ready business method
timeout
@PrePassivate @PostActivate
timeout
Passive
create()
71
Stavy session beanu • Does Not Exist State – session neexistuje v paměti, nebyla dosud vytvořena instance • Method-Ready State – v tomto stavu instance beanu může sloužit požadavkům od klientů – Transakce do Method-Ready State – Když klient vyvolá první metodu stavového session beanu, začíná jeho život. Kontejner vyvolá metodu newInstance() třídy beanu. – Kontejner injektuje všechny závislosti do instance beanu. – Kontejner vyvolá @PostConstruct callback metody, pak pokračuje s aktuální první metodou. 72
Stavy session beanu – Život ve stavu Method-Ready. • Během tohoto stavu instance neobsluhuje požadavky klienta a udržuje konverzační stav a otevřené zdroje ve svých instančních proměnných.
– Transakce out of the Method-Ready State • Instance beanu může opustit tento stav buď do stavu Passivete nebo stavu Does Not Exist. • Je-li instance beanu odstraněna, dostane se do stavu Does Not Exist. • Odstranění se dosáhne vyvoláním metody, která je anotovaná jako @Remove. 73
Stavy session beanu • Kontejner může také instanci beanu do stavu Does Not Exist po uplynutí timeoutu. To však není možné, pokud probíhá transakce.
• Passivated State – je to doba, kdy instance beanu neslouží metodám klienta. – V tomto stavu kontejner uloží konverzační stav a vykáže instanci beanu z paměti. – Konverzační stav instance beanu může sestávat z hodnot primitivních typů, serializovatelných objektů a následujících speciálních typů: 74
Stavy session beanu – – – – – –
javax.ejb.SessionContext javax.jta.UserTransaction javax.naming.Context javax.persistence.EntityManager javax.persistence.EntityManagerFactory odkazy na další instance beanů
– konverzační stav je obyčejně serializován pro další uchování.
75
Stavové session beany a rozšířený perzistentní kontext • Dosud byt využíván perzistentní kontext ohraničený transakcí. Na konci transakcí se proto prováděla operace: EntityManager.merge(). • Rozšířený perzistentní kontext umožňuje, aby všechny nahrané (loaded) entity zůstaly stále řízené po libovolném volání metody TravelAgentBean.
76
import static javax.persistence. javax.persistence.PersistenceContextType .persistence.PersistenceContextType.EXTENDED; PersistenceContextType.EXTENDED; @Stateful public class TravelAgentBean implements TravelAgentRemote { @PersistenceContext( PersistenceContext(unitName="titan unitName="titan” ="titan”, type=EXTENDED) type=EXTENDED) private EntityManager entityManager; entityManager;
public void updateAddress( addr) ) { updateAddress(Address addr customer. customer.setAddress( setAddress(addr); addr); } . . . } //Nen //Není Není potř potřeba př přidá idávat metodu merge() merge() entitní entitního managera
Poznámky
Vnořené stavové session beany • Při injektování stavový session bean do EJB s anotací @EJB je vytvořena session pro takto injektovanou instanci: @Stateful public class ShoppingCartBean implements ShopingCart { @EJB AnotherStatefulLocal another; @Remove void checkout{ } }
• Když je reference stavového session beanu injektovaná do jiného stavového session beanu, obsažený (vnější) bean vlastní injektovanou session. 78
Vnořené stavové session beany • Při odstranění vnějšího beanu je také odstraněn vnořený bean. • Tato konstrukce je výhodná pro agregaci konverzace business procesů bez starostí o řízení jejich životního cyklu.
79