APPFUSE : LANCEMENT DE VOS APPLICATIONS AVEC APPFUSE

Par Ryan Withers, ingénieur logiciel principal chez OCI MAI 2008

INTRODUCTION "Combien de temps pouvez-vous l'avoir fait ?» Cela vous semble trop familier ?

Au fil des ans, les gestionnaires ont été confrontés à une demande sans cesse croissante pour produire davantage de logiciels sur des cycles de livraison plus courts. À mesure que le paysage économique s'est mondialisé, ces pressions pour obtenir des résultats ont considérablement augmenté.

Pour les développeurs de logiciels, cela se traduit par la responsabilité 'atteindre les plus hauts niveaux de productivité. Compte tenu de cela, tout développeur bien intentionné doit constamment rechercher les outils appropriés pour le travail. Dans le monde de J2EE, nous sommes nombreux à écrire un peu de code, à ajouter une petite configuration, à écrire un peu de code, à ajouter une petite configuration, et ainsi de suite. Nous passons une grande partie de notre temps à nous préoccuper de la configuration et nous consacrons davantage de temps à la logique métier.

Le temps passé à configurer ne traduit pas bien en valeur réelle. En outre, le risque associé est énorme, dans la mesure où des erreurs de configuration peuvent générer des problèmes. En outre, l'écriture du code standard écrit pour coller les différentes couches d'un système peut être un travail fastidieux et monotone.

Si cela vous semble familier, poursuivez votre lecture pour voir comment AppFuse peut accélérer les cycles de développement et aider à réduire les risques associés aux projets nécessitant beaucoup de configuration.

AppFuse fournit des modèles de projet standard, une génération de code et un shell de code d'application permettant aux projets de démarrer rapidement. L'un de ses principaux atouts est l'automatisation de la configuration, deux éléments standard dans la plupart des projets J2EE.

Cet article couvrira les trois choses suivantes :

1. Tout d'abord, nous allons créer un projet shell. 2. Deuxièmement, nous étendrons le projet à la main en ajoutant une page de base de suivi du temps. 3. Troisièmement, nous allons effacer l'ardoise et créer à nouveau la même page de suivi temporel. Seulement cette fois, nous utiliserons l'outil appgen pour effectuer la génération complète du code de l'ensemble.> HISTOIRE / CONTEXTE

AppFuse est le fruit de Matt Raible . Il y a eu deux versions principales, mais cet article se concentrera sur AppFuse version 2. Il est conçu pour rendre le processus de création d'une application Web shell aussi clé en main que possible. La version 1 utilisait ant pour effectuer la génération de chaque niveau d'une application Web (avec la permission de la commande appgen). En outre, la version 1 prenait en charge quatre infrastructures Web. Dans la version 2, ant a été remplacé par Maven et des classes supplémentaires ont été ajoutées pour fournir un modèle d'application Web miniature entièrement fonctionnel.

Ce modèle d'application est implémenté dans quatre infrastructures Web prises en charge :

1. JSF 2. Spring MVC 3. Struts 2 4. Tapisserie

Ces modèles d’application sont disponibles sous forme d’archétypes Maven. De plus, le développeur peut choisir entre deux classifications d'archétype différentes : basique ou modulaire.

À partir du guide de démarrage rapide (ci-après dénommé "Démarrage rapide"), la version de base regroupe l'application Web sous la forme d'une unité déployable unique, tandis que la version modulaire offre une séparation entre l'interface Web et son back-end. Cela offre la possibilité de déployer plusieurs serveurs frontaux dans le futur.

Avant que je sache quoi que ce soit à propos d'AppFuse, j'avais l'impression qu'il s'agissait d'un cadre. Bien qu'il utilise des cadres, ce n'est pas nécessairement un cadre en soi.

Il serait beaucoup plus précis de dire qu’AppFuse est une distribution de bibliothèques et d’outils open source. Ces bibliothèques et outils ont été intégrés pour créer un environnement de développement d'applications rapide. Téléchargé par défaut, l'application shell prend en charge : la connexion, la sécurité et les fonctions administratives de base.

Les fonctionnalités intégrées incluent : le téléchargement de fichiers, l'ajout d'un utilisateur, la modification de votre profil, etc. Certains des packages inclus sont : , , Acegi, plusieurs packages / bibliothèques de Jakarta Commons, ANTLR, Velocity, Log4j, jMock, sans parler des frameworks Web. Comme vous pouvez le constater, la liste des bibliothèques tierces prises en charge est longue et exhaustive.

INTRODUCTION ET CONFIGURATION

Les exemples de l'article ont été développés et testés à l'aide du logiciel ci-dessous :

1. AppFuse a. Remarque: le démarrage rapide est vivement recommandé. 2. Maven 2.0.8 3. MySQL 4. JDK 1.6

Pour commencer, nous suivrons le Quick-start à la lettre, pour montrer à quel point il est facile de créer l’application shell. Suivez les étapes ci-dessous à partir de la ligne de commande pour créer l'exemple d'application. Il y a beaucoup plus d'informations dans le démarrage rapide, ceci est simplement pour la référence commode. L'exercice ci-dessous nous permettra de créer ultérieurement une extension personnalisée.

 Exécutez la commande suivante:

