Services – TDD mit Arquillian, JSF2, JEE6, CDI

Mittwoch, 29. April 2015 von  
unter Fachartikel Architektur

 Die Motivation

Single Page Web-Applikationen (SPAs) haben nicht nur wegen Ihrer guten Laufzeit-Performance in JEE Web-Applikationen zunehmend an Bedeutung gewonnen. Diese können durchaus auch mit JSF 2 Implementierungen, wie dem RichFaces 4 umgesetzt werden und müssen nicht immer responsiv funktionieren. Primefaces Mobile Implementierungen können also verwendet werden, wenn eine mobile Version erforderlich ist, ansonsten ist eine RichFaces 4 Open Source Version mit der ihr eigenen Template-Technologie ebenfalls eine gute Lösung, die für Tablets und größere Displays auch super funktioniert.

Über Test-getriebene Entwicklung von SPAs mit responsivem Frontend (mittels Backbone, jQuery, QUnit, Selenium, etc.) gibt es in diesem Blog bereits einen Eintrag hier und ich möchte bei dieser Gelegenheit auf das sehr interessante, erfolgreiche und empfehlenswerte Seminar TDD mit Java von Binaris hinweisen

hier und hier.

In diesem sechsten Blog-Eintrag zum Thema „Test-driven Development mit SPAs“ soll ausser den Beispielen zu Java/JEE-implementierten integrativen EJB Backend Service-Tests mittels dem Arquillian-Testframework auch eine simple Grusskarten Applikation implementiert werden, welche mittels JSF2 (RichFaces 4) funktioniert und eine Grusskarten-Mail mit dem Link auf ein Grusskartenbild versendet (Auswahl aus dem ‘Primefaces Gallerie‘ Komponenten-Beispiel hier). Zur Verbesserung der Usability kommen dabei HTML5 und CSS3 (Transitionen) zum Einsatz, ohne jedoch auf Animationen mit jQuery zurückzugreifen.

TDD der Beispiel Applikation

Wie zusätzliche Selenium-Tests mit dem Selenium FireFox-PlugIn durchgeführt und diese Tests gespeichert werden können, um mit solchen integrativen, automatisierten Tests ein paar Grusskarten zu versenden, ist bereits in diesem Blog-Eintrag hier beschrieben, um auch später jederzeit die korrekte Funktionsweise der Eingabe-Aktionen und Link-Aufrufe der SPA Applikation automatisiert über die erstellte Selenium Test-Suite verifizieren zu können.

Die Technologien:

Die Beispiel-Applikation ‘grusskarten‘ verwendet im Frontend JSF2 (RichFaces 4) und HTML5-/CSS3, sowie die .xhtml-Template Technologie und für das JAX-RS Service-Backend Java EE (Stateless EJB 3.1 und JEE 6 mittels CDI) auf dem ‘JBoss AS 7.4‘ mit der Produktbezeichnung ‘JBoss EAP 6.3‘.

Für die Weiterentwicklung, den Build und das Deployment des Projekts ist mindestens Java 6 und Maven 3 erforderlich. Als Entwicklungsumgebung wurde Eclipse 4.3 (Kepler) in Form des “JBoss Developer Studios 7.1“ mit Java 7 verwendet und die FireFox WebDeveloper IDE/Tools.

Hier die verwendete pom.xml (mit der optionalen Primefaces Dependency):

