Principe d'architecture
Le schéma suivant illustre le principe d'architecture utilisé
pour gérer la notion de composant (bean). Tout bean n'est jamais
accédé directement par un client (qui peut être également
un bean). L'accès à une méthode métier d'un
bean passe par un objet d'interposition (d'interface EJBObject), qui se
charge de gérer les propriétés associées au
bean. La classe d'un objet d'interposition est automatiquement généré
par le compilateur jonas, en fonction de l'interface du bean auquel il
est associé.
Tout bean entité ou session (interface EntityBean
/ SessionBean)
possède une méthode setEntityContext qui permet de positionner
un contexte courant, depuis lequel on peut récupérer une
référence vers l'objet d'interposition courant. Tout objet
d'interposition (interface EJBObject)
fournit des méthodes (getHandle(), getPrimaryKey()) permettant de
manipuler différentes sortes de références vers le
bean (voir ci-après).
Références distribuées vers les beans
Pour comprendre comment mettre en place des communications entre des
beans, ou entre un client et un bean donné, on peut consulter la
documentation
d'assemblage fournie par Jonas. Lorsqu'un client (qui peut être
un bean) souhaite communiquer avec un bean B, il doit posséder une
référence distribuée vers B. Une
référence distribuée vers un bean est en fait une
référence remote (au sens de java) vers son objet d'interposition.
Pour obtenir une référence distribuée, les scénarios
suivants sont possibles :
// Crée un contexteLorsqu'une référence vers un bean B1 doit être stockée dans un bean persistant B2, alors soit B2 conserve un handler vers B1, soit B2 conserve la clé primaire de B1 ou bien une valeur équivalente. Selon le cas, les valeurs conservées dans la base seront lisibles ou non. Remarquons qu'aussi bien le handler que la clé primaire d'un bean B peuvent être obtenues via la référence distribuée de B (méthodes getHandle() et getPrimaryKey()).
initialContext = new InitialContext();
// Récupère une référence sur un Home
AccountHome home = null;
try {
home = (AccountHome)PortableRemoteObject.narrow(
initialContext.lookup("AccountHome"),
AccountHome.class);
} catch (Exception e) {
System.err.println("Cannot lookup AccountHome: " +e);
System.exit(2);
}
// Récupère un bean identifié par sa PK (clé primaire)
Account clientAccount = (Account)accountHome.findByPrimaryKey(clientAccountPK);
Clé primaire
Tout bean entité possède une clé primaire permettant
de le désigner de manière unique. La composition d'une clé
primaire est définie par le programmeur, soit au travers d'une classe
explicite nommée XXXBeanPK, soit en utilisant une classe Java standart
telle que java.lang.Integer, ou java.lang.String par exemple.
Dans le premier cas, les champs de la classe XXXBeanPK doivent être
un sous-ensemble des champs du bean (classe XXXBean). C'est de cette manière
que Jonas pourra savoir, étant donné un bean, quelle est
sa clé primaire.
Dans le deuxième cas (PK de classe Java standard), la configuration
associé au bean XXX devra définir quel est le champs du bean
(champs de la classe XXXBean) qui correspond à la clé primaire.
Persistance
Un bean entité peut être de catégorie "container-managed
persistence" ou bien "entity-managed persistence". Dans le premier cas,
sa persistance est automatiquement gérée par le support Jonas,
en fonction de règles de "mapping" avec une base de données
fournies par le programmeur sous une forme déclarative. Dans le
second cas, c'est au programmeur de gérer explicitement la persistance
des beans dans ses programmes, en implémentant les méthodes
de création, chargement, etc (ejbCreate, ejbLoad, ejbStore,...).
Dans le projet ecom, nous allons utiliser des beans de catégorie
"container-managed persistence". Des informations plus précises
concernant la programmation de ce type de beans sont données en
2.3.
Transactions
Un bean donné peut nécessiter de
s'exécuter de manière transactionnelle. Selon le cas, toutes
ses méthodes ou bien seulement une partie devront être exécutées
au sein d'une transaction. L'association d'un comportement transactionnel
à un bean est réalisé par la définition du
descripteur de déploiement du bean. Ce descripteur de déploiement
permet d'attacher un attribut transactionnel à chaque méthode
du bean. Les différents attributs disponibles ainsi que la sémantique
qui leur est associée sont décrits dans la documentation
de programmation de Jonas (p 14).
Dans certains cas, une transaction peut être commencée au niveau d'un client. Dans ce cas, la création, la validation et la destruction de la transaction ne peuvent pas être gérés de manière implicite par Jonas. La séquence suivante montre comment procéder.
UserTransaction utx = (UserTransaction) PortableRemoteObject.narrow(
initialContext.lookup("javax.transaction.UserTransaction"),
UserTransaction.class);
utx.begin();
...
utx.commit(); // or utx.rollback();
EJB de type Account
Un EJB de type Account gère un compte client ou magasin. Les
méthodes métier qu'il fournit sont les suivantes.
- public void deposit(double amount) : dépose une
somme d'argent sur le compte.
- double withdraw(double amount) : retire une somme d'argent
du compte.
- double balance() : retourne la somme d'argent disponible
sur le compte.
EJB de type Product
Un EJB de type Product gère un produit d'un magasin. Les méthodes
métier qu'il fournit sont les suivantes.
- int getReference() : retourne la référence
(identificateur unique) du produit
- String getName(): retourne le nom du produit
- double getPrice() : retourne le prix du produit
- int getProductStore() :
retourne la référence du magasin qui vend ce produit
EJB de type ProductStore
Un EJB de type ProductStore gère un magasin. Les méthodes
métier qu'il fournit sont les suivantes.
- Vector getProducts() : retourne la liste des produits
vendus par le magasin
- String getName() : retourne le nom du produit.
- int getReference() : retourne
la référence (identificateur unique) du magasin
- int
getAccountId() : retourne
le compte du magasin
EJB de type Cart
Un EJB de type Cart gère un caddie d'un client connecté
à l'application. Les méthodes métier qu'il fournit
sont les suivantes.
- addProduct(ProductPK productPK)
: ajoute un produit dans le caddie.
- Vector getProducts() : retourne la liste des produits
contenus dans le caddie.
- double getTotalPrice() : retourne le prix total des
produits contenus dans le caddie.
- buy(int accountId) : achète les produits contenus
dans le caddie.
Dans la version qui vous est fournie de l'application de commerce électronique, seuls les composants Account et ProductStore sont définis.
Les descripteurs de déploiement des composants sont définis au travers de deux types de fichiers XML, nommés XXX.xml et jonas-XXX.xml. On peut utiliser des fichiers de configuration propres à chaque composant, ou bien des fichiers globaux à l'application. Dans notre cas, nous avons utilisé des fichiers globaux. Le fichier ecom.xml précise pour chaque bean, quel est son type et quels sont les fichiers d'implémentation ou de spécification qui lui sont associés. Si le bean est persistant, ce fichier précise également quels sont les champs qui doivent être sauvegardés dans la base de données. La liste complète des informations contenues est donnée dans la documentation de programmation, p19.
(fichier ecom.xml)
...
<entity>
<description>Deployment descriptor for the ProductStore bean </description>
<ejb-name>ProductStore</ejb-name>
<home>ecom.ProductStoreHome</home>
<remote>ecom.ProductStore</remote>
<ejb-class>ecom.ProductStoreBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>ecom.ProductStorePK</prim-key-class>
<reentrant>False</reentrant>
<cmp-field>
<field-name>productStoreId</field-name> //field of ProductStoreBean
</cmp-field>
<cmp-field>
<field-name>name</field-name>
</cmp-field>
<cmp-field>
<field-name>accountId</field-name>
</cmp-field>
</entity>
Le fichier jonas-ecom.xml précise les règles de mapping
entre les champs du bean et la base de données. Il définit
également les clauses WHERE liées aux méthodes finder
fournies par le Home du bean considéré. Enfin, les contraintes
transactionnelles associées à chaque méthode de chaque
bean sont également données.
(fichier jonas-ecom.xml)
...
<jonas-entity>
<ejb-name>ProductStore</ejb-name>
<jndi-name>ProductStoreHome</jndi-name>
<jdbc-mapping>
<jndi-name>jdbc_1</jndi-name>
<jdbc-table-name>productStores</jdbc-table-name> //DB table name
<cmp-field-jdbc-mapping>
<field-name>productStoreId</field-name> // field of the bean
<jdbc-field-name>productStoreId</jdbc-field-name> // attribute in DB
</cmp-field-jdbc-mapping>
<cmp-field-jdbc-mapping>
<field-name>name</field-name>
<jdbc-field-name>name</jdbc-field-name>
</cmp-field-jdbc-mapping>
<cmp-field-jdbc-mapping>
<field-name>accountId</field-name>
<jdbc-field-name>accountId</jdbc-field-name>
</cmp-field-jdbc-mapping>
<finder-method-jdbc-mapping>
<jonas-method>
<method-name>findByNumber</method-name>
</jonas-method>
<jdbc-where-clause>where productStoreId = ?</jdbc-where-clause>
</finder-method-jdbc-mapping>
<finder-method-jdbc-mapping>
<jonas-method>
<method-name>findAllProductStores</method-name> //findAllXXX is defined by Jonas
</jonas-method>
<jdbc-where-clause></jdbc-where-clause>
</finder-method-jdbc-mapping>
</jdbc-mapping>
</jonas-entity><assembly-descriptor>
<container-transaction>
<method>
<ejb-name>ProductStore</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Supports</trans-attribute>
</container-transaction>
</assembly-descriptor>
La mise à jour de la base est alors réalisée
au travers d'un script d'initialisation (fichier ecom.idb)
qui contient, entres autres, les directives suivantes.
; delete the old table if necessaryPour consulter ou modifier cette base au travers d'un interprête SQL, vous pouvez utiliser la commande java commsql, en précisant ensuite l'URL de la base à laquelle vous souhaitez vous connecter (jdbc:idb:ecom.prp par exemple). Pour plus d'informations sur cette commande et sur IDB, vous pouvez consulter le site Web d'InstantDB.
e DROP TABLE productstores;
; create the new table
e CREATE TABLE productstores {
productStoreId int PRIMARY KEY,
accountId int,
name VARCHAR(30));; put some initial data in the table
p INSERT INTO productstores VALUES(?,?,?);
s 1001,107, 'Galeries Lafayette';
s 1002,108, 'Mark & Spencer';
s 1003, 109, 'Bazar de l'Hotel de Ville';
En outre, le répertoire jonas_jdk1.2 contient les fichiers
importants suivants :
Si cela est possible, il est conseillé de positionner la
variable d'environnement PATH de manière
à référencer le chemin d'accès à $JONAS_ROOT/bin
et à . La variable CLASSPATH sera quant
à elle positionnée automatiquement par le script config.env.
Répertoire sb
Ce répertoire contient un bean session appelé Op. Il
contient les fichiers suivants :
Op.java : Op bean Remote interface.
OpHome.java : Op bean Home remote interface.
OpBean.java : Op bean implementation.
ejb-jar.xml : Standard deployment descriptor for bean Op.
jonas-ejb-jar.xml : JOnAS specific deployment descriptor
jonas.properties : Property file for the EJB server, local to this directory.
compile.sh : Script shell to compile the bean
ClientOp.java : Client for the bean.
Répertoire eb
Ce répertoire contient deux beans persistents partageant la
même interface (Account, pour la gestion d'un compte bancaire). L'un
de ces beans est géré selon le modèle bean-managed
persistence, c'est à dire que la gestion de sa persistence est programmée
explicitement par le programmeur. La gestion de la persistence de l'autre
bean suit le modèle container-managed persistence, c'est à
dire que la gestion de sa persistence est prise en charge de manière
implicite par le support d'exécution sous-jacent.
Le répertoire eb contient plus précisément les
fichiers suivants :
Account.java : Account bean Remote interface.Pour exécuter ces exemples, il faut :
AccountHome.java : Account bean Home interface.
AccountExplBean.java : Account bean implementation, in bean-managed (explicit) persistence.
AccountImplBean.java : Account bean implementation, in container-managed (implicit) persistence.
AccountBeanPK.java : Primary key class of the Account bean.
ClientAccount.java : Client for this bean
ejb-jar.xml : standard deployment descriptor, for both beans
jonas-ejb-jar.xml : JOnAS specific deployment descriptor
jonas.properties : Property file for the EJB server, local to this directory.
Account.sql : SQL script for Oracle, used to create the table in the database.
Account.idb : SQL script for InstantDB, used to create the table in the database.
Account.prp : Configuration file for InstantDB.
compile.sh : Script shell to compile the bean
Note : pour tout probème de connection avec la base, il est conseillé :
Pour programmer des beans, vous pouvez utiliser l'utilitaire newbean fourni par Jonas (cf. $JONAS_ROOT/bin/newbean), qui permet de générer des squelettes de tous les fichiers requis pour la définition d'un bean.
Dans un deuxième temps, on s'intéresse à la gestion des aspects transactionnels. Après vous être référré à la documentation fournie par Jonas à cet égard, vous adapterez les descripteurs de déploiement des beans en fonction du comportement transactionnel que vous jugez utile de mettre en place. Vous testerez et validerez le bon fonctionnement des transactions, par exemple en lanceant un achat et en arrêtant brutalement le serveur.
Enfin, si le temps vous le permet, vous pourrez étudier comment réaliser la transformation du bean session Cart en un bean persistant. En particulier, cela pose certains problèmes au niveau de la gestion des références inter-beans. Vous tenterez de mettre en évidence ces problèmes et de proposer une solution.
Rappels
Pour exécuter votre application, il faudra que vous ayez crée
la base requise pour faire fonctionner votre exemple, en complétant
le fichier de script ecom.idb sous $JONAS_ROOT/ecom/src.
Pour disposer d'une documentation de type javadoc des interfaces et classes standards fournies par la spécification J2EE, consulter l'URL http://java.sun.com/products/ejb/javadoc-2_0-pfd/.
3.4 Expérimentations
supplémentaires
Si le temps le permet, il est intéressant de tester l'application
de commerce électronique en répartissant les beans qui la
composent sur un ensemble de serveurs EJB. La configuration de la répartition
de l'application suit des règles précises qui sont décrites
dans la documentation
de programmation, p 17.
3.5 Une solution
Une fois le mini-projet terminé, vous pourrez consulter une
solution au travers de l'archive compréssée ecom.tar.gz.
La solution proposée n'utilise pas (volontairement) les handles
pour gérer les références inter-beans. De cette manière,
les liens qui associent les références distribuées
et les clés primaires sont explicités. Autrement dit, la
gestion des références qui est proposée dans la solution
fournie reflète ce qui est géré de manière
transparente dans les handles.