1. mvn archetype: créer 2. -DarchetypeGroupId = org.appfuse.archetypes 3. -DarchetypeArtifactId = appfuse-basic-struts 4. -DremoteRepositories = http: //static.appfuse.org/releases 5. -DarchetypeVersion = 2.0.1 -DgroupId = com.oci.jnb -DartifactId = timeEntry

 Veuillez noter que cette commande doit être entrée sur une ligne longue mais continue. Lors de l'exécution de maven, il créera un répertoire avec le nom timeEntrycorrespondant à la valeur fournie au -DartifactIdparamètre. Le répertoire créé est appelé répertoire du projet.  Malheureusement, le projet de base struts 2 avait des problèmes à quelques endroits. En fait, ils étaient assez mauvais pour exiger une consultation de la liste de diffusion . On m'a donné quelques suggestions, dont la meilleure consistait à extraire la source complète d'AppFuse. Cela peut être fait en exécutant la commande: mvn appfuse:full-source. Je ne sais pas pourquoi cette commande n'est pas une option standard pour tous les archétypes. Étant donné que chaque archétype est un projet shell, une grande partie du code AppFuse principal peut nécessiter une modification une fois que de nouvelles fonctionnalités ont été développées. Compte tenu de cela et de la facilité de démarrage avec la source complète, j'ai choisi de supprimer toute la source. Veuillez noter que cette commande ne fonctionnera que si elle est exécutée au tout début, avant l'ajout de personnalisations. Maintenant, prenez une minute pour exécuter la commande:mvn appfuse:full-source, qui devrait produire le résultat ci-dessous:

 Une fois le répertoire du projet créé et la source téléchargée, modifiez les paramètres de la base de données dans le fichier pom.xml. Le fichier pom a une section utilisée pour spécifier le mot de passe root de la base de données. Voir le segment en gras ci-dessous :

1. 2. < dbunit. dataTypeFactoryName > 3. org. dbunit . ensemble de données . type de données . DefaultDataTypeFactory 4. 5. < dbunit. opération . type > CLEAN_INSERT 6. < hiberner. dialecte > 7. org. hiberner . dialecte . MySQL5InnoDBDialect 8. 9. < jdbc. groupId > mysql 10. < jdbc. artifactId > mysql - connector - 11. < jdbc. version > 5.0.5 12. < jdbc. driverClassName > com. mysql . jdbc . Pilote 13. < jdbc. url > 14. 16. 17. < jdbc. nom d'utilisateur > root 18. < jdbc. mot de passe >

Cela nécessite une instance de MySQL. Installez donc la base de données et définissez un mot de passe root. Le démarrage rapide a une référence pour savoir comment changer le mot de passe root. Ou simplement, exécutez les commandes suivantes, après l’installation : définir le mot de passe pour root @ localhost = PASSWORD ( '[mot de passe choisi]' ) ; définir le mot de passe pour root @ host = PASSWORD ( '[choisir un mot de passe]' ) ;

Il y a deux lignes ici car il y a généralement deux entrées pour l'utilisateur root: une avec l'hôte localhost et l'autre avec le nom d'hôte du système (Dyer, page 14). Pour plus d'informations, consultez le manuel d'aide en ligne.  Une fois le fichier pom.xml modifié, vous pouvez exécuter la commande "mvn". Cette commande peut prendre une minute alors soyez patient. Si tout va bien, le message rapporté est :

1. [ INFO ] ------2. [ INFO ] CONSTRUIRE AVEC SUCCÈS 3. [ INFO ] ------

Maintenant, lancez la commande : 'mvn jetty: run-war' et naviguez jusqu'à http: // localhost: 8080 . Si tout se passe bien, le nom de connexion pour l'exemple d'application devrait apparaître comme dans la fenêtre ci- dessous. Les mots de passe de l'application par défaut sont admin / admin pour un administrateur et utilisateur / utilisateur pour un utilisateur normal.

La base de code de l'application shell est légère et complète. Il ne contient que 81 fichiers source et de test, ce qui est une autre raison pour laquelle je n’ai pas hésité à le télécharger. Les développeurs du développement piloté par les tests (TDD) apprécieront le code fourni avec une suite complète de tests unitaires qui passent sans problème.

Au cours de ma formation à AppFuse, j'ai téléchargé des projets pour tous les archétypes pris en charge, ce qui s'est avéré être une tâche assez facile. Comme indiqué, l'une des motivations d'AppFuse est de rendre la création d'un projet Web de base aussi simple que de créer un projet via un IDE. Comme vous pouvez le constater grâce à l'expérience ci-dessus, AppFuse a initialement réussi à atteindre cet objectif. Dans la section suivante, je traite de l'extension de cette application car, après tout, il existe des exigences commerciales qui ne demandent qu'à être mises en œuvre.

EXTENSION DE L'EXEMPLE D'APPLICATION APPFUSE

La suite de l'article se concentrera sur l'extension et la modification de l'application shell appfuse. L’objectif est d’ajouter à l’application un formulaire prenant en charge la saisie du temps. Les activités suivantes seront nécessaires : 1. Créer une classe d'entité et générer la table correspondante 2. Écrire un DAO et les tests unitaires correspondants. 3. Créez une classe de gestionnaire qui enveloppe le DAO ci-dessus en tant que service, comme une façade. 4. Développez et générez les ajouts nécessaires au niveau Web Struts2.

CRÉER UNE ENTITÉ

Par défaut, AppFuse est mis en veille prolongée en tant qu'architecture de persistance. Ce projet utilisera la valeur par défaut sans modification. Maintenant, dans le com.oci.jnb.timeEntrypaquet, créez un com.oci.jnb.timeEntry.modelpaquet, et dans ce nouveau paquet, créez une TimeEntry.javaclasse. La nouvelle TimeEntryclasse doit être annotée avec les annotations suivantes: @Entityet un @Table(name="time_entry"). Ces annotations indiquent à AppFuse que nous souhaitons créer une table appelée time_entryqui conservera les données persistantes du dao. S'il vous plaît voir ci-dessous pour la source :

