-
Notifications
You must be signed in to change notification settings - Fork 1
Tutoriel développeur : créer un composant de A à Z.
- Conception : comment organiser son composant ?
- Implémentation : comment créer une stratégie ?
- Test : comment tester une stratégie ?
- Intégration : comment ajouter son composant à la bibliothèque ?
- Documentation : comment documenter mon composant et ma stratégie ?
La première étape de la création d'un composant est de réfléchir à son organisation et son potentiel découpage. Le découpage d'un composant peut être facultatif, il dépend des attentes et des objectifs que l'on se fixe, il est donc important de réfléchir à ce que l'on souhaite avant de se lancer dans le développement.
L'intérêt du découpage est d'affiner le détail de la simulation et de répartir les responsabilités de chaque élément pour mieux répartir l'implémentation au passage. Encore une fois, si la simulation est destinée à un large public non expert, on privilégiera un scénario simple donc avec peu de détail et de complexité. Si la simulation est destinée à un public expert, il sera cette fois-ci plus intéressant de détailler pour en tirer un intérêt de la simulation.
Exemple Une carte à puce peut être vue comme un composant abstrait "Carte" contenant une puce et une bande magnétique. La puce pourrait alors avoir un niveau de détail supplémentaire avec les applications "Visa", "CB", "Mastercard", "Moneo", ...
Pour chaque composant identifié lors du précédent découpage, il faut maintenant réfléchir au type de composant qui sera nécessaire pour l'implémentation.
Voici les 3 types de composant :
- ComponentI : entité passive qui réagit un message
- ComponentO : entité active qui réagit à un évènement
- ComponentIO : combo des capacités de Input et Output
Exemple Une puce réagit à des messages provenant d'un lecteur de carte à puce.
Il faut maintenant implémenter le comportement de nos composants afin de reproduire le scénario imaginé au travers d'une stratégie.
Conseil 1 : Une stratégie n'a pas de notion d'état, elle est juste l'implémentation déléguée du composant : il ne faut donc pas utiliser les attributs de classe mais les propriétés du composant (component.getProperties()).
Conseil 2 : Mettre les clés des propriétés en tant que constante de la classe.
Exemple public static final String CKEY_ACQUIRER_ID = "acquirer_id";
Pour créer une stratégie, créer une classe implémentant l'interface IStrategy.
public List<PropertyDefinition> getPropertyDefinitions();
Permet à la stratégie de communiquer les propriétés qu'elle utilise au composant (porteur des propriétés).
Exemple
ArrayList defs = new ArrayList();
defs.add(new PropertyDefinition(CKEY_ACQUIRER_ID, "", true, "Identifiant de l'acquéreur"));
defs.add(new PropertyDefinition(CKEY_ACCEPTOR_TERMINAL_ID, "", true, "Identifiant du système d'acceptation"));
defs.add(new PropertyDefinition(CKEY_ACCEPTOR_TERMINAL_ID, "", true, "Identifiant de l'accepteur"));
defs.add(new PropertyDefinition(CKEY_MERCHANT_CATEGORY_CODE, "", false, "Code de catégorie de marchandise"));
defs.add(new PropertyDefinition(CKEY_CURRENCY_CODE, "", true, "Code de la devise utilisée"));
return defs;
public void init(IOutput _this, Context ctx);
Permet à la stratégie de s'initialiser avant le lancement de la simulation. Voici les opérations à réaliser à cette étape :
- s'enregistrer à un évènement
- remettre à zéro des propriétés avant qu'elle soit demandée à la simulation (exemple : pin)
@Override
public void init(IOutput _this, Context ctx) {
// enregistrement aux évènements suivants
ctx.subscribeEvent(_this, "SMART_CARD_INSERTED");
ctx.subscribeEvent(_this, "REMOTE_DATA_COLLECTION");
((ComponentIO) _this).getProperties().put("pin_enter", null, true);
((ComponentIO) _this).getProperties().put("amount", null, true);
}
public void processEvent(T _this, String event);
Permet de traiter un évènement "event". Les évènements de la simulation dépendent des points de démarrage configurés dans le simulateur.
@Override
public void processEvent(ComponentIO _this, String event) {
switch (event) {
case "SMART_CARD_INSERTED":
// implémentation
break;
}
}
public IResponse processMessage(T _this, Mediator mediator, String data);
Permet de traiter un message entrant provenant d'un autre composant.
@Override
public IResponse processMessage(ComponentIO chip, Mediator m, String data) {
// parse du message entrant, on a du ISO 7816 ...
ISOMsg msg = ISO7816Tools.read(data);
// impl ...
// message réponse avec resData, chaine de réponse (éventuellement un pack() d'un ISOMsg).
return DataResponse.build(m, resData);
}
Dans le cas d'un traitement d'un évènement ou d'un message nous pouvons être amené à appeler un autre composant. Pour cela il faut utiliser les médiateurs par le biais de la classe Context.
Récupération d'un médiateur avec un composant d'un type donné
// récupération d'un médiateur
Mediator m = Context.getInstance().getFirstMediator(_this, ComponentEP.CARD.ordinal());
// envoi de données
m.send(_this, data);
Pour plus de clarté dans le code, nous conseillons d'utiliser les énumérations pour gérer les types de composant mais il est possible d'utiliser les entiers directement (ComponentEP.CARD.ordinal()).
Transfert d'un message à un sous-composant
Pour gérer les cas de composant abstrait telle qu'une carte, on veut transférer automatiquement les messages à la puce.
// récupération du composant enfant
Component chip = Component.getFirstChildType(card, ComponentEP.CARD_CHIP.ordinal());
// récupération médiateur de transfert
Mediator m_card_chip = MediatorFactory.getInstance().getForwardMediator(m, (IInput) chip);
// envoi des données
m_card_chip.send(card, data);
Utilisation de la console
Si vous souhaitez faire apparaître des messages dans la console vous devez utilisé l'interface de journalisation SLF4J.
Pour cela, définir un logger en attribut si ce n'est déjà fait (remplacer MaClasse par votre classe)
private static Logger log = LoggerFactory.getLogger(MaClasse.class);
Puis journalisez les évènements que vous souhaitez
log.debug(LogUtils.MARKER_COMPONENT_INFO, "Mon composant vient de recevoir un message : "+data);
Les composants s'adaptent bien aux développements avec les tests unitaires. Cela permet de tester son composant en cours de développement dans un premier temps et d'assurer la détection de régression dans un second temps. Les tests unitaires doivent être rédigés de façon segmentée afin de tester chaque fonction indépendamment efficacement.
Conseils
- Définir les composants en attribut
- Initialiser les composants (nom, strategie, parenté, propriétés, ...) dans une méthode statique annotée @Before
- Rédiger chaque test dans une méthode annotée de @Test
- Nettoyer le contexte dans une méthode statique annotée de @After
Exemple
public class FOAcquirerRemoteDataCollectionUnitTest {
private static ComponentIO frontOffice;
private static ComponentIO acquirer;
private static ComponentIO remoteDataCollection;
@Before
public void init() throws Exception {
Context.getInstance().autoRegistrationMode();
/* Init component */
frontOffice = new ComponentIO("Front Office", ComponentEP.FRONT_OFFICE.ordinal());
acquirer = new ComponentIO("Acquirer", ComponentEP.FO_ACQUIRER.ordinal());
remoteDataCollection = new ComponentIO("Remote Data Collection",
ComponentEP.FO_ACQUIRER_REMOTE_DATA_COLLECTION.ordinal());
/* Settings kinship */
frontOffice.addChild(acquirer);
acquirer.addChild(remoteDataCollection);
/* Settings strategies */ frontOffice.setStrategy(new FOStrategy());
acquirer.setStrategy(new FOAcquirerStrategy());
remoteDataCollection.setStrategy(new FOAcquirerRemoteDataCollectionStrategy());
}
@After
public void clean() throws Exception {
Context.getInstance().reset();
}
@Test
public void testSignOn() throws ISOException {
// sign on 0804
IResponse res = remoteDataCollection.notifyMessage(null, msg0804());
Assert.assertFalse(res.isVoid());
try {
ISOMsg msg = ISO8583Tools.read_CB2A_TLC(((DataResponse) res).getData());
Assert.assertEquals(msg.getMTI(), "0814");
Assert.assertEquals(msg.getValue(39), "0000");
}
catch (ISO8583Exception | ISOException e) {
Assert.assertTrue(false);
e.printStackTrace();
}
}
}
Il faut re-générer l'application avec les modifications de la couche monétique. Si vous êtes dans Eclipse, cette opération sera effectuée automatiquement ; en dehors, il vous faudra utiliser Maven. Dans une invite de commandes, positionnez-vous dans le référentiel simulator et exécuter les commandes suivantes :
- cd simulator
- mvn clean install
- cd ../simulator-ep
- mvn clean install
- cd ../gui-simulator
- mvn clean install
- cd build
- java -jar gui-simulator-ep-1.0.0.jar
Vous pouvez copier gui-simulator-ep-1.0.0.jar dans votre répertoire livrable pour remplacer l'ancienne version où se trouve icônes, documentations et la librairie (comme défini dans le guide d'utilisateur).
Dans le cas d'un composant simple sans enfants, vous pouvez l'ajouter depuis l'interface graphique via le menu Atelier > Nouveau composant. Il suffit de ...
- Nommer votre composant
- Définir l'identifiant de classification : l'énumération ComponentEP s'en charge dans la couche monétique. On utilise alors simplement la position dans l'énumération comme identifiant. Si vous n'avez pas besoin de dissocier des types de composants, mettez 0.
- Le type de composant : Voir point 1.Conception/Organisation.
L'interface graphique n'étant pas totalement terminée, la fonction d'ajout d'un enfant à un composant n'est pas gérée de manière fonctionnelle. Pour parer cette limite, vous devez générer votre composant depuis une classe tiers en prenant exemple sur fr.ensicaen.simulator_ep.utils.GenerateBaseComponents.
Supprimez le dossier library dans le projet simulator-ep (ou celui de votre surcouche) puis définissez votre composant via le code Java dans une méthode main :
ComponentIO card = new ComponentIO("Card", ComponentEP.CARD.ordinal());
ComponentIO chip = new ComponentIO("Chip", ComponentEP.CARD_CHIP.ordinal());
chip.getProperties().put("protocol", "ISO7816");
chip.getProperties().put("pan", "4976710025642130");
ComponentIO magstrippe = new ComponentIO("Magstrippe", ComponentEP.CARD_MAGSTRIPPE.ordinal());
magstrippe.getProperties().put("iso2", "59859595985888648468454684");
card.addChild(magstrippe);
card.addChild(chip);
Créez le composant parent via la DAO :
DAO comp = DAOFactory.getFactory().getComponentDAO();
comp.create(card);
Exécutez la classe Java dans Eclipse, et rafraîchissez le projet de votre surcouche (clic droit > Refresh). Dans le dossier library/model, copiez le fichier qui s'est créé et coller-le dans votre librairie (dossier library/model à côté de votre archive Java (JAR) que vous avez régénéré en première partie.
La documentation pour aider dans l'utilisation des composants et des stratégies est directement intégrée et intégrée dans l'application (panel de droit, onglet "détails", bouton "informations"). Cette documentation se rédige en externe au format HTML.
Dans le dossier "components", les documentations des composants et dans "strategies", les documentations des stratégies. Vous devez utiliser l'identifiant de classification en nom de fichier (avec l'extension .html) ou le nom de votre composant/stratégie sachant que le nom est prioritaire sur la classification.
La documentation doit être pertinente :
- Enumération des cas gérés
- Paramétrage général du composant
- Evènement(s) géré(s)
- Protocole(s) utilisé(s)
- Composant(s) attendu(s)