<?xml version=“1.0″ encoding=“UTF-8″?>
<project xmlns=“http://maven.apache.org/POM/4.0.0″ xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“
   xsi:schemaLocation=“http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd“>
   <modelVersion>4.0.0</modelVersion>
 
   <groupId>de.binaris.grusskarten</groupId>
   <artifactId>grusskarten</artifactId>
   <version>1.0.0-SNAPSHOT</version>
   <packaging>war</packaging>
   <name>Binaris Greetingcards</name>
   <description>A starter Java EE 6 webapp project for use on JBoss AS 7 / EAP 6, generated from the jboss-javaee6-webapp archetype</description>
 
                <url>http://repository.jboss.org/nexus/content/groups/public</url>
   <properties>
       <!– Explicit declaration of source encoding eliminates the following
           message: –>
       <!– [WARNING] Using platform encoding (UTF-8 actually) to copy filtered
           resources, i.e. build is platform dependent! –>
       <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
       <!– JBoss dependency versions –>
       <version.org.jboss.as.plugins.maven.plugin>7.2.Final</version.org.jboss.as.plugins.maven.plugin>
       <!– Define the version of the JBoss BOMs we want to import. The
           JBoss BOMs specify tested stacks. –>
       <version.org.jboss.bom>1.0.0.Final</version.org.jboss.bom>
       <!– Alternatively, comment out the above line, and un-comment the
           line below to use version 1.0.0.Final-redhat-1 which is a release certified
           to work with JBoss EAP 6. It requires you have access to the JBoss EAP 6
           maven repository. –>
       <!– <version.org.jboss.bom>1.0.0.Final-redhat-1</version.org.jboss.bom>> –>
 
       <!– other plugin versions –>
       <version.compiler.plugin>2.3.1</version.compiler.plugin>
       <version.surefire.plugin>2.4.3</version.surefire.plugin>
       <version.war.plugin>2.1.1</version.war.plugin>
 
       <!– maven-compiler-plugin –>
       <maven.compiler.target>1.6</maven.compiler.target>
       <maven.compiler.source>1.6</maven.compiler.source>
   </properties>
 
   <dependencyManagement>
       <dependencies>
           <!– JBoss distributes a complete set of Java EE 6 APIs including
               a Bill of Materials (BOM). A BOM specifies the versions of a „stack“ (or
               a collection) of artifacts. We use this here so that we always get the correct
               versions of artifacts. Here we use the jboss-javaee-6.0-with-tools stack
               (you can read this as the JBoss stack of the Java EE 6 APIs, with some extras
               tools for your project, such as Arquillian for testing) and the jboss-javaee-6.0-with-hibernate
               stack you can read this as the JBoss stack of the Java EE 6 APIs, with extras
               from the Hibernate department of projects) –>
           <dependency>
               <groupId>org.jboss.bom</groupId>
               <artifactId>jboss-javaee-6.0-with-tools</artifactId>
               <version>${version.org.jboss.bom}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <dependency>
               <groupId>org.jboss.bom</groupId>
               <artifactId>jboss-javaee-6.0-with-hibernate</artifactId>
               <version>${version.org.jboss.bom}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
   </dependencyManagement>
  
   <dependencies>
       <!– First declare the APIs we depend on and need for compilation.
           All of them are provided by JBoss AS 7 –>
       <dependency>
           <groupId>javax.enterprise</groupId>
           <artifactId>cdi-api</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.spec.javax.annotation</groupId>
           <artifactId>jboss-annotations-api_1.1_spec</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.spec.javax.ws.rs</groupId>
           <artifactId>jboss-jaxrs-api_1.1_spec</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.hibernate.javax.persistence</groupId>
           <artifactId>hibernate-jpa-2.0-api</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.spec.javax.ejb</groupId>
           <artifactId>jboss-ejb-api_3.1_spec</artifactId>
           <scope>provided</scope>
       </dependency>
       <dependency>
           <groupId>org.hibernate</groupId>
           <artifactId>hibernate-validator</artifactId>
           <scope>provided</scope>
           <exclusions>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-api</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
       <!– Import the JSF API, we use provided scope as the API is included
             in JBoss AS 7 –>
       <dependency>
           <groupId>org.jboss.spec.javax.faces</groupId>
           <artifactId>jboss-jsf-api_2.1_spec</artifactId>
          <scope>provided</scope>
       </dependency>
       <!– Annotation processor to generate the JPA 2.0 metamodel classes
             for typesafe criteria queries –>
       <dependency>
           <groupId>org.hibernate</groupId>
           <artifactId>hibernate-jpamodelgen</artifactId>
           <scope>provided</scope>
       </dependency>
       <!– Annotation processor to raise compilation errors whenever
             constraint annotations are incorrectly used. –>
       <dependency>
           <groupId>org.hibernate</groupId>
           <artifactId>hibernate-validator-annotation-processor</artifactId>
           <scope>provided</scope>
       </dependency>
       <!– Needed for running tests (you may also use TestNG) –>
       <dependency>
           <groupId>junit</groupId>
           <artifactId>junit</artifactId>
           <scope>test</scope>
       </dependency>
       <!– Arquillian allows you to test enterprise code such as EJBs and
           Transactional(JTA) JPA from JUnit/TestNG –>
       <dependency>
           <groupId>org.jboss.arquillian.junit</groupId>
           <artifactId>arquillian-junit-container</artifactId>
           <scope>test</scope>
       </dependency>
       <dependency>
           <groupId>org.jboss.arquillian.protocol</groupId>
           <artifactId>arquillian-protocol-servlet</artifactId>
           <scope>test</scope>
       </dependency>
                               <dependency>
                                               <groupId>javax.mail</groupId>
                                               <artifactId>mail</artifactId>
                                               <version>1.4.4</version>
                               </dependency>
                               <dependency>
                                               <groupId>javax.mail</groupId>
                                               <artifactId>javax.mail-api</artifactId>
                                               <version>1.5.2</version>
                               </dependency>
                               <dependency>
                                               <groupId>org.primefaces</groupId>
                                               <artifactId>primefaces</artifactId>
                                               <version>4.0</version>
                               </dependency>
   </dependencies>
 
   <build>
       <!– Maven will append the version to the finalName (which is the
           name given to the generated war, and hence the context root) –>
       <finalName>${project.artifactId}</finalName>
       <plugins>
           <!– Compiler plugin enforces Java 1.6 compatibility and activates
               annotation processors –>
           <plugin>
               <artifactId>maven-compiler-plugin</artifactId>
               <version>${version.compiler.plugin}</version>
               <configuration>
                   <source>${maven.compiler.source}</source>
                   <target>${maven.compiler.target}</target>
                </configuration>
           </plugin>
           <plugin>
               <artifactId>maven-war-plugin</artifactId>
               <version>${version.war.plugin}</version>
               <configuration>
                   <!– Java EE 6 doesn’t require web.xml, Maven needs to know –>
                   <failOnMissingWebXml>false</failOnMissingWebXml>
               </configuration>
           </plugin>
           <!– To use, run: mvn package jboss-as:deploy –>
           <plugin>
               <groupId>org.jboss.as.plugins</groupId>
               <artifactId>jboss-as-maven-plugin</artifactId>
               <version>${version.org.jboss.as.plugins.maven.plugin}</version>
           </plugin>
       </plugins>
   </build>
 
   <profiles>
       <profile>
           <!– The default profile skips all tests, though you can tune
                 it to run just unit tests based on a custom pattern –>
           <id>default</id>
           <activation>
               <activeByDefault>true</activeByDefault>
           </activation>
           <build>
               <plugins>
                   <plugin>
                       <artifactId>maven-surefire-plugin</artifactId>
                      <version>${version.surefire.plugin}</version>
                       <configuration>
                           <skip>true</skip>
                       </configuration>
                   </plugin>
               </plugins>
           </build>
       </profile>
       <profile>
           <!– An optional Arquillian testing profile that executes tests
                                                               the profile will start a new JBoss AS instance, execute
               the test, shut it down when done –>
            <!– Run with: mvn clean test -Parq-jbossas-managed –>
           <id>arq-jbossas-managed</id>
           <dependencies>
               <dependency>
                   <groupId>org.jboss.as</groupId>
                   <artifactId>jboss-as-arquillian-container-managed</artifactId>
                   <scope>test</scope>
               </dependency>
           </dependencies>
       </profile>
       <profile>
           <!– An optional Arquillian testing profile that executes tests
                in a remote JBoss AS instance –>
           <!– Run with: mvn clean test -Parq-jbossas-remote –>
           <id>arq-jbossas-remote</id>
           <dependencies>
               <dependency>
                   <groupId>org.jboss.as</groupId>
                   <artifactId>jboss-as-arquillian-container-remote</artifactId>
                   <scope>test</scope>
               </dependency>
           </dependencies>
       </profile>
   </profiles>
   <repositories>
       <repository>
           <id>jboss</id>
           <name>JBoss Release Repository</name>
           <url>http://repository.jboss.org/maven2</url>
       </repository>
       <repository>
                       <id>jboss-snapshot</id>
                       <name>JBoss Maven Repo</name>
                       <url>http://snapshots.jboss.org/maven2</url>
                   </repository>
   </repositories>
</project>

Über CDI und JavaEE gibt es bereits Blog-Einträge in diesem Blog hier und hier. Die Aktivierung von CDI erfolgt, wie beschrieben, mittels beans.xml im /WEB-INF Verzeichnis.

Die Architektur:

a) Frontend:

Das JSF2 Servlet wird über die folgende faces-config.xml aktiviert:

<?xml version=„1.0“ encoding=„UTF-8“?>
<!– This file is optional for any extra configuration. –>
<faces-config version=„2.0“ xmlns=„http://java.sun.com/xml/ns/javaee“
   xmlns:xsi=„http://www.w3.org/2001/XMLSchema-instance“
   xsi:schemaLocation=
       http://java.sun.com/xml/ns/javaee
       http://java.sun.com/xml/ns/javaee/web-facesconfig_2_0.xsd“>
   <!– This descriptor activates the JSF 2.0 Servlet –>
   <!– Navigation rules go here. Using CDI for creating @Named annotated managed beans. –>
   <!– No vavigation rules required for a SPA with only one site –>