1. 1 paquetage com.oci.jnb.timeEntry.model ; 2. 2 3. 3 import javax.persistence.Column ; 4. 4 import javax.persistence.Entity ; 5. 5 import javax.persistence.GeneratedValue ; 6. 6 import javax.persistence.GenerationType ; 7. 7 import javax.persistence.Id ; 8. 8 import javax.persistence.Table ; 9. 9 import com.oci.jnb.model.BaseObject ; 10. dix 11. 11 / ** 12. 12 * Entité de saisie du temps créée pour l'extension de l'application de base AppFuse. 13. 13 * 14. 14 * @author rwithers 15. 15 * / 16. 16 @ Entity 17. 17 @Table ( name = "time_entry" ) 18. 18 public class TimeEntry étend BaseObject { 19. 19 20. 20 identifiant long privé ; 21. 21 description de chaîne privée ; 22. 22 heures doubles privées ; 23. 23 projets de cordes privés ; 24. 24 chaîne privée numéro de projet ; 25. 25 26. 26 @Id @GeneratedValue ( stratégie = GenerationType. AUTO ) 27. 27 public Long getId ( ) { 28. 28 id retour ; 29. 29 } 30. 30 setId public vide ( id long ) { 31. 31 ceci . id = id ; 32. 32 } 33. 33 34. 34 @Column ( name = "description" , longueur = 255 ) 35. 35 String publique getDescription ( ) { 36. 36 retour description ; 37. 37 } 38. 38 public void setDescription ( Description de la chaîne ) { 39. 39 ceci . description = description ; 40. 40 } 41. 41 42. 42 @Column ( name = "hours" ) 43. 43 public double getHours ( ) { 44. 44 heures de retour ; 45. 45 } 46. 46 publics vides setHours ( doubles heures ) { 47. 47 ceci . heures = heures ; 48. 48 } 49. 49 50. 50 @Column ( name = "project" ) 51. 51 public String getProject ( ) { 52. 52 projet de retour ; 53. 53 } 54. 54 public void setProject ( projet String ) { 55. 55 ceci . projet = projet ; 56. 56 } 57. 57 58. 58 @Column ( name = "numéro_projet" ) 59. 59 chaîne publique getProjectNumber ( ) { 60. 60 numéro de projet retourné ; 61. 61 } 62. 62 public void setProjectNumber ( String projectNumber ) { 63. 63 ceci . numéro de projet = numéro de projet ; 64. 64 } 65. 65 66. 66 @Override 67. 67 chaîne publique toString ( ) { 68. 68 Générateur StringBuilder = new StringBuilder ( ) ; 69. 69 70. 70 constructeur. append ( "TimeEntry {id =" + this . id ) ; 71. 71 constructeur. append ( ", description =" + this . description ) ; 72. 72 constructeur. append ( ", hours =" + this . hours ) ; 73. 73 constructeur. append ( ", projet =" + this . projet ) ; 74. 74 constructeur. append ( ", numéro_projet =" + ceci . numéro_projet + "}" ) ; 75. 75 76. 76 constructeur de retour . toString ( ) ; 77. 77 } 78. 78 79. 79 @Override 80. 80 publics égaux booléens ( Object o ) { 81. 81 si ( ceci == o ) { 82. 82 retourne vrai ; 83. 83 } 84. 84 85. 85 if ( ( o == null ) || getClass ( ) ! = O. GetClass ( ) ) { 86. 86 retourne faux ; 87. 87 } 88. 88 89. 89 Entrée TimeEntry = ( TimeEntry ) o ; 90. 90 91. 91 if ( description ! = Null ? 92. 92 ! la description. est égal à ( entrée. description ) : 93. 93 entrée. description ! = null ) { 94. 94 retourne faux ; 95. 95 } 96. 96 97. 97 if ( hours ! = Entry. Hours ) { return false ; } 98. 98 99. 99 si ( projet ! = Null ? 100. 100 ! projet. est égal à ( entrée. projet ) : entrée. projet ! = null ) { 101. 101 retourne faux ; 102. 102 } 103. 103 if ( numéro de projet ! = Null ? 104. 104 ! numéro de projet. égal à égal ( entrée. ProjectNumber ) : 105. 105 entrée. numéro de projet ! = null ) { 106. 106 retourne faux ; 107. 107 } 108. 108 109. 109 retourne vrai ; 110. 110 } 111. 111 112. 112 @Override 113. 113 public int hashCode ( ) { 114. 114 int résultat = 0 ; 115. 115 116. 116 résultat = ( description ! = Null ? Description. HashCode ( ) : 0 ) ; 117. 117 résultat = résultat * ( int ) heures ; 118. 118 résultat = résultat * ( projet ! = Null ? Projet. HashCode ( ) : 0 ) ; 119. 119 120. 120 résultat = résultat * 121. 121 ( numéro de projet ! = Null ? Numéro de projet. HashCode ( ) : 0 ) ; 122. 122 123. 123 résultat retourné ; 124. 124 } 125. 125 126. 126 }

Une chose qui devrait être immédiatement évidente est l'extension de BaseObject. Ceci est une AppFuse classe qui fait les toString(), equals() et les hashCode() méthodes abstraites développeurs obligent à les mettre en œuvre.

Pour une discussion approfondie sur le contrat des Object méthodes equals(), hashCode() veuillez consulter le chapitre suivant de l'ouvrage de Joshua Bloch, Effective Java .

À ce stade, vous connecter à la base de données et effectuer une opération show tables devrait donner les résultats suivants :

Maintenant que la TimeEntry.java classe d'entité a été créée, quelques étapes supplémentaires sont nécessaires pour que la table apparaisse dans la base de données.

1. Modifiez l’ hibernate.cfg.xml ajout de l’entrée suivante:

Veuillez noter que lorsque le projet est créé, il y a deux hibernate.cfg.xml fichiers, un dans une zone de test et un dans la zone principale. Assurez-vous de modifier le fichier dans le timeEntry/src/main/resources répertoire. 2. Générez le ddl approprié avec mvn test-compile hibernate3:hbm2ddl. Veuillez noter que cette tâche doit également mettre à jour la base de données. La liste résultante des tableaux montre maintenant ce qui suit, notez l’ajout de time_entry:

CONSTRUIRE UN DAO TIMEENTRY ET UNITTEST

Maintenant qu’une table existe pour conserver les entrées de temps, nous pouvons passer à la construction du dao. Il existe deux classes qui prennent en charge la fonctionnalité générique de crud, ce sont GenericDaoHibernate et UniversalDaoHibernate. La différence entre ceux-ci est que l’universel place la charge de la conversion sur le développeur, alors que le générique permet de spécifier un type paramétré via des génériques. Le TimeEntryDAO sera étendu à la GenericDaoHibernate classe. Il existe une série d'étapes à suivre pour rendre DAO disponible pour le reste de l'application, notamment :

1. Modifiez le applicationContext-dao.xml fichier en timeEntry/src/main/resources ajoutant le fragment xml suivant pour câbler le DAO pour l'injection basée sur le setter.

1. 3. 4. 2. Créez l'interface pour TimeEntryDao, illustrée ci-dessous.

1. 1 paquetage com.oci.jnb.timeEntry.dao ; 2. 2 3. 3 import com.oci.jnb.timeEntry.model.TimeEntry ; 4. 4 import java.util.List ; 5. 5 import org.appfuse.dao.GenericDao ; 6. 6 7. 7 / ** 8. 8 * @author rwithers 9. 9 * / 10. 10 interface publique TimeEntryDao étend GenericDao < TimeEntry, Long > { 11. 11 12. 12 / ** 13. 13 * Trouver une liste des entrées de temps par numéro de projet. 14. 14 * 15. 15 * @param projNumber 16. 16 * @retour 17. 17 * / 18. 18 publique Liste < TimeEntry > findByProjectNumber ( chaîne projNumber ) ; 19. 19 20. 20 } 3. Implémentez l'interface dans TimeEntryDaoImpl, illustrée ci-dessous:

1. 1 paquetage com.oci.jnb.timeEntry.dao.hibernate ; 2. 2 3. 3 import com.oci.jnb.dao.hibernate.GenericDaoHibernate ; 4. 4 import com.oci.jnb.timeEntry.dao.TimeEntryDao ; 5. 5 import com.oci.jnb.timeEntry.model.TimeEntry ; 6. 6 import java.util.List ; 7. 7 8. 8 / ** 9. 9 * Cette classe implémente TimeEntryDao et étend le 10. 10 * Classe GenericDaoHibernate. Le GenericDaoHibernate 11. 11 * class est une classe fournie par AppFuse qui prend en charge 12. 12 * fonctionnalité de base CRUD. Alors que le TimeEntryDao 13. 13 * interface expose les méthodes de récupération personnalisées comme dans 14. 14 * la méthode findByProjectNumber illustrée ci-dessous. 15. 15 * 16. 16 * @author rwithers 17. 17 * / 18. 18 publique classe TimeEntryDaoImpl 19. 19 extensions GenericDaoHibernate < TimeEntry, Long > 20. 20 implémente TimeEntryDao { 21. 21 22. 22 public TimeEntryDaoImpl ( ) { 23. 23 super ( classe TimeEntry ) ; 24. 24 } 25. 25 26. 26 / ** 27. 27 * Recherche une liste d'entrées de temps par numéro de projet. 28. 28 * 29. 29 * @param projNumber 30. 30 * @retour 31. 31 * / 32. 32 publique Liste < TimeEntry > findByProjectNumber ( chaîne projNumber ) { 33. 33 retourne getHibernateTemplate ( ) 34. 34 . find ( "de TimeEntry où numéro_projet =?" , numéro_proj ) ; 35. 35 } 36. 36 37. 37 } 4. Construisez le test unitaire nécessaire. À ce stade, il serait approprié de mentionner que TDD est suggéré dans les tutoriels. Il n'y a pas de raison que l'exemple ne puisse pas être développé TDD. Il vous suffit de définir le test unitaire présenté ci-dessous avant de définir l'interface et sa mise en oeuvre correspondante. Même en travaillant à travers les exemples de l'article, j'ai trouvé indispensable de disposer d'un test. Le test unitaire pour les classes ci-dessus devrait ressembler à :

1. 1 paquetage com.oci.jnb.timeEntry.dao ; 2. 2 3. 3 import com.oci.jnb.dao.BaseDaoTestCase ; 4. 4 import com.oci.jnb.timeEntry.model.TimeEntry ; 5. 5 import java.util.List ; 6. 6 import org.springframework.dao.DataAccessException ; 7. 7 8. 8 / ** 9. 9 * Cette classe de test étend BaseDaoTestCase qui est un 10. 10 * Classe AppFuse utilisée pour prendre en charge les tests DAO. Tout test 11. 11 * cas sont la méthode précédée du mot test en tout 12. 12 * minuscules. La fonctionnalité de printemps est amorcée 13. 13 * dans AbstractTransactionalDataSourceSpringContextTests 14. 14 * classe étendue par la BaseDaoTestCase. 15. 15 * 16. 16 * @author rwithers 17. 17 * / 18. 18 Classe publique TimeEntryDAOTest étend BaseDaoTestCase { 19. 19 20. 20 TimeEntryDao dao privé = null ; 21. 21 22. 22 / ** 23. 23 * Setter fourni pour l'injection de dépendance de printemps. 24. 24 * 25. 25 * @param timeEntryDao 26. 26 * / 27. 27 public void setTimeEntryDao ( TimeEntryDao timeEntryDao ) { 28. 28 dao = timeEntryDao ; 29. 29 } 30. 30 31. 31 / ** 32. 32 * Testez la méthode de recherche personnalisée. 33. 33 * / 34. 34 public void testFindEntryByProjectNumber ( ) { 35. 35 Liste < TimeEntry > entries = dao. findByProjectNumber ( "JNB2508" ) ; 36. 36 assertNotNull ( "entrées attendues, mais n'a reçu aucune entrée" ) ; 37. 37 assertTrue ( entrées. Taille ( ) > 0 ) ; 38. 38 } 39. 39 40. 40 / ** 41. 41 * Testez les fonctionnalités de base de CRUD. 42. 42 * 43. 43 * @throws java.lang.Exception 44. 44 * / 45. 45 public void testAddAndRemoveTimeEntry ( ) lève une exception { 46. 46 Entrée TimeEntry = new TimeEntry ( ) ; 47. 47 entrée. setDescription ( "Travailler sur des tests jnb." ) ; 48. 48 entrée. setHours ( 32.0d ) ; 49. 49 entrée. setProject ( "Bulletin mensuel de Jnb." ) ; 50. 50 entrées. setProjectNumber ( "JNB2509" ) ; 51. 51 52. 52 entrée = dao. enregistrer ( entrée ) ; 53. 53 affleurant ( ) ; 54. 54 55. 55 TimeEntry entry2 = dao. get ( entry. getId ( ) . longValue ( ) ) ; 56. 56 57. 57 assertEquals ( "JNB2509" , entry2. GetProjectNumber ( ) ) ; 58. 58 assertNotNull ( entry2. GetId ( ) ) ; 59. 59 60. 60 log. debug ( "enlever une entrée ..." ) ; 61. 61 62. 62 dao. remove ( entry2. getId ( ) ) ; 63. 63 chasse d'eau ( ) ; 64. 64 65. 65 essayer { 66. 66 dao. get ( entry2. getId ( ) ) ; 67. 67 échec ( "Personne trouvée dans la base de données" ) ; 68. 68 } catch ( DataAccessException dae ) { 69. 69 log. debug ( "Exception attendue:" + dae. getMessage ( ) ) ; 70. 70 assertNotNull ( dae ) ; 71. 71 } 72. 72 } 73. 73 74. 74 } Une exigence supplémentaire, avant d'exécuter le test unitaire ci-dessus, configurez certaines données de test à l'aide de dbunit. Ne vous inquiétez pas, vous avez probablement tout ce dont vous avez besoin. C’est l’un des principaux atouts de la combinaison Maven / AppFuse. Il extrait l’application shell fournie avec un certain nombre d’outils open source couramment utilisés. La seule chose à fournir est dans le fragment xml montré ci-dessous en tant que partie du sample-data.xmlfichier. Ce fichier se trouve dans le timeEntry/src/test/resourcesrépertoire. 1. < nom de la table = "heure_entrée" > 2. id 3. heures 4. description 5. projet 6. numéro_projet 7. 8. 1 9. 5.5 10. 11. 12. 13. JNB 14. JNB2508 15. 16.

5. Exécutez maintenant la commande mvn test ou mvn test -Dtest=TimeEntryDAOTestet recherchez les résultats.

CRÉER LE MANAGER

AppFuse fait référence aux objets métier en tant que "Managers". Les gestionnaires sont essentiellement des classes qui permettent de découpler les actions (dans struts2) prenant en charge l'interface Web de toute connaissance de la base de données. Les gestionnaires sont un bon endroit pour fournir une validation au niveau des données, et toute logique métier que l'application peut avoir besoin de prendre en charge. AppFuse fournit deux gestionnaires "prêts à l'emploi" qui prennent en charge les fonctionnalités CRUD de base, très similaires aux DAO. Ces gestionnaires sont GenericManager et UniversalManager, GenericManager permet le paramétrage via génériques alors que le UniversalManager repose sur la coulée explicite. Pour l' timeEntry ajout, TimeEntryManager il faudra étendre le GenericManager pour fournir des fonctionnalités supplémentaires avec une findByProjectNumber() méthode.

1. La première chose à faire est de modifier le applicationContext-service.xml fichier en ajoutant la ligne indiquée ci-dessous. Cette entrée spécifie l'injection de constructeur pour injecter l'implémentation dao dans le gestionnaire.

1. 3. 4. La nouvelle entrée ci-dessus permettra de faire TimeEntryManagerconnaître le TimeEntryDAO. Si la fonctionnalité crud est tout ce qui est nécessaire, nous allons créer des paramètres qui prendront une interface GenericManageret nous aurons terminé. Cependant, étant donné que TimeEntryManagernous étendrons les fonctionnalités avec une findByroutine personnalisée, nous devons continuer. 2. La prochaine étape consiste à créer l'interface du gestionnaire. La source est indiquée ci-dessous.

1. 1 paquetage com.oci.jnb.timeEntry.service ; 2. 2 3. 3 import com.oci.jnb.service.GenericManager ; 4. 4 import com.oci.jnb.timeEntry.model.TimeEntry ; 5. 5 import java.util.List ; 6. 6 7. 7 / ** 8. 8 * @author rwithers 9. 9 * / 10. 10 interface publique TimeEntryManager 11. 11 étend GenericManager < TimeEntry, Long > { 12. 12 13. 13 / ** 14. 14 * Trouver une liste des entrées de temps par numéro de projet. 15. 15 * 16. 16 * @param projNumber 17. 17 * @retour 18. 18 * / 19. 19 publique Liste < TimeEntry > findByProjectNumber ( chaîne projNumber ) ; 20. 20 21. 21 } 22. S'il vous plaît noter que cette interface étend le GenericManagerqui fournit un conduit à la fonctionnalité de base CRUD via l'héritage standard. Il nous faudra étendre la GenericManagerImplclasse dans la mise en œuvre, les deux GenericManageret GenericManagerImplsont fournis par le cadre AppFuse. 3. Maintenant, implémentez la findByméthode dans la classe d'implémentation personnalisée ci-dessous.

1. 1 paquetage com.oci.jnb.timeEntry.service.impl ; 2. 2 3. 3 import com.oci.jnb.service.impl.GenericManagerImpl ; 4. 4 import com.oci.jnb.timeEntry.dao.TimeEntryDao ; 5. 5 import com.oci.jnb.timeEntry.model.TimeEntry ; 6. 6 import com.oci.jnb.timeEntry.service.TimeEntryManager ; 7. 7 import java.util.List ; 8. 8 9. 9 / ** 10. 10 * Cette classe expose les méthodes de service ou de couche de gestion. 11. 11 * / 12. 12 classe publique TimeEntryManagerImpl 13. 13 étend GenericManagerImpl < TimeEntry, Long > 14. 14 implémente TimeEntryManager { 15. 15 16. 16 TimeEntryDao dao ; 17. 17 18. 18 public TimeEntryManagerImpl ( TimeEntryDao dao ) { 19. 19 super ( dao ) ; 20. 20 ceci . dao = dao ; 21. 21 } 22. 22 23. 23 publique Liste < TimeEntry > findByProjectNumber ( chaîne projNumber ) { 24. 24 retour dao. findByProjectNumber ( projNumber ) ; 25. 25 } 26. 26 27. 27 } 28. 4. Il convient de noter que l'injection de constructeur est fournie via l'ajout au applicationContext.xml fichier ci-dessus à l'aide du conteneur IoC à ressort. Le test unitaire pour tout cela devrait ressembler au code suivant. À l'heure actuelle, nous exposons uniquement une méthode personnalisée à laquelle aucune logique métier n'est associée. Cependant, le test unitaire que nous ajoutons est un paramètre fictif pertinent pour les ajouts futurs, ainsi que pour tout changement apporté à la logique de la méthode ou aux validations.

1. 1 paquetage com.oci.jnb.timeEntry.service.impl ; 2. 2 3. 3 import com.oci.jnb.service.impl.BaseManagerMockTestCase ; 4. 4 import com.oci.jnb.timeEntry.dao.TimeEntryDao ; 5. 5 import com.oci.jnb.timeEntry.model.TimeEntry ; 6. 6 import java.util.ArrayList ; 7. 7 import java.util.List ; 8. 8 import org.jmock.Mock ; 9. 9 10. dix 11. 11 / ** 12. 12 * @author rwithers 13. 13 * / 14. 14 classe publique TimeEntryManagerImplTest 15. 15 étend BaseManagerMockTestCase { 16. 16 17. 17 gestionnaire privé TimeEntryManagerImpl = null ; 18. 18 privé Mock Dao = null ; 19. 19 entrées privées TimeEntry = null ; 20. 20 21. 21 @ Override 22. 22 protégé vide setUp ( ) jette Exception { 23. 23 dao = new Mock ( TimeEntryDao. Class ) ; 24. 24 manager = 25. 25 nouveaux TimeEntryManagerImpl ( ( TimeEntryDao ) dao. Proxy ( ) ) ; 26. 26 } 27. 27 28. 28 @Override 29. 29 void protected tearDown ( ) lève une exception { 30. 30 manager = null ; 31. 31 } 32. 32 33. 33 public void testGetTimeEntry ( ) { 34. 34 log. debug ( "test de getTimeEntry" ) ; 35. 35 36. 36 id long = 7L ; 37. 37 entry = new TimeEntry ( ) ; 38. 38 39. 39 // définit le comportement attendu sur dao 40. 40 dao. attend ( une fois ( ) ) . méthode ( "get" ) 41. 41 . avec ( eq ( id ) ) 42. 42 . will ( returnValue ( entry ) ) ; 43. 43 44. 44 TimeEntry result = manager. get ( id ) ; 45. 45 assertSame ( entrée, résultat ) ; 46. 46 } 47. 47 48. 48 public void testGetEntries ( ) { 49. 49 log. debug ( "test de getEntries" ) ; 50. 50 51. 51 Listes d' entrées = new ArrayList ( ) ; 52. 52 53. 53 // définit le comportement attendu sur dao 54. 54 dao. attend ( une fois ( ) ) . méthode ( "getAll" ) 55. 55 . will ( returnValue ( entrées ) ) ; 56. 56 57. 57 Résultat liste = manager. getAll ( ) ; 58. 58 assertSame ( entrées, résultat ) ; 59. 59 } 60. 60 61. 61 public void testFindByProjectNumber ( ) { 62. 62 log. debug ( "test findByProjectNumber" ) ; 63. 63 64. 64 Listes d' entrées = new ArrayList ( ) ; 65. 65 String projectNumber = "JNB2509" ; 66. 66 67. 67 // définit le comportement attendu sur dao 68. 68 dao. attend ( une fois ( ) ) . méthode ( "findByProjectNumber" ) 69. 69 . avec ( eq ( numéro de projet ) ) 70. 70 . will ( returnValue ( entrées ) ) ; 71. 71 72. 72 Résultat liste = 73. 73 manager. findByProjectNumber ( numéro de projet ) ; 74. 74 assertSame ( entrées, résultat ) ; 75. 75 } 76. 76 77. 77 public void testSaveTimeEntry ( ) { 78. 78 log. debug ( "testing saveTimeEntry" ) ; 79. 79 80. 80 entry = new TimeEntry ( ) ; 81. 81 82. 82 // définit le comportement attendu sur dao 83. 83 dao. attend ( une fois ( ) ) . méthode ( "save" ) 84. 84 . avec ( pareil ( entrée ) ) 85. 85 . will ( returnValue ( entry ) ) ; 86. 86 87. Directeur 87 . enregistrer ( entrée ) ; 88. 88 } 89. 89 90. 90 public void testRemoveTimeEntry ( ) { 91. 91 log. debug ( "test removeTimeEntry" ) ; 92. 92 93. 93 id long = 11L ; 94. 94 entry = new TimeEntry ( ) ; 95. 95 96. 96 // définit le comportement attendu sur dao 97. 97 dao. attend ( une fois ( ) ) . méthode ( "remove" ) 98. 98 . avec ( eq ( id ) ) 99. 99 . isVoid ( ) ; 100. 100 101. Gestionnaire 101 . remove ( id ) ; 102. 102 } 103. 103 104. 104 }

NIVEAU WEB STRUTS 2

Pour construire complètement le niveau Web struts, commencez par assembler quelques JSP. L'un de ces fichiers JSP servira à répertorier les entrées de temps et un autre à la modification. De plus, une classe d'action et un test unitaire correspondant sont nécessaires. Cependant, pour être un peu brèves, les validations seront ignorées. Veuillez noter que si la validation est une étape importante pour une application Web complète, elle sera "un exercice laissé au lecteur". Pour commencer à construire le test d’action, il couvre les tests d’ajout, de sauvegarde, de suppression et de listage des entrées de temps. Voir ci-dessous pour la liste :

1. 1 paquetage com.oci.jnb.timeEntry.actions ; 2. 2 3. 3 import com.oci.jnb.service.GenericManager ; 4. 4 import com.oci.jnb.timeEntry.model.TimeEntry ; 5. 5 import com.oci.jnb.webapp.action.BaseActionTestCase ; 6. 6 import com.opensymphony.xwork2.ActionSupport ; 7. 7 import org.apache.struts2.ServletActionContext ; 8. 8 import org.springframework.mock.web.MockHttpServletRequest ; 9. 9 10. 10 / ** 11. 11 * @author rwithers 12. 12 * / 13. 13 Classe publique TimeEntryActionTest étend BaseActionTestCase { 14. 14 15. 15 action privée TimeEntryAction ; 16. 16 17. 17 @Override 18. 18 void protégé onSetUpBeforeTransaction ( ) lève Exception { 19. 19 super . onSetUpBeforeTransaction ( ) ; 20. 20 action = new TimeEntryAction ( ) ; 21. 21 GenericManager timeEntryManager = 22. 22 ( GenericManager ) applicationContext. getBean ( "timeEntryManager" ) ; 23. 23 action. setTimeEntryManager ( timeEntryManager ) ; 24. 24 25. 25 entrée TimeEntry = new TimeEntry ( ) ; 26. 26 entrée. setDescription ( "Travail sur les actions JNB." ) ; 27. 27 entrée. setHours ( 32.0d ) ; 28. 28 entrées. setProject ( "Nouvelles brèves annuelles de JNB java." ) ; 29. 29 entrée. setProjectNumber ( "JNB2510" ) ; 30. 30 timeEntryManager. enregistrer ( entrée ) ; 31. 31 } 32. 32 33. 33 public void testSearch ( ) lève une exception { 34. 34 assertEquals ( action. List ( ) , ActionSupport. SUCCESS ) ; 35. 35 assertTrue ( action. GetEntries ( ) . Size ( ) > = 1 ) ; 36. 36 } 37. 37 38. 38 testEdit ( ) public void jette une exception { 39. 39 log. debug ( "testing edit ..." ) ; 40. 40 action. setId ( 1L ) ; 41. 41 assertNull ( action. GetEntry ( ) ) ; 42. 42 assertEquals ( "success" , action. Edit ( ) ) ; 43. 43 assertNotNull ( action. GetEntry ( ) ) ; 44. 44 assertFalse ( action. HasActionErrors ( ) ) ; 45. 45 } 46. 46 47. 47 public void testSave ( ) lève Exception { 48. 48 demande MockHttpServletRequest = new MockHttpServletRequest ( ) ; 49. 49 ServletActionContext. setRequest ( demande ) ; 50. 50 action. setId ( 1L ) ; 51. 51 assertEquals ( "success" , action. Edit ( ) ) ; 52. 52 assertNotNull ( action. GetEntry ( ) ) ; 53. 53 54. 54 // met à jour le nom de famille et sauvegarde 55. 55 action. getEntry ( ) . setDescription ( "Description mise à jour" ) ; 56. 56 assertEquals ( "input" , action. Save ( ) ) ; 57. 57 assertEquals ( "Description mise à jour" , action. GetEntry ( ) . GetDescription ( ) ) ; 58. 58 assertFalse ( action. HasActionErrors ( ) ) ; 59. 59 assertFalse ( action. HasFieldErrors ( ) ) ; 60. 60 assertNotNull ( request. GetSession ( ) . GetAttribute ( "messages" ) ) ; 61. 61 } 62. 62 63. 63 public void testRemove ( ) lève une exception { 64. 64 demande MockHttpServletRequest = new MockHttpServletRequest ( ) ; 65. 65 ServletActionContext. setRequest ( demande ) ; 66. 66 action. setDelete ( "" ) ; 67. 67 Entrée TimeEntry = new TimeEntry ( ) ; 68. 68 entrée. setId ( new Long ( 2L ) ) ; 69. 69 action. setEntry ( entrée ) ; 70. 70 assertEquals ( "erreur" , action. Delete ( ) ) ; 71. 71 assertNull ( request. GetSession ( ) . GetAttribute ( "messages" ) ) ; 72. 72 } 73. 73 74. 74 }

Maintenant, afin d'obtenir l’ActionTest construction de la classe, ajoutez la TimeEntryAction.java classe. Cette classe étend une BaseAction classe fournie dans le projet AppFuse basic struts2. Il BaseAction étend à son tour l’ActionSupport classe struts 2 qui fournit des fonctionnalités pour la validation, la gestion des messages d'erreur, les transferts, etc. Les méthodes de cette classe d'actions sont associées à des pages Web via le struts.xml fichier de configuration. La version struts2 de ce fichier est une version plus fine et plus fine du struts-config.xml fichier que vous connaissez peut-être à partir de struts 1.x days. Une fois la classe d'implémentation ajoutée, lancez les tests. Cela peut être fait via l'mvn test objectif ou pour un test unitaire spécifique mvn test -Dtest=TimeEntryActionTest.

1. 1 paquetage com.oci.jnb.timeEntry.actions ; 2. 2 3. 3 import com.oci.jnb.service.GenericManager ; 4. 4 import com.oci.jnb.timeEntry.model.TimeEntry ; 5. 5 import com.oci.jnb.webapp.action.BaseAction ; 6. 6 import java.util.List ; 7. 7 8. 8 / ** 9. 9 * @author rwithers 10. dix */ 11. 11 public class TimeEntryAction étend BaseAction { 12. 12 13. 13 privé statique finale Chaîne TIME_ENTRY_ID = "time_entry_id" ; 14. 14 15. 15 private GenericManager < TimeEntry, Long > timeEntryMgr ; 16. 16 entrées privées de la liste ; 17. 17 entrées privées TimeEntry ; 18. 18 identifiant long privé ; 19. 19 20. 20 public void setTimeEntryManager ( 21. 21 GenericManager < TimeEntry, long > manager ) { 22. 22 timeEntryMgr = manager ; 23. 23 } 24. 24 25. 25 liste publique getEntries ( ) { 26. 26 entrées de retour ; 27. 27 } 28. 28 29. 29 liste de chaînes publique ( ) { 30. 30 entrées = timeEntryMgr. getAll ( ) ; 31. 31 retour SUCCESS ; 32. 32 } 33. 33 34. 34 setId public vide ( id long ) { 35. 35 ceci . id = id ; 36. 36 log. debug ( "Paramétrer id (Long) à:" + id ) ; 37. 37 getSession ( ) . setAttribute ( TIME_ENTRY_ID, id ) ; 38. 38 getRequest ( ) . setAttribute ( TIME_ENTRY_ID, id ) ; 39. 39 } 40. 40 41. 41 public TimeEntry getEntry ( ) { 42. 42 entrée de retour ; 43. 43 } 44. 44 45. 45 public void setEntry ( entrée TimeEntry ) { 46. 46 ceci . entry = entry ; 47. 47 } 48. 48 49. 49 chaîne publique delete ( ) { 50. Résultat de 50 cordes ; 51. 51 Long localId = null ; 52. 52 53. 53 localId = ( Long ) getSession ( ) . getAttribute ( TIME_ENTRY_ID ) ; 54. 54 si ( localId == null ) { 55. 55 localId = 56. 56 ( Long ) getRequest ( ) . getAttribute ( TIME_ENTRY_ID ) ; 57. 57 } 58. 58 if ( localId == null ) { 59. 59 résultat = ERREUR ; 60. 60 } else { 61. 61 timeEntryMgr. remove ( localId ) ; 62. 62 saveMessage ( getText ( "timeEntry.deleted" ) ) ; 63. 63 résultat = SUCCESS ; 64. 64 } 65. 65 66. 66 résultat retourné ; 67. 67 } 68. 68 69. 69 chaîne publique edit ( ) { 70. 70 Long localId = null ; 71. 71 72. 72 localId = ( Long ) getSession ( ) . getAttribute ( TIME_ENTRY_ID ) ; 73. 73 74. 74 if ( localId ! = Null ) { 75. 75 entrée = timeEntryMgr. get ( localId ) ; 76. 76 } else { 77. 77 entry = new TimeEntry ( ) ; 78. 78 } 79. 79 80. 80 retour de SUCCESS ; 81. 81 } 82. 82 83. 83 chaîne publique add ( ) { 84. 84 entry = new TimeEntry ( ) ; 85. 85 retour SUCCESS ; 86. 86 } 87. 87 88. 88 chaîne publique save ( ) lève Exception { 89. 89 Long localId = null ; 90. 90 91. 91 if ( cancel ! = Null ) { 92. 92 retour "annuler" ; 93. 93 } 94. 94 95. 95 if ( delete ! = Null ) { 96. 96 return delete ( ) ; 97. 97 } 98. 98 99. 99 localId = ( Long ) getSession ( ) . getAttribute ( TIME_ENTRY_ID ) ; 100. 100 101. 101 boolean isNew = ( localId == null ) ; 102. 102 103. 103 si ( ! IsNew ) { 104. 104 entrée. setId ( localId ) ; 105. 105 log. debug ( "La valeur de l'identifiant de session stocké est:" + identifiant local ) ; 106. 106 } else { 107. 107 log. debug ( "La valeur de l'identifiant de session stocké est: null" ) ; 108. 108 } 109. 109 110. 110 log. debug ( "Tentative de sauvegarde de l'entrée:" + entrée ) ; 111. 111 112. 112 entrée = timeEntryMgr. enregistrer ( entrée ) ; 113. 113 114. 114 log. debug ( "Entrée enregistrée:" + entrée ) ; 115. 115 116. 116 String key = ( isNew ) ? "timeEntry.added" : "timeEntry.updated" ; 117. 117 saveMessage ( getText ( key ) ) ; 118. 118 119. 119 si ( ! IsNew ) { 120. 120 retour INPUT ; 121. 121 } else { 122. 122 return SUCCESS ; 123. 123 } 124. 124 } 125. 125 }

Ajoutez maintenant les JSP pour afficher une vue de la fonctionnalité de base CRUD de l’TimeEntry entité. Pour ce faire, ajoutez deux jsp qui prennent en charge toutes nos fonctionnalités nécessaires, à timeEntryList.jsp et à timeEntryForm.jsp. Commençant par timeEntryList ci-dessous :

1. 1 <% @ include file = "/common/taglibs.jsp" % > 2. 2 3. 3 4. 4 5. 5 7. 7 8. 8 9. 9 10. dix 11. 11 < type d' entrée = "bouton" 12. 12 style = "margin-right: 5px" 13. 13 onclick = "location.href = ' '" 14. 14 valeur = " " /> 15. 15 16. 16 < type d' entrée = "bouton" 17. 17 onclick = "location.href = ' '" 18. 18 valeur = " " /> 19. 19 20. 20 21. 21 22. 22 23. 23 24. 24 25. 25 26. 26 32. 32 33. 33 39. 39 40. 40 43. 43 44. 44 47. 47 48. 48 51. 51 54. 54 55. 55 58. 58 61. 61 64. 64 65. 65 66. 66 67. 67 68. 68 69. 69