</faces-config>

Der Aufruf erfolgt über einen Redirect in der index.html:

<!– Plain HTML page redirecting to index.jsf –>
<html>
<head>
<meta http-equiv=„Refresh“ content=„0; URL=index.jsf“>
</head>
</html>

Das Template für die einzige SPA-Seite befindet sich unter /WEB-INF/templates/template.xhtml:

<!DOCTYPE html PUBLIC „-//W3C//DTD XHTML 1.0 Strict//EN“
   „http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd“>
<html xmlns=„http://www.w3.org/1999/xhtml“
   xmlns:h=„http://java.sun.com/jsf/html“
   xmlns:ui=„http://java.sun.com/jsf/facelets“>
<h:head>
   <title>Greetingcards</title>
   <meta http-equiv=„Content-Type“ content=„text/html; charset=utf-8“ />
   <h:outputStylesheet name=„css/screen.css“ />
</h:head>
<h:body>
   <div id=„container“>
       <div id=„content“>
           <ui:insert name=„content“>
           </ui:insert>
       </div>
       <div id=„aside“>
           <p>Learn more about JBoss Enterprise Application Platform 6.</p>
           <ul>
               <li><a href=„https://access.redhat.com/knowledge/docs/JBoss_Enterprise_Application_Platform/“>Documentation</a></li>
               <li><a href=„http://red.ht/jbeap-6“>Product Information</a></li>
           </ul>
           <p>Learn more about JBoss AS 7.</p>
           <ul>
               <li><a
                   href=„http://www.jboss.org/jdf/quickstarts/jboss-as-quickstart/guide/Introduction“>Getting
                       Started Developing Applications Guide</a></li>
               <li><a href=„http://jboss.org/jbossas“>Community
                       Project Information</a></li>
           </ul>
           <p>Learn more about TDD with Java <a href=„http://binaris-informatik.de/?p=2275“>here</a> and <a href=„http://www.binaris-education.com/?portfolio=test-driven-development-mit-java“>here</a></p>
       </div>
       <div id=„footer“>
           <p>
               This project was generated from a Maven archetype from JBoss.<br />
           </p>
       </div>
   </div>
</h:body>
</html>

Die einzige Seite index.xhtml der SPA wird im div der id “content“ per <ui:insert>-Tag integriert:

<?xml version=“1.0″ encoding=“UTF-8″?>
<ui:composition xmlns=“http://www.w3.org/1999/xhtml“
   xmlns:ui=“http://java.sun.com/jsf/facelets“
   xmlns:f=“http://java.sun.com/jsf/core“
   xmlns:h=“http://java.sun.com/jsf/html“
   template=“/WEB-INF/templates/template.xhtml“>
   <ui:define name=“content“>
       <h1>Welcome to Greetingcards!</h1>
 
       <div>
           <p>Greetingcards – a Java EE 6 Enterprise Application.</p>
           <h3>Application runs on:</h3>
           <img src=“resources/gfx/dualbrand_as7eap.png“ />
       </div>
 
       <h:form id=“reg“>
           <h2>Recipient and Greetingcard Registration</h2><br />
           <h:panelGrid columns=“7″ columnClasses=“titleCell“>
               <h:outputLabel for=“name“ value=“Name:“ />
               <h:inputText id=“name“ value=“#{newRecipient.name}“ />
               <h:message for=“name“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
 
               <h:outputLabel for=“email“ value=“Email:“ />

               <h:inputText id=“email“ value=“#{newRecipient.email}“ />
               <h:message for=“email“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
 
               <h:outputLabel for=“message“ value=“Message:“ />
               <h:inputText id=“message“
                   value=“#{newRecipient.message}“ />
               <h:message for=“message“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
               <h:message for=“pathToCard“ errorClass=“invalid“ />
           </h:panelGrid>
           <h2>Choose a picture card to send it:</h2>
           <h:panelGrid columns=“7″>
               <h:commandButton id=“register1″ action=“#{recipientController.register(‚resources/gfx/galleria1.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria1.jpg“ />
               <h:commandButton id=“register2″ action=“#{recipientController.register(‚resources/gfx/galleria2.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria2.jpg“ />
               <h:commandButton id=“register3″ action=“#{recipientController.register(‚resources/gfx/galleria3.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria3.jpg“ />
               <h:commandButton id=“register4″ action=“#{recipientController.register(‚resources/gfx/galleria4.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria4.jpg“ />
               <h:commandButton id=“register5″ action=“#{recipientController.register(‚resources/gfx/galleria5.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria5.jpg“ />
               <h:commandButton id=“register6″ action=“#{recipientController.register(‚resources/gfx/galleria6.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria6.jpg“ />
               <h:commandButton id=“register7″ action=“#{recipientController.register(‚resources/gfx/galleria7.jpg‘)}“ value=“Register“ styleClass=“card“ image=“resources/gfx/galleria7.jpg“ />
           </h:panelGrid>
           <h:panelGrid columns=“1″>
               <h:messages styleClass=“messages“
                   errorClass=“invalid“ infoClass=“valid“
                   warnClass=“warning“ globalOnly=“true“ />
           </h:panelGrid>
       </h:form>
       <h2>Recipients and Greetingcards sent:</h2>
       <h:panelGroup rendered=“#{empty recipients}“>
           <em>No registered recipients.</em>
       </h:panelGroup>
       <h:dataTable var=“recipient“ value=“#{recipients}“
           rendered=“#{not empty recipients}“
           styleClass=“simpletablestyle“>
           <h:column>
               <f:facet name=“header“>Id</f:facet>
               #{recipient.id}
           </h:column>
           <h:column>
               <f:facet name=“header“>Name</f:facet>
               #{recipient.name}
           </h:column>
           <h:column>
               <f:facet name=“header“>Email</f:facet>
               #{recipient.email}
           </h:column>
           <h:column>
               <f:facet name=“header“>Message</f:facet>
               #{recipient.message}
         </h:column>
           <h:column>
               <f:facet name=“header“>Card</f:facet>
               <h:button image=“#{recipient.pathToCard}“ styleClass=“card“ />
           </h:column>
           <h:column>
               <f:facet name=“header“>REST URL</f:facet>
               <a href=“#{request.contextPath}/rest/recipients/#{recipient.id}“>/rest/recipients/#{recipient.id}</a>
           </h:column>
           <f:facet name=“footer“>
           REST URL for all recipients: <a
                    href=“#{request.contextPath}/rest/recipients“>/rest/recipients</a>
           </f:facet>
       </h:dataTable>
   </ui:define>
</ui:composition>

Die register(…) Action des RecipientController wird durch einen Click auf ein Grusskarten Bild in der Gallerie aufgerufen, z. B.: action=“#{recipientController.register(‚resources/gfx/galleria7.jpg‘)}“

Hier der RecipientController mit der auszuführenden Action:

package de.binaris.grusskarten.controller;
 
import java.util.HashMap;
import java.util.logging.Logger;
 
import javax.annotation.PostConstruct;
import javax.enterprise.inject.Model;
import javax.enterprise.inject.Produces;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
 
import de.binaris.grusskarten.model.Recipient;
import de.binaris.grusskarten.service.RecipientRegistration;
 
@Model
public class RecipientController {
 
   @Inject
   private FacesContext facesContext;
 
   @Inject
   private RecipientRegistration recipientRegistration;
 
   @Produces
   @Named
   private Recipient newRecipient;
 
   @PostConstruct
   public void initNewRecipient() {
               newRecipient = new Recipient();
   }
  
   public void register(String pathToCard) throws Exception {
       try {
           Logger log = Logger.getLogger(„RecipientController“);
           HashMap<String, String> cardsRecipient = new HashMap<String, String>();
           newRecipient.setPathToCard(pathToCard);
           cardsRecipient.put(„mailRecipientName“, newRecipient.getName());
           cardsRecipient.put(„mailRecipientEmail“, newRecipient.getEmail());
           cardsRecipient.put(„message“, newRecipient.getMessage());
           cardsRecipient.put(„pathToCard“, newRecipient.getPathToCard());
           recipientRegistration.setCardsRecipient(cardsRecipient);
 
           recipientRegistration.register(newRecipient);
              
           log.info(„register: cardsRecipient.get(‚“+“mailRecipientName“+“‚): „+cardsRecipient.get(„mailRecipientName“));
           log.info(„register: cardsRecipient.get(‚“+“mailRecipientEmail“+“‚): „+cardsRecipient.get(„mailRecipientEmail“));
           log.info(„register: cardsRecipient.get(‚“+“message“+“‚): „+cardsRecipient.get(„message“));
           log.info(„register: cardsRecipient.get(‚“+“pathToCard“+“‚): „+cardsRecipient.get(„pathToCard“));
             
           initNewRecipient();
              
           FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_INFO, „Recipient registered, card sent!“, „Registration successful“);
           facesContext.addMessage(null, m);
       } catch (Exception e) {
           String errorMessage = getRootErrorMessage(e);
           FacesMessage m = new FacesMessage(FacesMessage.SEVERITY_ERROR, errorMessage, „Registration unsuccessful“);
           facesContext.addMessage(null, m);
       }
   }
 
   private String getRootErrorMessage(Exception e) {
       // error message when registration fails.
       String errorMessage = „Registration failed. See server log for more information“;
       if (e == null) {
           // default error messages
           return errorMessage;
       }
       // find the root cause
       Throwable t = e;
       while (t != null) {
           // Get the message from the Throwable class instance
           errorMessage = t.getLocalizedMessage();
           t = t.getCause();
       }
       // root cause message
       return errorMessage;
   }
}

b) Service Backend:

Dabei entspricht die im Service-Backend verwendeten Stateless Session Beans (EJB 3.1) vom Design Pattern her der Facade (SLSB – Stateless Session Bean) für ein einheitliches Interface. Die SLSBs wird hier ohne deklarativ annotierte @Transaction oder zusätzliche User Transaction verwendet.

Hier das RecipientRepository der für die Grusskarten verwendeten Empfänger unter Verwendung der JPA Recipient Entity und der benötigten Queries:

package de.binaris.grusskarten.data;
 
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
 
import java.util.List;
 
import de.binaris.grusskarten.model.Recipient;
 
@ApplicationScoped
public class RecipientRepository {
 
   @Inject
   private EntityManager em;
 
   public Recipient findById(Long id) {
       return em.find(Recipient.class, id);
   }
 
   public Recipient findByEmail(String email) {
       CriteriaBuilder cb = em.getCriteriaBuilder();
       CriteriaQuery<Recipient> criteria = cb.createQuery(Recipient.class);
       Root<Recipient> recipient = criteria.from(Recipient.class);
       // Swap criteria statements to try out type-safe criteria queries, a new
       // feature in JPA 2.0
       // criteria.select(member).where(cb.equal(member.get(Recipient_.name), email));
       criteria.select(recipient).where(cb.equal(recipient.get(„email“), email));
       return em.createQuery(criteria).getSingleResult();
   }
 
   public List<Recipient> findAllOrderedByName() {
       CriteriaBuilder cb = em.getCriteriaBuilder();
       CriteriaQuery<Recipient> criteria = cb.createQuery(Recipient.class);
       Root<Recipient> recipient = criteria.from(Recipient.class);
       // Swap criteria statements to try out type-safe criteria queries, a new
       // feature in JPA 2.0
       // criteria.select(recipient).orderBy(cb.asc(member.get(Recipient_.name)));
       criteria.select(recipient).orderBy(cb.asc(recipient.get(„name“)));
       return em.createQuery(criteria).getResultList();
   }
}

Für die Verwendung von JPA 2.0 im obigen RecipientRepository generiert Maven mittels pom.xml im Verzeichnis target/generated-sources/de/binaris/grusskarten/model die abstrakte Klasse Recipient_.java, die bei Bedarf dem Build Path hinzuzufügen ist, mit den folgenden Metadaten:

package de.binaris.grusskarten.model;
 
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
 
@Generated(value = „org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor“)
@StaticMetamodel(Recipient.class)
public abstract class Recipient_ {
 
                public static volatile SingularAttribute<Recipient, String> message;
                public static volatile SingularAttribute<Recipient, Long> id;
                public static volatile SingularAttribute<Recipient, String> email;
                public static volatile SingularAttribute<Recipient, String> name;
                public static volatile SingularAttribute<Recipient, String> pathToCard;
 
}

Hier nun der RecipientRegistration-Service (als SLSB):

package de.binaris.grusskarten.service;
 
import java.util.HashMap;
import java.util.logging.Logger;
 
import javax.ejb.Stateless;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.persistence.EntityManager;
 
import de.binaris.grusskarten.model.Recipient;
import de.binaris.grusskarten.util.MailSender;
 
// @Stateless annotation without explicit manual transaction demarcation
@Stateless
public class RecipientRegistration {
 
   @Inject
   private Logger recipientLogger;
 
   @Inject
   private EntityManager em;
 
   @Inject
   private Event<Recipient> recipientEventSrc;
 
   private HashMap<String, String> cardsRecipient;
  
   public void register(Recipient recipient) throws Exception {
               recipientLogger.info(„Registering “ + recipient.getName());
       em.persist(recipient);
       recipientEventSrc.fire(recipient);
      
       recipientLogger.info(„Registered. „);
try {
                MailSender.sendMessage(„mycards@yahoo.com“,
                „password“,
                cardsRecipient.get(„mailRecipientName“),
                cardsRecipient.get(„mailRecipientEmail“),
                cardsRecipient.get(„message“),
                cardsRecipient.get(„pathToCard“));
} catch (AddressException e) {
                e.printStackTrace();
} catch (MessagingException e) {
                e.printStackTrace();
}
   }
  
   public void setCardsRecipient(HashMap<String, String> cardsRecipient) {
               this.cardsRecipient = cardsRecipient;
   }
  
   public HashMap<String, String> getCardsRecipient() {
               return cardsRecipient;
   }
}

c) Der REST Webservice:

Über den Einsatz von JBoss RESTEasy für RESTful Webservices für SPAs gibt es bereits hier einen Blog-Eintrag in diesem Blog hier. Die Aktivierung des RESTEasy-Webservices geschieht dort über die klassische Verwendung der web.xml (Servlet 2.3, 2.4 oder Servlet 2.5) zur Aktivierung von RESTEasy Webservices:

<?xml version=„1.0“?>
<!DOCTYPE web-app PUBLIC „-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN“
„http://java.sun.com/dtd/web-app_2_3.dtd“>

<web-app>
<display-name>Archetype Created Web Application</display-name>
<context-param>
<param-name>javax.ws.rs.core.Application</param-name>
<param-value>de.binaris.rest.samples.service.SumOfMultiplesApplication
</param-value>

</context-param>
<context-param>
<param-name>resteasy.servlet.mapping.prefix</param-name>
<param-value>/resteasy</param-value>
</context-param>
<listener>
<listener-class>
org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap
</listener-class>
</listener>
<servlet>
<servlet-name>Resteasy</servlet-name>
<servlet-class>
org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Resteasy</servlet-name>
<url-pattern>/resteasy/*</url-pattern>
</servlet-mapping>
</web-app>

Stattdessen wird nun die folgende Klasse JaxRsActivator verwendet, die von Application abgeleitet ist und mit der @ApplicationPath Annotation annotiert ist, da seit Servlet-3.0 keine web.xml mehr benötigt wird, sondern alles über Annotationen deklarierbar ist:

package de.binaris.grusskarten.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath(„/rest“)
public class JaxRsActivator extends Application {
/* neither extended class attributes nor methods required */
}

Hier der Beispiel REST-Webservice zum Eintragen des Recipients und dem Versand der Card:

packge de.binaris.grusskarten.rest;
 
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
 
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.persistence.NoResultException;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.ValidationException;
import javax.validation.Validator;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
 
import de.binaris.grusskarten.data.RecipientRepository;
import de.binaris.grusskarten.model.Recipient;
import de.binaris.grusskarten.service.RecipientRegistration;
 
/**
* JAX-RS Example
* <p/>
* This class produces a RESTful service to read/write the contents of the recipients table.
*/
@Path(„/recipients“)
@RequestScoped
public class RecipientResourceRESTService {
  
   @Inject
   private Logger recipientLogger;
 
   @Inject
   private Validator validator;
 
   @Inject
   private RecipientRepository repository;
 
   @Inject
   RecipientRegistration registration;
 
   @GET
   @Produces(MediaType.APPLICATION_JSON)
   public List<Recipient> listAllRecipients() {
       return repository.findAllOrderedByName();
   }
 
   @GET
   @Path(„/{id:[0-9][0-9]*}“)
   @Produces(MediaType.APPLICATION_JSON)
   public Recipient lookupRecipientById(@PathParam(„id“) long id) {
       Recipient recipient = repository.findById(id);
       if (recipient == null) {
           throw new WebApplicationException(Response.Status.NOT_FOUND);
       }
       return recipient;
   }
 
   /**
     * Creates a new member from the values provided. Performs validation, and will return a JAX-RS response with either 200 ok,
     * or with a map of fields, and related errors.
     */
   @POST
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
   public Response createRecipient(Recipient recipient) {
 
       Response.ResponseBuilder builder = null;
       try {
           // Validates member using bean validation
           validateRecipient(recipient);
           registration.register(recipient);
 
           // Create an „ok“ response
           builder = Response.ok();
       } catch (ConstraintViolationException ce) {
           // Handle bean validation issues
            builder = createViolationResponse(ce.getConstraintViolations());
       } catch (ValidationException e) {
           // Handle the unique constrain violation
           Map<String, String> responseObj = new HashMap<String, String>();
           responseObj.put(„email“, „Email taken“);
           builder = Response.status(Response.Status.CONFLICT).entity(responseObj);
       } catch (Exception e) {
           // Handle generic exceptions
           Map<String, String> responseObj = new HashMap<String, String>();
           responseObj.put(„error“, e.getMessage());
           builder = Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
       }
       return builder.build();
   }
 
   /**
     * <p>
     * Validates the given Recipient variable and throws validation exceptions based on the type of error. If the error is standard
     * bean validation errors then it will throw a ConstraintValidationException with the set of the constraints violated.
     * </p>
     * <p>
     * If the error is caused because an existing recipient with the same email is registered it throws a regular validation
     * exception so that it can be interpreted separately.
     * </p>
     *
     * @param recipient Recipient to be validated
     * @throws ConstraintViolationException If Bean Validation errors exist
     * @throws ValidationException If member with the same email already exists
     */
   private void validateRecipient(Recipient recipient) throws ConstraintViolationException, ValidationException {
       // Create a bean validator and check for issues.
       Set<ConstraintViolation<Recipient>> violations = validator.validate(recipient);
 
       if (!violations.isEmpty()) {
           throw new ConstraintViolationException(new HashSet<ConstraintViolation<?>>(violations));
       }
       // Check the uniqueness of the email address
       if (emailAlreadyExists(recipient.getEmail())) {
//           throw new ValidationException(„Unique Email Violation“);
       }
   }
 
   /**
     * Creates a JAX-RS „Bad Request“ response including a map of all violation fields, and their message. This can then be used
     * by clients to show violations.
     *
     * @param violations A set of violations that needs to be reported
     * @return JAX-RS response containing all violations
     */
   private Response.ResponseBuilder createViolationResponse(Set<ConstraintViolation<?>> violations) {
             recipientLogger.fine(„Validation completed. violations found: “ + violations.size());
       Map<String, String> responseObj = new HashMap<String, String>();
 
       for (ConstraintViolation<?> violation : violations) {
           responseObj.put(violation.getPropertyPath().toString(), violation.getMessage());
       }
       return Response.status(Response.Status.BAD_REQUEST).entity(responseObj);
   }
 
   /**
     * Checks if a recipient with the same email address is already registered.
     * This is the only way to easily capture the
     * „@UniqueConstraint(columnNames = „email“)“ constraint from the Recipient class.
     *
     * @param email The email to check
     * @return True if the email already exists, and false otherwise
     */
   public boolean emailAlreadyExists(String email) {
       Recipient recipient = null;
       try {
               recipient = repository.findByEmail(email);
       } catch (NoResultException e) {
           // ignore
       }
       return recipient != null;
   }
}

Die Yahoo Mail Account-Session, um die Grusskarten Mail über den Account userMail zu versenden, erhält man folgendermaßen:

/**
* get a Yahoo mail account session to send the email via accunt userMail
*
* @param userMail
* @return Session
*/
public static Session getMailSenderSessionYahoo(String userMail) {
    final Properties props = System.getProperties();
    props.setProperty( „mail.smtp.from“, userMail);
    props.setProperty( „mail.smtp.host“, „smtp.mail.yahoo.com“ );
    props.setProperty( „mail.smtp.auth“, „true“ );
    props.setProperty( „mail.smtp.port“, „465“ );
    props.setProperty( „mail.smtp.starttls.enable“, „true“);
    props.setProperty( „mail.smtp.quitwait“, „false“);
    props.setProperty( „mail.smtp.ssl.enabled“, „true“);
    props.setProperty( „mail.smtp.socketFactory.port“, „465“ );
    props.setProperty( „mail.smtp.socketFactory.class“,“javax.net.ssl.SSLSocketFactory“ );
    return Session.getInstance( props, null);
}

d) Die Datenbank:

Die Testdaten aus der import.sql:

insert into Recipient (id, name, email, message, pathToCard) values (0, ‚Banana Joe‘, ‚banana.joe@bananajoe.com‘, ‚Hey Joe, wazzuuup… call me.‘, ‚resources/gfx/galleria1.jpg‘)

Die persistence.xml aus dem /META-INF Unterverzeichnis zur Aktivierung von JPA 2 sieht für die Verwendung der HSQL Beispiel-Datenbank folgendermaßen aus:

<?xml version=“1.0″ encoding=“UTF-8″?>
<persistence version=“2.0″
   xmlns=“http://java.sun.com/xml/ns/persistence“
   xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“
   xsi:schemaLocation=“http://java.sun.com/xml/ns/persistence
       http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd“>
   <persistence-unit name=“grusskarten“>
     <jta-data-source>java:jboss/datasources/GrusskartenDS</jta-data-source>
     <properties>
         <property name=“hibernate.hbm2ddl.auto“ value=“create-drop“ />
         <property name=“hibernate.show_sql“ value=“false“ />
     </properties>
   </persistence-unit>
</persistence>

Die Verwendung von JPA 2 ist am folgenden <persistence …>-Element sehr gut zu erkennen. Denn ohne dass hier die version=“2.0“ eingetragen ist, fährt man gezwungenermaßen stets nur persistence 1.0, d.h. z.B. JPA 1.

<persistence version=“2.0″
xmlns=“http://java.sun.com/xml/ns/persistence“
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“
xsi:schemaLocation=“http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd„>

Für eine produktive Applikation und Datenbank sollte die Datasource in der standalone.xml entsprechend konfiguriert werden, wie es in diesem Blog-Eintrag mit dem Titel “Services – mit Hibernate 4, Vaadin 6, JBoss 7“ hier beschrieben ist.

Für die Test-Applikation soll die Verwendung der HSQL “in memory“-Datenbank genügen.
Hierfür wird im /WEB-INF-Verzeichnis neben der beans.xml (CDI-Aktivierung) auch die folgende grusskarten-ds.xml hinterlegt:

<?xml version=“1.0″ encoding=“UTF-8″?>
<!– unmanaged datasource for testing purposes only uses H2,
       an in memory database that ships with JBoss AS. –>
<datasources xmlns=“http://www.jboss.org/ironjacamar/schema“
   xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance“
   xsi:schemaLocation=“http://www.jboss.org/ironjacamar/schema  
   http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd„>
 
   <!– The datasource is bound into JNDI at this location. We reference
       this in META-INF/persistence.xml –>
   <datasource jndi-name=“java:jboss/datasources/GrusskartenDS“
       pool-name=“grusskarten“ enabled=“true“
       use-java-context=“true“>
       <connection-url>
jdbc:h2:mem:grusskarten;DB_CLOSE_ON_EXIT=FALSE;DB_CLOSE_DELAY=-1
       </connection-url>
       <driver>h2</driver>
       <security>
           <user-name>admin</user-name>
           <password>banana_joe</password>
       </security>
   </datasource>
</datasources>

Abschließend noch die Die CSS3-Styles mit den Transitionen in der Klasse .card und dem Style für das .hover-Event über einer Grusskarte .card:hover

screen.css:

/* Core styles for the page */
body {
margin: 0;
padding: 0;
background-color: #4a5d75;
font-family: „Lucida Sans Unicode“, „Lucida Grande“, sans-serif;
font-size: 0.8em;
color:#363636;
}

#container {
margin: 0 auto;
padding: 0 20px 10px 20px;
border-top: 5px solid #000000;
border-left: 5px solid #8c8f91;
border-right: 5px solid #8c8f91;
border-bottom: 25px solid #4a5d75;
width: 865px; /* subtract 40px from banner width for padding */
background: #d5d5d5;
background-repeat: repeat-x;
padding-top: 30px;
box-shadow: 3px 3px 15px #d5d5d5;
}

#content {
float: left;
width:500px;
margin: 20px;
}

#aside {
font-size: 0.9em;
width: 275px;
float: left;
margin: 20px 0px;
border: 1px solid #D5D5D5;
background: #F1F1F1;
background-image: url(#{request.contextPath}/resources/gfx/asidebkg.png);
background-repeat: repeat-x;
padding: 20px;
}

.card {
font-size: 0.9em;
transition: all 0.3s ease-in-out 0.5s;
width: 25px;
height: 20px;
float: left;
margin: 0px 0px;
border: 1px solid #D5D5D5;
background: #F1F1F1;
background-repeat: repeat-x;
padding: 2px;
}

#aside ul {
padding-left: 30px;
}

.dualbrand {
float: right;
padding-right: 10px;
}

#footer {
clear: both;
text-align: center;
color: #666666;
font-size: 0.85em;
}

code {
font-size: 1.1em;
}

a {
color: #4a5d75;
text-decoration: none;
}

a:hover {
color: #369;
text-decoration: underline;
}

.card:hover {
transition: all 0.5s ease-in-out;
width: 130px;
height: 100px;
}

h1 {
color:#243446;
font-size: 2.25em;
}

h2 {
font-size: 1em;
}

h3 {
color:#243446;
}

h4 {
}

h5 {
}

h6 {
}

/* Recipient registration styles */
span.invalid {
padding-left: 3px;
color: red;
}

form {
padding: 1em;
font: 80%/1 sans-serif;
width: 375px;
border: 1px solid #D5D5D5;
background: #F1F1F1;
}

label {
float: left;
width: 15%;
margin-left: 20px;
margin-right: 0.5em;
padding-top: 0.2em;
text-align: right;
font-weight: bold;
color:#363636;
}

input {
margin-bottom: 8px;
}

.register {
float: left;
margin-left: 85px;
}

.simpletablestyle {
background-color:#E6E7E8;
clear:both;
width: 550px;
}

.simpletablestyle img {
border:0px;
}

.simpletablestyle td {
height:2em;
padding-left: 6px;
font-size:11px;
padding:5px 5px;
}

.simpletablestyle th {
background: url(#{request.contextPath}/resources/gfx/bkg-blkheader.png) black repeat-x top left;
font-size:12px;
font-weight:normal;
padding:0 10px 0 5px;
border-bottom:#999999 dotted 1px;
}

.simpletablestyle thead {
background: url(#{request.contextPath}/resources/gfx/bkg-blkheader.png) black repeat-x top left;
height:31px;
font-size:10px;
font-weight:bold;
color:#FFFFFF;
text-align:left;
}

.simpletablestyle .header a {
color:#94aebd;
}

.simpletablestype tfoot {
height: 20px;
font-size: 10px;
font-weight: bold;
background-color: #EAECEE;
text-align: center;
}

.simpletablestyle tr.header td {
padding: 0px 10px 0px 5px;
}

.simpletablestyle .subheader {
background-color: #e6e7e8;
font-size:10px;
font-weight:bold;
color:#000000;
text-align:left;
}

/* Using new CSS3 selectors for styling*/
.simpletablestyle tr:nth-child(odd) {
background: #f4f3f3;
}

.simpletablestyle tr:nth-child(even) {
background: #ffffff;
}

.simpletablestyle td a:hover {
color:#3883ce;
text-decoration:none;
}

Die Test-Frameworks und die Tests

 a) Die Arquillian Tests für das Service-Backend werden folgendermaßen aktiviert:

Das /test-Verzeichnis neben dem /WEB-INF-Verzeichnis des grusskarten.war hat hierfür folgende Struktur:

– src/test/
– resources
   – META-INF  
       test-persistence.xml
   – arquillian.xml
   – grusskarten-test-ds.xml
– java
– de.binaris.grusskarten.test
RecipientRegistrationTest.java

Mit der folgenden test-persistence.xml:

<?xml version=„1.0“ encoding=„UTF-8“?>
<persistence version=„2.0“
   xmlns=„http://java.sun.com/xml/ns/persistence“ xmlns:xsi=„http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=„http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd“>
   <persistence-unit name=„grusskarten“>
     <!– We use a different datasource for tests, so as to not overwrite
         production data. This is an unmanaged data source, backed by H2, an in memory
         database. Production applications should use a managed datasource. –>
     <!– The datasource is deployed as WEB-INF/test-ds.xml,
         you can find it in the source at src/test/resources/grusskarten-test-ds.xml –>
     <jta-data-source>java:jboss/datasources/GrusskartenTestDS</jta-data-source>
     <properties>
         <!– Properties for Hibernate –>
         <property name=„hibernate.hbm2ddl.auto“ value=„create-drop“ />
         <property name=„hibernate.show_sql“ value=„false“ />
     </properties>
   </persistence-unit>
</persistence>

Und der folgenden arquillian.xml:

<?xml version=„1.0“ encoding=„UTF-8“?>
<arquillian xmlns=„http://jboss.org/schema/arquillian“ xmlns:xsi=„http://www.w3.org/2001/XMLSchema-instance“ xsi:schemaLocation=„http://jboss.org/schema/arquillian http://jboss.org/schema/arquillian/arquillian_1_0.xsd“>
   <!– Force the use of the Servlet 3.0 protocol with all containers, as it is the most mature –>
   <defaultProtocol type=„Servlet 3.0“ />
   <!– Example configuration for a remote JBoss AS 7 instance –>
   <container qualifier=„jboss“ default=„true“>
     <!– If you want to use the JBOSS_HOME environment variable, just delete the jbossHome property –>
     <configuration>
         <property name=„jbossHome“>/path/to/jboss/as</property>
     </configuration>
   </container>
</arquillian>

Hier die grusskarten-test-ds.xml:

<?xml version=„1.0“ encoding=„UTF-8“?>
<!– This is an unmanaged datasource. It should be used for proofs of concept
   or testing only. It uses H2, an in memory database that ships with JBoss –>
<datasources xmlns=„http://www.jboss.org/ironjacamar/schema“ xmlns:xsi=„http://www.w3.org/2001/XMLSchema-instance“
   xsi:schemaLocation=„http://www.jboss.org/ironjacamar/schema http://docs.jboss.org/ironjacamar/schema/datasources_1_0.xsd“>
   <!– The datasource is bound into JNDI at this location. We reference
     this in META-INF/test-persistence.xml –>
   <datasource jndi-name=„java:jboss/datasources/GrusskartenTestDS“
     pool-name=„grusskarten-test“ enabled=„true“
     use-java-context=„true“>
     <connection-url>jdbc:h2:mem:grusskarten-test;DB_CLOSE_DELAY=-1</connection-url>
     <driver>h2</driver>
     <security>
         <user-name>admin</user-name>
         <password>banana_joe</password>
     </security>
   </datasource>
</datasources>

Und hier die Test-Klasse RecipientRegistrationTest für den Backend Service-Test:

package de.binaris.grusskarten.test;
 
import static org.junit.Assert.assertNotNull;
 
import java.util.logging.Logger;
 
import javax.inject.Inject;
 
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;
 
import de.binaris.grusskarten.model.Recipient;
import de.binaris.grusskarten.service.RecipientRegistration;
import de.binaris.grusskarten.util.Resources;
 
@RunWith(Arquillian.class)
public class RecipientRegistrationTest {
   @Deployment
   public static Archive<?> createTestArchive() {
       return ShrinkWrap.create(WebArchive.class, „test.war“)
               .addClasses(Recipient.class, RecipientRegistration.class, Resources.class)
               .addAsResource(„META-INF/test-persistence.xml“, „META-INF/persistence.xml“)
               .addAsWebInfResource(EmptyAsset.INSTANCE, „beans.xml“)
               // deploy the test datasource
               .addAsWebInfResource(„grusskarten-test-ds.xml“);
   }
 
   @Inject
   RecipientRegistration recipientRegistration;
 
   @Inject
   Logger recipientLogger;
 
   @Test
   public void testRegister() throws Exception {
       Recipient newRecipient = new Recipient();
       newRecipient.setName(„Django Doe“);
       newRecipient.setEmail(„django@mailinator.com“);
       newRecipient.setMessage(„Hey Django, please call me.“);
       newRecipient.setPathToCard(„resources/gfx/galleria1.jpg“);
       recipientRegistration.register(newRecipient);
       assertNotNull(newRecipient.getId());
       recipientLogger.info(newRecipient.getName() + “ was persisted with id “ + newRecipient.getId());
   }
}

In der pom.xml können unter den Profilen im Default Profil für den skip-Test Wert ‘false‘ eingetragen werden und z. B. der Maven Task mvn clean test -Parq -jbossas-managed aufgerufen werden, damit die Tests ausgeführt werden. Hier noch mal der relevante Abschnitt in der pom.xml:

   <profiles>
       <profile>
           <!– The default profile skips all tests, though you can tune
                 it to run just unit tests based on a custom pattern –>
           <id>default</id>
           <activation>
               <activeByDefault>false</activeByDefault>
           </activation>
           <build>
               <plugins>
                   <plugin>
                       <artifactId>maven-surefire-plugin</artifactId>
                       <version>${version.surefire.plugin}</version>
                       <configuration>
                           <skip>true</skip>
                       </configuration>
                   </plugin>
               </plugins>
           </build>
       </profile>
       <profile>
           <!– An optional Arquillian testing profile that executes tests
                                                               the profile will start a new JBoss AS instance, execute
               the test, shut it down when done –>
           <!– Run with: mvn clean test -Parq-jbossas-managed –>
           <id>arq-jbossas-managed</id>
           <dependencies>
               <dependency>
                   <groupId>org.jboss.as</groupId>
                   <artifactId>jboss-as-arquillian-container-managed</artifactId>
                   <scope>test</scope>
               </dependency>
           </dependencies>
       </profile>
       <profile>
           <!– An optional Arquillian testing profile that executes tests
               in a remote JBoss AS instance –>
           <!– Run with: mvn clean test -Parq-jbossas-remote –>
           <id>arq-jbossas-remote</id>
           <dependencies>
               <dependency>
                   <groupId>org.jboss.as</groupId>
                   <artifactId>jboss-as-arquillian-container-remote</artifactId>
                   <scope>test</scope>
               </dependency>
           </dependencies>
       </profile>
   </profiles>

b) Selenium Tests:

Installation: In der Test-Browser Instanz die folgende Installations-Url aufrufen und der Installation zustimmen:

http://release.seleniumhq.org/selenium-ide/2.9.0/selenium-ide-2.9.0.xpi

Selenium-Blog:
http://blog.reallysimplethoughts.com/2015/03/09/selenium-ide-scheduler-has-arrived-part-1/

Selenium Download:
http://docs.seleniumhq.org/download/

Selenium Documentation:
http://docs.seleniumhq.org/docs/
https://seleniumhq.github.io/docs/
http://seleniumhq.github.io/selenium/docs/api/java/index.html

Die Tests werden folgendermaßen angelegt, nachdem das Selenium IDE PlugIn installiert wurde. Dabei sollten folgende Grundregeln beachtet werden:

– Tests sollten immer eine definierte Start-Url haben, z.B. hier: http://localhost:8080/grusskarten/
– Tests sollten nicht von anderen Tests abhängig sein.
– Tests sollten immer nur eine Sache/einen kleinen Workflow je Test Case testen, z.B. den Versand einer Card.
– Test Cases können in einer Test Suite zusammengefasst werden, um den Automatisierungsgrad zu erhöhen.

Bei dieser Gelegenheit darf auch auf das sehr effektive und interessante Seminar “TDD mit Java”

hier und hier von Binaris Informatik hingewiesen werden.

Wie Test-Suites, die mit dem Selenium IDE PlugIn durchgeführt wurden und automatisiert ausgeführt werden können, erstellt werden, wurde bereits in diesem vorigen Blog Eintrag hier beschrieben.

Hier nun auch das komplette Web-Archiv grusskarten.war zum Download:
http://www.4shared.com/file/YxqB4JjRba/grusskarten.html

Hier den JBoss EAP 6.3 (JBoss AS 7.4) als Jar-Installer zum Download.

http://www.jboss.org/download-manager/file/jboss-eap-6.3.0.GA-installer.jar

Installation/Start der Beispiel-Applikation:

– Das JBoss Zip-Archiv entpacken
– JAVA_HOME, JBOSS_HOME entsprechend setzen und
in der standalone.bat oder standalone.sh verwenden.
– grusskarten.war ins Server-Unterverzeichnis /standalone/deployments speichern
– Neben grusskarten.war eine leere Textdatei namens grusskarten.war.dodeploy speichen.
– Server starten per standalone.bat oder standalone.sh, http://localhost:8080/grusskarten/ aufrufen
– Die Selenium Tests per Selenium IDE PlugIn/Recorder erstellen und ausführen.

Allen interessierten Leserinnen und Lesern weiterhin viel Freude bei der agilen Softwareentwicklung mittels Scrum und dem Test Driven Development mit Java, sowie einen schönen Mai.

Kommentare

6 Kommentare zu “Services – TDD mit Arquillian, JSF2, JEE6, CDI”

  1. Von Services – TDD mit Vaadin Testbench, Java, Hibernate, JBoss : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Sonntag, 26. Juli 2015 14:58

    […] über Arquillian Tests für das Service-Backend wurde in diesem Blog-Eintrag hier bereits […]

  2. Von Services – TDD mit QF Test, Java, JDK, Eclipse RCP : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Sonntag, 30. August 2015 20:31

    […] über Arquillian Tests für das Service-Backend wurde in diesem Blog-Eintrag hier bereits […]

  3. Von Services – mit REST, JPA 2, EJB 3.2, JSF 2.2, Angular JS, Bootstrap, JBoss Forge : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Mittwoch, 30. September 2015 19:21

    […] in diesem Blog-Eintrag hier bereits […]

  4. Von Services – mit REST, JPA 2, EJB 3.2, WebFrameworks, JBoss WildFly : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Samstag, 31. Oktober 2015 20:51

    […] in diesem Blog-Eintrag hier bereits […]

  5. Von Services – mit Angular JS, REST, JPA 2, JEE7 auf JBoss WildFly 9 : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Sonntag, 22. November 2015 18:53

    […] in diesem Blog-Eintrag hier bereits […]

  6. Von WebApps – mit REST, JPA 2, JSF 2, AngularJS, JEE6, JEE7 auf JBoss WildFly 9 : Softwareentwicklung, Projektmanagement & Schulung | binaris informatik GmbH am Sonntag, 13. Dezember 2015 15:18

    […] Admin-Applikation unter Verwendung von Bootstrap, Underscore, jQuery, und hier eine Grusskarten-SPA-Beispielapplikation unter Verwendung eines JSF Frontends zum Test zur Verfügung […]

Einen Kommentar hinzufügen...

Sie müssen registriert und angemeldet sein um einen Kommentar zu schreiben.