Via EL-Expression auf ein CDI-Bean zugreifen
 
  Stereotyp-Annotation annotiert mit Stereotyp
 
  Manuelle Integration von Bean-Validation und CDI
 
  Manuelle CDI Injizierung in Message-Body Writer
 
  Konfiguration JAX-RS Application
 
  JSON Konvertierung mit View
 
  JAX-RS Ressource mit CDI Injection-Points
 
  Injizierung in einem Servlet-Filter
 
  Injizierung in einem Servlet
 
  HTML-Link auf JAX-RS Endpoint
 
  HTML-Form für den Ideen-Import
 
  Helper für manuelle Injizierung
 
  Generisches JPA-Repository
 
  Erweiterte JAX-RS Application (nicht portabel)
 
  Entity-Manager Producer-Methode
 
  Entity-Manager Producer-Feld
 
  EJB mit CDI-Stereotype und übersteuertem Scope
 
  EJB mit CDI-Stereotype
 
  EJB mit CDI Injection-Points
 
  EJB mit asynchroner CDI-Observer Methode
 
  EJB als Backing-Bean mit Callback
 
  Constraint-Validator als CDI-Bean
 
  CDI-Beans als View-Controller
 
  CDI-Bean mit Stereotype und übersteuertem Scope
 
  CDI-Bean mit Namen
 
  CDI-Bean mit angepasstem Namen
 
  CDI-Bean als zustandsloses Service
 
  CDI-Bean als Value-Holder
 
  CDI-Bean als Backing-Bean mit Injection-Point zu anderem Backing-Bean
 
  Aktivierung der Validator-Factory

3 CDI und Java EE

CDI wurde erstmals mit Java EE6 als fixer Bestandteil der Java EE Plattform veröffentlicht. Anfangs war CDI primär als Bindeglied zwischen JSF und EJB gedacht. Darauf ist auch der ursprüngliche Name der Spezifikation (Web-Beans) zurückzuführen. Doch CDI wurde sehr schnell zu einem vollwertigen Komponentenmodell, welches weit mehr als nur ein Bindeglied ist. Obwohl dies in der ersten Revision der Spezifikation nicht vorgesehen war, kann sowohl Apache OpenWebBeans als auch JBoss Weld, mit Hilfe von proprietären APIs, in Java SE Applikationen verwendet werden. Seit CDI 1.1 wird dies durch die Spezifikation selbst unterstützt. Im Kapitel Apache DeltaSpike werden wir die Kompatibilität mit Java SE genauer betrachten. In diesem Kapitel konzentrieren wir uns auf die Standardintegration in Java EE6 und EE7.

3.1 Die ersten Schritte in Richtung Java EE

In Java EE6 wurden einige grundlegende Integrationspunkte direkt in der CDI- und Plattform-Spezifikation definiert. Als Grundregel kann in jeder gemanagten Instanz einer Klasse,bei der Ressourcen-Injizierung bereits mit Java EE5 möglich war, auch CDI basierte Injizierung via @Inject verwendet werden. Durch diese Regel kann CDI Injizierung in Artefakten unterstützt werden, welche nicht explizit in der Plattform-Spezifikation aufgelistet werden. Darüber hinaus unterstützen EE Server Injection-Points für vordefinierte EE Artefakte. Hierzu zählen UserTransaction , javax.validation.ValidationFactory , javax.validation.Validator und javax.security.Principal .
Durch die enge Integration von CDI, EJB und JSF stößt man in vielen Fällen erst spät an die Grenzen der Integrationspunkte. In den nachfolgenden Revisionen von Java EE werden diese Grenzen weiter verschoben. Dank der stark zunehmenden Verbreitung von CDI wird seit EE7 die Integration mit CDI auch in anderen EE Spezifikationen ausgeweitet. Doch bereits EE6 selbst bietet viele Integrationspunkte. Einige davon werden wir in den nachfolgenden Teilen dieses Kapitels genauer betrachten. Bevor wir eine Web-Oberfläche für IdeaFork erstellen und dessen Funktionalität erweitern, werfen wir einen Blick auf die Umstrukturierungsmöglichkeiten in CDI basierten Applikationen. In IdeaFork können wir bspw. sämtliche Klassen in ein anderes Package verschieben. Jede moderne Java IDE unterstützt solche Änderungen in wenigen Schritten. Durch die Typsicherheit von Java wird das Ergebnis spätestens durch den Compiler überprüft. In vielen Projekten wird die Konfigurationsdatei beans.xml als einfacher Marker verwendet, wodurch für CDI keine weiteren Änderungen erforderlich sind. In IdeaFork nutzen wir allerdings einige Konfigurationsmöglichkeiten. Manche IDEs stellen einen erstklassigen CDI-Support zur Verfügung, wodurch selbst diese Konfigurationseinträge automatisch aktualisiert werden. Sollte dies nicht der Fall sein und ein oder mehrere Einträge sind nach der Umstrukturierung nicht mehr aktuell, so wird dies spätestens beim nächsten Applikationsstart vom CDI-Container überprüft und als Fehler erkannt. Der Startvorgang wird in einem solchen Fall mit einer entsprechenden Fehlermeldung abgebrochen. Technische Fehler, die erst nach dem Deployment der Applikation auftreten, werden dadurch auf ein absolutes Minimum reduziert. Hier müssen primär dynamische Bean-Lookups, welche nur im Ausnahmefall oder in Verbindung mit der EL (Expression Language) empfehlenswert sind, berücksichtigt werden.

3.2 Web-Applikationen mit CDI

Sowohl JSF als auch JAX-RS basieren auf Servlets. Für Servlet basierte Technologien gelten die gleichen Regeln in Hinblick auf die Konfiguration von CDI. Die Datei beans.xml könnten wir wie bisher im Verzeichnis META-INF hinterlegen. In Web-Applikationen wird zusätzlich WEB-INF als Konfigurationsverzeichnis unterstützt. Manche EE Server unterstützen META-INF/beans.xml in Web-Applikationen allerdings nicht korrekt. Aus diesem Grund verwenden wir das Verzeichnis WEB-INF . Nachdem wir WEB-INF/beans.xml als leere Markerdatei angelegt haben ist CDI im Web-Modul unserer Applikation aktiviert und wir können die ersten Funktionalitäten umsetzen.
Tipp: Unsere primäre Laufzeitumgebung ist Apache TomEE 1.7+. Darüber hinaus ist IdeaFork auch mit anderen EE6 Servern wie bspw. JBoss AS7 und Oracle Glassfish 3 getestet. Eine grundsätzliche Kompatibilität besteht jedoch mit jedem Server, der Java EE6 spezifikationskonform unterstützt.
Das zentrale Thema bei zustandsbehafteten Web-Applikationen im Java EE Umfeld im Zusammenhang mit CDI ist die Behandlung der serverseitigen Scopes. Der Request- sowie der Session-Scope sind für alle Servlet basierten Technologien relevant. Die korrekte Behandlung beider Scopes wird durch den CDI-Container sichergestellt. Somit sind hier keine zusätzlichen Integrationen in den anderen Spezifikationen erforderlich. Applikationsentwickler können CDI-Beans mit den entsprechenden Scope-Annotationen versehen und der CDI-Container sorgt wie gewohnt für die korrekte Verwaltung der Beans. Damit wir unsere bisherige Implementierung unabhängig wiederverwenden können, erstellen wir ein neues Java EE6 spezifisches Web-Modul. In diesem definieren wir die Java EE6 API und unser bisheriges Modul als Abhängigkeit.
Tipp: Seit EE6 bietet die Verwendung von Enterprise-Archiven (EARs) im Vergleich zu einfachen Web-Archiven (WARs) nur wenige Vorteile, aber in vielen Applikationsservern ergeben sich durch die unterschiedliche Interpretation der Bean Deployment Archive (BDA) Regeln unnötige Nachteile. In IdeaFork gibt es keinen Bedarf für EARs und daher können wir durch die Verwendung eines Web-Archivs die Portabilität erhöhen.

3.3 JSF mit CDI

In Java EE6 wird die CDI-Integration mit JSF primär durch die CDI-Spezifikation definiert. Da JSF-Beans laut der EE6 Plattform-Spezifikation Komponenten sind, welche Injizierung unterstützen müssen, können in einem EE-Server CDI-Beans problemlos in JSF-Beans injiziert werden.

 

Injizierung in anderen JSF Artefakten, wie bspw. in Phase-Listenern, wird in EE6 allerdings noch nicht unterstützt. Neben Java-Klassen spielen in JSF auch die sogenannten View Declaration Languages (VDL) eine zentrale Rolle. Sowohl JSP als auch die modernere Alternative namens Facelets verwenden die Expression-Language (EL), um auf Beans zuzugreifen. Durch @Named (aus JSR-330) können CDI-Beans in einer EL-Expression mit ihrem Namen angesprochen werden. Wie in JSF üblich wird dies intern mit einem eigenen EL-Resolver umgesetzt. CDI-Implementierungen registrieren diesen EL-Resolver automatisch, wodurch die Integration ohne manuelle Konfigurationsschritte aktiviert ist. Dieser Resolver ist dafür verantwortlich die entsprechende Contextual-Reference zu finden oder null zurückzuliefern. Dependent-scoped Beans nehmen allerdings erneut eine Sonderstellung ein. Sie existieren in diesem Zusammenhang für die Auswertungszeit einer vollständigen EL-Expression, selbst wenn sie in der Expression mehrmals angesprochen werden. Erst nach der vollständigen Auswertung der EL-Expression werden dependent-scoped Beans wieder zerstört. Neben dependent-scoped Beans können Beans mit beliebigen CDI-Scopes direkt via EL-Expression angesprochen werden, sofern der entsprechende Scope zum Zeitpunkt der Auswertung aktiv ist. In diesem Zusammenhang nimmt der CDI Conversation-Scope eine Sonderstellung ein, da für diesen eine spezielle Regel in Verbindung mit JSF definiert wurde. Details zum Conversation-Scope werden wir im Kapitel Apache DeltaSpike genauer betrachten, da wir hier auch alternative Conversation -Konzepte kennenlernen werden. Somit haben wir die wichtigsten Integrationspunkte zwischen CDI und JSF bereits theoretisch kennengelernt und es ist Zeit IdeaFork mit einer simplen Weboberfläche zu erweitern. In unserer Web-Applikation legen wir neben einer einfachen XHTML-Datei für JSF noch die Konfigurationsdatei WEB-INF/web.xml an und konfigurieren das Faces-Servlet für JSF. In unserem Fall definieren wir zusätzlich die eben erstellte XHTML-Datei als Startpunkt.

 

Im nächsten Schritt verwenden wir Twitter-Bootstrap (http://getbootstrap.com), um unser Seitentemplate visuell ansprechender zu gestalten. Hierfür werden primär HTML-Tags verwendet. Nur an Stellen an denen es unerlässlich ist, verwenden wir JSF Standardkomponenten. Somit bleiben wir in IdeaFork unabhängig von einer zusätzlichen Komponentenbibliothek für JSF. Sie können selbstverständlich jede beliebige JSF-Komponentenbibliothek verwenden, da die Integration mit CDI an einem unabhängigen Erweiterungspunkt ansetzt. Dieser erste Schritt ist unabhängig von CDI und daher werden wir die Details überspringen. Im Git-Repository von IdeaFork sind die erforderlichen Änderungen in einem Commit zusammengefasst und können einfach nachvollzogen werden.

 

Unabhängig vom Layout sollte einer der ersten Schritte das Hinzufügen einer "Messages"-Komponente sein, damit Nachrichten wie bspw. Fehlermeldungen angezeigt werden können. In IdeaFork wollen wir den Nachrichtenbereich visuell etwas aufbessern und zeigen globale Nachrichten in einem Message-Panel an. Hierzu betten wir die Standardkomponente h:messages in ein Panel, welches mit Hilfe von Twitter-Bootstrap formatiert ist, ein. Damit wir kein leeres Panel anzeigen, falls es keine Meldungen gibt, können wir diesen Bereich standardmäßig ausblenden. In Listing Via EL-Expression auf ein CDI-Bean zugreifen wird deutlich, dass wir uns hier dem rendered -Attribut bedienen. Innerhalb der Panel-Group ist folglich neben einigen Tags zur Formatierung auch die h:messages -Komponente zu finden. Der für uns entscheidende Teil ist hier allerdings der Inhalt von rendered . Die EL-Expression #{messageController.globalMessageAvailable} verrät noch nicht, dass messageController ein CDI-Bean referenziert. Erst durch Listing CDI-Bean mit Namen wird dies deutlich.
<h:panelGroup layout="block" class="panel"
  rendered="#{messageController.globalMessageAvailable}">
    <!-- message panel content -->
</h:panelGroup>
@javax.enterprise.inject.Model
public class MessageController {
  public boolean isGlobalMessageAvailable() {
    return !FacesContext.getCurrentInstance()
      .getMessageList(null).isEmpty();
  }
}

Durch die Stereotype-Annotation @Model definieren wir in Listing CDI-Bean mit Namen ein @javax.inject.Named und @javax.enterprise.context.RequestScoped CDI-Bean. Die Namenskonvention entspricht jener von klassischen JSF-Managed Beans. Der EL-Ausdruck #{messageController.globalMessageAvailable} referenziert somit ein CDI-Bean, welches die Methode isGlobalMessageAvailable zur Verfügung stellt. Über diese Methode können wir auswerten, ob es globale Faces-Messages gibt oder nicht und folglich das dazugehörige Message-Panel ein- oder ausblenden.

 

Als nächstes wollen wir unser Menü mit Leben füllen. Hierfür ersetzen wir die HTML-Links, welche als Platzhalter dienten, mit h:commandLink -Komponenten. Diese Menülinks verwenden ein CDI-Bean namens menuBean , welches äquivalent zu messageController mit @Model annotiert ist. In unserem Fall heißt die Java-Klasse jedoch MenuController . Um dennoch den Namen menuBean verwenden zu können und somit die Default-Namenskonvention anzupassen, müssen wir den durch @Model vorgegebenen Namen mit der expliziten Verwendung von @Named(''menuBean'') abändern.

 

Listing CDI-Bean mit angepasstem Namen zeigt, dass wir an einigen Stellen den eingeloggten User überprüfen oder zurücksetzen müssen, damit sich das Menü wie erwartet verhält. Den eingeloggten User speichern wir im Session-scoped ActiveUserHolder -Bean, welches wir in das MenuController -Bean injizieren und verwenden. Die Klasse ActiveUserHolder selbst ist mit @Model annotiert. Zusätzlich übersteuern wir den Scope, welcher durch @Model vorgegeben ist, durch die explizite Verwendung von @javax.enterprise.context.SessionScoped .
Tipp: Anpassungen von @Model reduzieren die Vorteile dieses Stereotypes erheblich, da sich die Anzahl der Annotations nicht reduziert. Die gezeigten Beispiele sollen primär veranschaulichen, dass solche Anpassungen grundsätzlich möglich sind. Die explizite Verwendung von @Named kann den Aufwand bei einem Refactoring reduzieren, da der Name des Beans zumindest im ersten Schritt unverändert bleibt und sich dennoch der Namen der Klasse ändern kann. In der Regel ist die Übersteuerung des Bean-Namens und des Scopes bei eigenen Stereotype-Annotationen sinnvoller, da eine Stereotype-Annotation die primären Eigenschaften der jeweiligen ''Bean-Kategorie'' bzw. -Rolle definiert und im Idealfall die Aussagekraft erhöht. In Ausnahmefällen können diese Vorgaben für einzelne Beans übersteuert werden. Dennoch bleibt zumindest der Vorteil der höheren Aussagekraft.
@Named("menuBean")
@Model
public class MenuController {
  @Inject
  private ActiveUserHolder userHolder;

  public String home() {
    return "/pages/index.xhtml";
  }

  public String login() {
    return "/pages/user/login.xhtml";
  }

  public String logout() {
    userHolder.setAuthenticatedUser(null);
    return "/pages/user/login.xhtml";
  }

  public String start() {
    if (userHolder.isLoggedIn()) {
      return "/pages/idea/overview.xhtml";
    }
    return "/pages/user/login.xhtml";
  }

  public String register() {
    return "/pages/user/registration.xhtml";
  }
}
@Named
@SessionScoped
public class ActiveUserHolder implements Serializable {
  private User authenticatedUser;

  public void setAuthenticatedUser(User authenticatedUser) {
    this.authenticatedUser = authenticatedUser;
  }

  public boolean isLoggedIn() {
    return authenticatedUser != null && !authenticatedUser.isTransient();
  }

  public User getAuthenticatedUser() {
    return authenticatedUser;
  }
}

Die Methode isTransient ist in BaseEntity neu hinzugekommen und wertet aus, ob die Versionsnummer bereits gesetzt ist. Dies wird später vor allem in Kombination mit JPA relevant, da wir nur persistente User -Entitäten für das Login akzeptieren wollen. In der Klasse MenuController referenzieren wir bereits Seiten als Navigationsziel, welche derzeit noch nicht vorhanden sind. Daher erstellen wir im nächsten Schritt die Seiten login.xhtml und registration.xhtml . Je Seite verwenden wir einen eigenen Controller, der genau für diese Seite zuständig ist. Vorerst sind die meisten unserer Controller Request-Scoped und müssen, wie zuvor erwähnt, mit @Named annotiert werden, damit sie in einer EL-Expression angesprochen werden können. Wir könnten auch hier statt dieser beiden Annotationen @Model verwenden. Allerdings ist ein eigener Stereotyp names @ViewController wesentlich aussagekräftiger. Abgesehen davon ist diese Stereotyp-Annotation eine inhaltliche Kopie von @Model . Listing CDI-Beans als View-Controller zeigt die Controller-Implementierungen LoginViewCtrl für login.xhtml , sowie RegistrationViewCtrl für registration.xhtml .
@ViewController
public class LoginViewCtrl {
  @Inject
  private UserService userService;

  @Inject
  private ActiveUserHolder userHolder;

  private String email;
  private String password;

  public String login() {
    userService.login(email, password);

    final String message;
    final String navigationTarget;
    FacesMessage.Severity severity = FacesMessage.SEVERITY_INFO;
    if (userHolder.isLoggedIn()) {
      message = "Welcome " +
        userHolder.getAuthenticatedUser().getNickName() + "!";
      navigationTarget = "/pages/idea/overview.xhtml";
    } else {
      message = "Login failed!";
      severity = FacesMessage.SEVERITY_ERROR;
      navigationTarget = null;
    }

    FacesContext.getCurrentInstance()
      .addMessage(null, new FacesMessage(severity, message, message));
    return navigationTarget;
  }

  //+ getter and setter
}

@ViewController
public class RegistrationViewCtrl {
  @Inject
  private UserService userService;

  private User newUser = new User();

  public String register() {
    User registeredUser = userService.registerUser(this.newUser);

    final String message;
    final String targetPage;
    FacesMessage.Severity severity = FacesMessage.SEVERITY_INFO;
    if (registeredUser != null) {
      message = "Registration successful!";
      targetPage = "/pages/user/login.xhtml";
    } else {
      message = "Registration failed!";
      severity = FacesMessage.SEVERITY_ERROR;
      targetPage = null;
    }

    FacesContext.getCurrentInstance()
      .addMessage(null, new FacesMessage(severity, message, message));
    return targetPage;
  }

  public User getNewUser() {
    return newUser;
  }
}

Die hier implementierte Logik ist sehr einfach gehalten. Derzeit verwenden wir noch unsere In-Memory Repositories. Sobald wir EJBs hinzufügen wird sich dies ändern. Als Vorbereitung für diese Änderung erstellen wir die Klasse UserService , welche für die Registrierung und für das Login zuständig ist und hierfür verschiedene Methoden von UserManager kombiniert, sowie mit Hilfe eines neu hinzugefügten PasswordManager s den Passworthash berechnet und mit dem gespeicherten Wert vergleicht. Außerdem erweitern wir unsere bisherigen Implementierungen und Tests. Die Entität User wird um ein Passwortfeld und UserRepository um die Methode loadByEmail erweitert. Dementsprechend muss die Methode createUserFor von UserManager erweitert werden. Die dazugehörigen Implementierungen und Änderungen sind im Git-Repository übersichtlich in einem Commit zusammengefasst und können mit dem bisher erworbenen Wissen einfach nachvollzogen werden.
Tipp: Obwohl UserService ein injiziertes Feld verwendet ist die Implementierung implizit zustandslos, da nur eine Contextual-Reference als Proxy injiziert wird. Die Proxy-Instanz selbst ist Thread-safe und kann bei Bedarf de-/serialisiert bzw. notfalls durch manuelle Injizierung jederzeit wiederhergestellt werden. Ist Thread-Sicherheit relevant, dann muss diese auch in allen injizierten Beans sichergestellt werden. Würden wir an dieser Stelle ein Session-scoped Bean injizieren, so wäre nur UserService inklusive der Contextual-Reference Thread-safe. Ein Session-scoped Bean müsste selbst Maßnahmen ergreifen, damit dessen Methoden Thread-safe funktionieren.
@ApplicationScoped
public class UserService {
  @Inject
  private UserManager userManager;

  @Inject
  private ActiveUserHolder userHolder;

  public User registerUser(User newUser) {
    if (userManager.loadByEmail(newUser.getEmail()) == null) {
      User result = userManager.createUserFor(
        newUser.getNickName(), newUser.getEmail(), newUser.getPassword());
      userManager.save(result);
      return userManager.loadById(result.getId());
    }
    return null;
  }

  public void login(String email, String password) {
    User registeredUser = userManager.loadByEmail(email);

    if (registeredUser != null) {
      if (password.equals(registeredUser.getPassword())) {
        userHolder.setAuthenticatedUser(registeredUser);
        return;
      }
    }

    userHolder.setAuthenticatedUser(null);
  }
}

3.4 Servlets mit CDI

IdeaFork besteht in der aktuellen Ausbaustufe aus einem CDI basierten Basismodul, welches unabhängig von einer konkreten UI-Technologie für verschiedene Oberflächen verwendet werden kann. Im vorherigen Abschnitt haben wir den ersten Teil einer JSF/CDI Applikation entwickelt. JSF basiert zwar auf Servlets, jedoch wird dies primär intern umgesetzt. Selbst in JSF-Applikationen gibt es weiterhin Anwendungsgebiete, bei denen auf Servlets zurückgegriffen werden kann. Ein solches Beispiel ist der Datei-Upload. In IdeaFork wollen wir diese Funktionalität nutzen, um via Datei-Upload Ideen zu importieren. Damit wir unser Seiten-Template auch hier wiederverwenden können, erstellen wir eine weitere JSF-Seite namens upload.xhtml . Wie in Listing HTML-Form für den Ideen-Import dargestellt, können wir in diesem Fall eine normale HTML-Form verwenden. Als action tragen wir den Pfad zum Upload-Servlet ein.
<form method="post" enctype="multipart/form-data"
      action="#{jsf.contextPath}/idea/import ">
  <!-- ... -->
</form>

Listing Injizierung in einem Servlet veranschaulicht, dass CDI basierte Injizierung in einem EE6+ Server auch in Servlets verwendet werden kann. IdeaImportServlet verwendet das bereits bekannte Session-scoped Bean ActiveUserHolder , sowie ein Application-scoped FileUploadService , welches an IdeaManager delegiert und das Ergebnis (Erfolg oder Fehlschlag) des Imports in einem Request-Scoped Bean ( ImportSummary ) ablegt. Dies verdeutlicht erneut den Vorteil von Contextual-References. Da für die Injection-Points nur Proxies verwendet werden, kann der CDI-Container immer auf die korrekte Contextual-Instance umleiten und wir müssen uns nicht um die Scopes der Beans kümmern, wie es bspw. bei JSF Managed-Beans der Fall ist. Nach dem Import leiten wir auf die Seite summary.xhtml um, auf welcher wir das Ergebnis anzeigen. Hierfür können wir wie bisher eine JSF-Seite erstellen, welche auf ein CDI-Bean, in diesem Fall ImportSummary , zugreift. Sobald ein CDI-Bean verwendet werden kann, ist es daher möglich dieses als eine Art Zwischen- bzw. Transferspeicher für Daten zu verwenden.
Tipp: Nur bei einer asynchronen Verarbeitung, wie es bei Servlets seit Version 3.0 möglich ist, muss mit etwas mehr Sorgfalt vorgegangen werden. Wird ein neuer Thread nicht vom EE-Server verwaltet, so können nur Scopes verwendet werden, welche unabhängig von einem Thread (und somit Request) sind. Alternativ ist es möglich über proprietäre APIs der Container, Scopes manuell zu starten und zu stoppen. Selbst wenn der Container den neuen Thread startet und somit die korrekte Behandlung der Scopes übernimmt, müssen alle erforderlichen Informationen per Parameter übergeben werden, da es keinen Automatismus in diesem Bereich gibt, der bspw. Daten aus dem ursprünglichen Request-Context in den neuen übernimmt.
@WebServlet("/idea/import ")
@MultipartConfig
public class IdeaImportServlet extends HttpServlet {
  @Inject
  private ActiveUserHolder userHolder;

  @Inject
  private FileUploadService fileUploadService;

  protected void doPost(HttpServletRequest request,
                        HttpServletResponse response)
        throws ServletException, IOException {

    fileUploadService.storeUploadedFiles(
      request.getParts(), userHolder.getAuthenticatedUser());
    request.getRequestDispatcher("/pages/import/summary.xhtml")
      .forward(request, response);
  }
}

CDI-Beans können auch in Servlet-Filtern injiziert werden. Wir können einen einfachen Filter ( UserAwareFilter ) erstellen, um einzelne Bereiche bzw. verschiedene Aktionen abzusichern. Listing Injizierung in einem Servlet-Filter zeigt, dass wir mit Hilfe von ActiveUserHolder , eine einfache Überprüfung durchführen können, ob der aktuelle Benutzer bereits eingelogged ist. Falls dies noch nicht oder nicht mehr der Fall ist, wird statt dem eigentlichen Ziel auf die Login-Seite ( login.xhtml ) umgeleitet.
@WebFilter(urlPatterns = {"/pages/import/*", "/idea/import"})
public class UserAwareFilter implements Filter {
  @Inject
  private ActiveUserHolder userHolder;

  @Override
  public void doFilter(ServletRequest request,
                       ServletResponse response,
                       FilterChain chain)
      throws IOException, ServletException {
    if (userHolder.isLoggedIn()) {
      chain.doFilter(request, response);
    } else {
      request.getRequestDispatcher("/pages/user/login.xhtml")
        .forward(request, response);
    }
  }
  //...
}

3.5 JAX-RS mit CDI

Die EE Spezifikation zu RESTful Services (JAX-RS) wird primär für Applikationen verwendet, die REST (Representational State Transfer) basierte Kommunikation mit der Außenwelt zur Verfügung stellen wollen. In IdeaFork verwenden wir JAX-RS für den Export von Ideen im JSON-Format. Durch die eingangs erwähnte Grundregel unterstützen bereits EE6-Server CDI basierte Injizierung in JAX-RS Ressourcen, obwohl die JAX-RS Spezifikation selbst in Version 1.1 CDI nicht erwähnt. Wie bei JAX-RS üblich beginnen wir bei Listing Konfiguration JAX-RS Application mit der Registrierung eines Basispfades und der Konfiguration der Ressourcen. Als Basispfad wählen wir public . Außerdem stellen wir eine Klasse ( IdeaExporter ) zur Verfügung.
@ApplicationPath("/public")
public class RestApplicationConfig extends Application {
  @Override
  public Set<Class<?>> getClasses() {
    return new HashSet<Class<?>>() {{
      add(IdeaExporter.class);
    }};
  }
}

Listing JAX-RS Ressource mit CDI Injection-Points veranschaulicht, dass wir neben der JAX-RS spezifischen Injizierung via @Context auch CDI basierte Injizierung verwenden können. Allerdings müssen wir unterscheiden, bei welchen Injection-Points wir @Context und bei welchen @Inject verwenden. Sollten Sie für JAX-RS Artefakte wie bspw. HttpServletResponse versehentlich @Inject statt @Context verwenden, dann werden Sie dies spätestens beim nächsten Applikationsstart bemerken, da dieser mit einer UnsatisfiedResolutionException abgebrochen wird.
@Path("/idea/")
@Produces(APPLICATION_JSON)
public class IdeaExporter {
  @Inject
  private IdeaManager ideaManager;

  @Inject
  private UserManager userManager;

  @Inject
  private ActiveUserHolder userHolder;

  @Context
  private HttpServletResponse response;

  @GET
  @Path("/export/all")
  public Response allIdeasOfCurrentUser() {
    User authenticatedUser = userHolder.getAuthenticatedUser();

    if (authenticatedUser == null) {
      try {
        return Response.temporaryRedirect(
          UriBuilder.fromPath("../pages/user/login.xhtml").build())
          .build();
      } catch (Exception e) {
        return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
          .build();
      }
    }
    return Response.ok(ideaManager.loadAllOfAuthor(authenticatedUser))
      .header(/*...*/)
      .build();
  }

  @GET
  @Path("/export/{nickname}")
  public List<Idea> allIdeasOfUser(
    @PathParam("nickname") String nickName) {
      response.setHeader(/*...*/);
      User loadedUser = userManager.loadByNickName(nickName);
      return ideaManager.loadAllOfAuthor(loadedUser);
  }
}

IdeaExporter definiert zwei Endpunkte. Via /public/idea/export/all werden alle Ideen des aktuell eingeloggten Benutzers mit Hilfe von IdeaManager geladen und als Response an den JAX-RS Container weitergegeben, welcher für die Umwandlung nach JSON sorgt. Wird dieser Endpunkt von einer anonymen Quelle aufgerufen, dann wird eine temporäre Umleitung zur Login-Seite ( login.xhtml ) veranlasst. Auch hier können wir wie bisher auf ActiveUserHolder zurückgreifen. Unabhängig von einem vorgelagerten Login ist der zweite Endpunkt. Mit diesem können alle Ideen eines bestimmten Benutzers abgefragt werden. Hierfür muss die Adresse des Endpunkts ( /public/idea /export/{nickname} ) nur mit einem gültigen Usernamen parametrisiert werden. Dieser wird an IdeaManager weitergeleitet, welcher eine entsprechende Ergebnisliste liefert. Diese Ergebnisliste wird anschließend vom JAX-RS Container wieder in JSON umgewandelt. Bei einem Export wollen wir jedoch nicht sämtliche internen Informationen vollständig in das Ergebnis übernehmen. Statt einer manuellen Nachbearbeitung können wir eine Datenprojektion verwenden. Diese wird allerdings nicht von JAX-RS selbst unterstützt. Daher müssen wir auf die proprietäre Funktionalität von Jackson zurückgreifen. Hierfür erweitern wir den bestehenden ObjectConverter um eine Methode. Listing JSON Konvertierung mit View zeigt einen Ausschnitt des angepassten Converters.
@ExternalFormat(ExternalFormat.TargetFormat.JSON)
@JacksonConverter
public class JSONConverterJackson implements ObjectConverter {
  //...

  @Override
  public String toString(Object entity, Class typeSafeDataView) {
    try {
      ObjectMapper objectMapper = new ObjectMapper();
      if (typeSafeDataView != null) {
        objectMapper.configure(
          MapperFeature.DEFAULT_VIEW_INCLUSION, false);

        return objectMapper.writerWithView(typeSafeDataView)
          .writeValueAsString(entity);
      }
      return objectMapper.writeValueAsString(entity);
    } catch (JsonProcessingException e) {
      throw new IllegalArgumentException(e);
    }
  }
}

Damit dieser ObjectConverter auch von JAX-RS verwendet wird, müssen wir einen Adapter zur Verfügung stellen. Listing Manuelle CDI Injizierung in Message-Body Writer zeigt den erforderlichen MessageBodyWriter für JAX-RS. Als View für die Datenprojektion übergeben wir dem ObjectConverter die selbst erstellte Marker-Klasse ExportView.Public.class . Um beim Export nur einen Teil der Daten zu verwenden, müssen wir in den Klassen Idea und User die entsprechenden Getter-Methoden mit @JsonView(ExportView.Public.class) markieren.
@Provider
@Produces(MediaType.APPLICATION_JSON)
public class CustomJsonWriter implements MessageBodyWriter<Object> {
  @Inject
  @ExternalFormat(JSON)
  private ObjectConverter objectConverter;

  @Override
  public boolean isWriteable(Class<?> rawType,
                             Type genericType,
                             Annotation[] annotations,
                             MediaType mediaType) {
    return true;
  }

  @Override
  public void writeTo(Object o,
                      Class<?> rawType,
                      Type genericType, Annotation[] annotations,
                      MediaType mediaType,
                      MultivaluedMap<String, Object> httpHeaders,
                      OutputStream entityStream) throws IOException {
    entityStream.write(
      objectConverter.toString(o, ExportView.Public.class).getBytes());
  }

  @Override
  public long getSize(Object o,
                      Class<?> rawType,
                      Type genericType,
                      Annotation[] annotations,
                      MediaType mediaType) {
    return -1;
  }
}

CustomJsonWriter definiert einen Injection-Point für ObjectConverter , wie wir ihn bereits früher verwendet haben. Da MessageBodyWriter -Implementierungen jedoch keine Injection-Points unterstützen, müssen wir dies selbst anstoßen. Dies können wir in RestApplicationConfig veranlassen. Listing Erweiterte JAX-RS Application (nicht portabel) zeigt als Ausschnitt von RestApplicationConfig die Methode getSingletons . In dieser Methode wird eine neue Instanz der Klasse CustomJsonWriter manuell erstellt und danach der Helper-Methode injectFields übergeben, in welcher die manuelle Injizierung durchgeführt wird. Abschließend wird die Instanz dem Ergebnis-Set hinzugefügt, welches später vom JAX-RS Container unverändert verwendet wird. Die in Erweiterte JAX-RS Application (nicht portabel) illustierte minimale Implementierung ist allerdings nicht vollständig portabel. Im Git-Repository von IdeaFork wird daher das Ergebnis in einem Set gecached. Dieser Trick funktioniert für die getesteten EE-Server. Eine vollständige Portabilität ist nicht garantiert, da JAX-RS in Version 1.1 einen solchen Anwendungsfall nicht berücksichtigt.
@Override
public Set<Object> getSingletons() {
  final CustomJsonWriter jsonWriter = new CustomJsonWriter();
  CdiUtils.injectFields(jsonWriter); //not portable at this point
  return new HashSet<Object>() {{
    add(jsonWriter);
  }};
}

Die eben erwähnte Helper-Methode injectFields kann ebenfalls von uns in wenigen Schritten implementiert werden. Ein EE-Container muss den BeanManger via JNDI unter java:comp/BeanManager zur Verfügung stellen. Dieser Lookup ist nur dann erforderlich, wenn wir uns in einem Bereich der Applikation befinden, der nicht durch den CDI-Container verwaltet wird. Mit Hilfe des BeanManger s erzeugen wir uns manuell eine Instanz vom Typ InjectionTarget , über welche wir abschließend mit der Methode inject die manuelle Injizierung an den CDI-Container delegieren können.
public class CdiUtils {
  public static <T> T injectFields(T instance) {
    if (instance == null) {
      return null;
    }

    BeanManager beanManager = resolveBeanManagerViaJndi();

    if (beanManager == null) {
      return instance;
    }

    CreationalContext creationalContext =
      beanManager.createCreationalContext(null);

    AnnotatedType annotatedType =
      beanManager.createAnnotatedType(instance.getClass());
    InjectionTarget injectionTarget =
      beanManager.createInjectionTarget(annotatedType);
    injectionTarget.inject(instance, creationalContext);
    return instance;
  }

  private static BeanManager resolveBeanManagerViaJndi() {
    try {
      return (BeanManager) new InitialContext()
        .lookup("java:comp/BeanManager");
    } catch (NamingException e) {
      return null;
    }
  }
}

Tipp: Die Implementierung von injectFields ist portabel und vollständig. Allerdings kann dieser Ansatz nicht an allen Punkten einer Applikation portabel verwendet werden. Verschiedene EE-Server und teilweise auch unterschiedliche Versionen eines Servers agieren beim Aufruf innerhalb von getSingletons sehr unterschiedlich. Unter anderem ist nicht garantiert, dass zu diesem Zeitpunkt ein valider CDI-Container zur Verfügung steht. Dies liegt jedoch nicht an CDI selbst, sondern an der Integration verschiedener EE-Spezifikationen in den Servern. Dieses Beispiel veranschaulicht, dass selbst korrekte Implementierungen im Ausnahmefall nicht immer auf direktem Weg zum gewünschten Ergebnis führen. In einer realen Applikation wäre es bei einer solchen Einschränkung einfacher auf CdiUtils#getContextualReference zurückzugreifen, da hiermit erst zum letztmöglichen Zeitpunkt auf den CDI-Container zugegriffen wird.

 

Die Umsetzung und manuelle Initialisierung eines MessageBodyWriter s ist somit fertig gestellt. Im nächsten Schritt können wir einen der Endpunkte direkt in einer JSF-Seiten ansprechen. Hierfür genügt der in Listing HTML-Link auf JAX-RS Endpoint gezeigten einfache HTML-Link.
<a href="#{jsf.contextPath}/public/idea/export/all" class="btn">
  <span class="glyphicon glyphicon-import"/> Export My Ideas
</a>

Würden wir zu diesem Zeitpunkt den Server starten, so würde der Export bereits funktionieren. Allerdings bekommen wir nicht bei jedem EE-Server das gleiche Ergebnis. Grund hierfür sind die sog. BDA-Regeln, welche wir in diesem Kapitel noch im Detail kennenlernen werden. Einige Details dieser Regeln sind nicht eindeutig definiert bzw. stark umstritten. Für uns bedeuten sie vorerst, dass wir einen Konfigurationseintrag duplizieren müssen, damit unsere Applikation portable bleibt. Konkret müssen wir den alternativen Stereotyp @JacksonConverter in WEB-INF/beans.xml erneut aktivieren, damit die alternativen Implementierungen in der Web-Applikation ebenfalls aktiv sind. Weitere Details zu diesem Thema werden wir in [ Bean-Deployment Archive mit Java EE] betrachten.

3.6 EJB mit CDI

Aus J2EE Zeiten haben EJBs bis heute noch einen angeschlagenen Ruf. Spätestens seit Java EE6 ist dies jedoch kaum noch berechtigt. EE6-Server selbst sind fast durchgängig sehr schnell beim Start. Lange Wartezeiten während der Entwicklung gehören somit der Vergangenheit an. Das Programmiermodell wurde ebenfalls wesentlich effizienter. Mittlerweile genügt es im einfachsten Fall eine Annotation zu verwenden, um ein POJO in ein EJB zu verwandeln. EJBs können mit CDI-Beans mit zusätzlichen Services, wie bspw. Transaktionen, verglichen werden. In IdeaFork können wir beginnen unsere Services auf EJBs umzustellen. Bisher haben wir FileUploadService und UserService als Application-scoped CDI-Bean definiert. Der äquivalente EJB-Typ ist ein Singleton EJB. Daher können wir @ApplicationScoped durch @javax.ejb.Singleton ersetzen. Wir bekommen mit dieser Änderung nicht nur per Default transaktionale Beans, wovon wir später profitieren werden, sondern alle für @Singleton definierten Zusatzfunktionalitäten. Hier liegt auch ein Fallstrick vergraben. Eine dieser Zusatzfunktionalitäten ist die Synchronisierung von Methodenaufrufen, welche zu einem ungewollten Flaschenhals in der Applikation werden kann. Um dies zu vermeiden, müssen wir noch @ConcurrencyManagement(ConcurrencyManagementType.BEAN) hinzufügen. Wir können in unserem Fall auf die Synchronisierung verzichten, weil wir keinen Zustand in den Bean-Instanzen verwalten. Denn Contextual-References auf CDI-Beans werden nur vom CDI-Container gesetzt und verändern sich nach der Erzeugung des Beans nicht. Die injizierte Proxy-Instanz selbst kann selbstverständlich mit parallelen Methodenaufrufen ohne Einschränkungen umgehen. Nur in der referenzierten Contextual-Instance muss darauf geachtet werden, ob parallele Aufrufe speziell behandelt werden müssen. In unserem Fall trifft dies nicht zu und daher ist keine weitere Änderung erforderlich. Listing EJB mit CDI Injection-Points zeigt, dass wir weiterhin CDI basierte Injizierung verwenden können. In unseren bisherigen Application-scoped CDI-Beans konnten die Methoden ebenfalls problemlos parallel aufgerufen werden. Dieser Aspekt ändert sich somit durch unsere Umstellung nicht.
@Singleton
@ConcurrencyManagement(BEAN)
public class FileUploadService {
    private static final Charset UTF8 = Charset.forName("UTF-8");

    @Inject
    private IdeaManager ideaManager;

    @Inject
    private ImportSummary importSummary;

    public void storeUploadedFiles(Collection<Part> parts, User user) {
      for (Part part : parts) {
        String fileName = getFileName(part);
        try {
          BufferedReader bufferedReader = new BufferedReader(
            new InputStreamReader(part.getInputStream(), UTF8));
          String ideaToImportString = bufferedReader.readLine();

          while (ideaToImportString != null) {
            try {
              Idea importedIdea =
                ideaManager.importIdea(user, ideaToImportString);
              importSummary.addImportedIdea(importedIdea);
            } catch (Exception e) {
              importSummary.addFailedImport(ideaToImportString);
            }
            ideaToImportString = bufferedReader.readLine();
          }
        } catch (Exception e) {
          //...
        }
      }
    }
    //...
}

Soll ein CDI-Scope für ein EJB verwendet werden, so ist dies mit der Annotation @Stateful (statt @Singleton ) möglich. Der EJB-Container erzeugt die Instanz und übergibt diese dem CDI-Container zur weiteren Verwaltung. In IdeaFork fügen wir als nächstes die Seite create.xhtml hinzu, um eine neue Idee zu erstellen. Als View-Controller können wir direkt ein EJB verwenden. Wie Listing EJB mit CDI-Stereotype zeigt können wir hierfür zu der bisherigen @ViewController Annotation noch @javax.ejb.Stateful hinzufügen. Das resultierende EJB ist durch diese Kombination wie erwartet Request-scoped und kann in EL-Expressions, in unserem Fall in create.xhtml , referenziert werden.
@Stateful
@ViewController
public class IdeaCreateViewCtrl implements Serializable {
  @Inject
  private IdeaManager ideaManager;

  @Inject
  private ActiveUserHolder userHolder;

  private String topic;
  private String category;
  private String description;

  public String save() {
    Idea ideaToSave = ideaManager.createIdeaFor(
      topic, category, userHolder.getAuthenticatedUser());
    ideaToSave.setDescription(description);
    ideaManager.save(ideaToSave);
    return "/pages/idea/overview.xhtml";
  }
  //+ Getter- and Setter-Methods
}

Die Methode #save ist in diesem Fall eine klassische JSF Action-Methode, welche in create.xhtml wie üblich von einer Command-Komponente verwendet wird und an die entsprechenden Methoden vom injizierten IdeaManager delegiert. Da wir durch diesen Ansatz einen transaktionalen View-Controller bekommen, läuft die gesamte Ausführung der Action-Methode(n), sowie jede Getter- und Setter- Methode, in einer Transaktion ab. In unserem Fall ist dies unproblematisch, bei komplexeren Konstellationen ist bereits eine transaktionale Action-Methode oftmals nicht erwünscht, sofern mehrere unabhängige Operationen durchgeführt werden sollen. Außerdem entsteht, ohne Eingrenzung via @javax.ejb.TransactionAttribute und @javax.ejb.Lock , ein unnötiger Overhead beim Zugriff auf Getter- bzw. Setter-Methoden. Aus diesen Gründen werden normalerweise EJBs primär für Services verwendet. Technisch gesehen ist es jedoch problemlos möglich EJB als View-Controller zu verwenden.

 

Im nächsten Schritt legen wir die Seite list.xhtml an. Wie der Seitenname bereits vermuten lässt werden die Ideen des eingeloggten Benutzers in einer Übersichtsliste dargestellt. Auch für diese Seite verwenden wir ein EJB als View-Controller. Hierzu erstellen wir die Klasse IdeaListViewCtrl und annotieren sie zusätzlich zu @javax.ejb.Stateful mit unserer Stereotyp-Annotation @ViewController , welche wie bisher @javax.enterprise.context.RequestScoped als Scope vorgibt. Allerdings passen wir diesen vorgegebenen Scope an und bekommen dadurch ein @javax.enterprise.context.SessionScoped -EJB. Die Methode #onPreRenderView wird in list.xhtml als Callback für das PreRenderView -Event verwendet und ist in Listing EJB als Backing-Bean mit Callback dafür verantwortlich, dass im nachfolgendem Rendering-Prozess von list.xhtml immer die aktuelle Liste angezeigt wird.
@Stateful
@SessionScoped
@ViewController
//can be optimized via @TransactionAttribute and @Lock
public class IdeaListViewCtrl implements Serializable {
  @Inject
  private IdeaManager ideaManager;

  @Inject
  private ActiveUserHolder userHolder;

  private List<Idea> ideaList;

  public void onPreRenderView() {
    ideaList = ideaManager.loadAllOfAuthor(
      userHolder.getAuthenticatedUser());
  }

  public void deleteIdea(Idea currentIdea) {
    this.ideaManager.remove(currentIdea);
  }

  public List<Idea> getIdeaList() {
    return ideaList;
  }
}

@Stateful
@SessionScoped
@ViewController
//can be optimized via @TransactionAttribute and @Lock
public class IdeaEditViewCtrl implements Serializable {
  @Inject
  private IdeaManager ideaManager;

  private Idea currentIdea;

  public String editIdea(Idea currentIdea) {
    this.currentIdea = currentIdea;
    return "/pages/idea/edit.xhtml";
  }

  public String save() {
    ideaManager.save(currentIdea);
    return "/pages/idea/list.xhtml";
  }

  public Idea getCurrentIdea() {
    return currentIdea;
  }
}

Gleiches gilt für IdeaDetailsViewCtrl und IdeaForkViewCtrl in Listing CDI-Bean mit Stereotype und übersteuertem Scope und CDI-Bean als Backing-Bean mit Injection-Point zu anderem Backing-Bean , allerdings handelt es sich bei diesen Beans um normale CDI-Beans, welche als View-Controller verwendet werden. Dies repräsentiert auch den klassischen Fall, bei welchem primär View-Controller Logik umgesetzt wird und der Rest an injizierte Beans delegiert wird.
@SessionScoped
@ViewController
public class IdeaDetailsViewCtrl implements Serializable {
  @Inject
  private IdeaManager ideaManager;

  private Idea currentIdea;

  private Stack<Idea> displayedIdeas = new Stack<Idea>();

  public String showIdea(Idea currentIdea) {
    this.currentIdea = currentIdea;
    return "/pages/idea/details.xhtml";
  }

  public void showOriginal() {
    displayedIdeas.push(currentIdea);
    currentIdea = ideaManager.loadById(currentIdea.getBaseIdeaId());
  }

  public String back() {
    if (displayedIdeas.empty()) {
      return "/pages/idea/list.xhtml";
    }
    currentIdea = displayedIdeas.pop();
    return null;
  }

  public Idea getCurrentIdea() {
     return currentIdea;
  }
}

@SessionScoped
@ViewController
public class IdeaForkViewCtrl implements Serializable {
  @Inject
  private IdeaEditViewCtrl ideaEditViewCtrl;

  @Inject
  private IdeaManager ideaManager;

  @Inject
  private ActiveUserHolder userHolder;

  public String forkIdea(Idea currentIdea) {
    Idea forkedIdea = ideaManager.forkIdea(
      currentIdea, userHolder.getAuthenticatedUser());
    ideaEditViewCtrl.editIdea(forkedIdea);
    return "/pages/idea/edit.xhtml";
  }
}

Abgesehen von CDI basierter Injizierung und von CDI-Scopes können EJBs auch Observer-Methoden für CDI-Events enthalten. In Kombination mit @javax.ejb.Asynchronous ergibt sich ein interessanter Vorteil, da die eigentliche Logik in einer solchen Observer-Methode asynchron ausgeführt wird. In Listing EJB mit asynchroner CDI-Observer Methode verwenden wir diesen Vorteil, um Login- und Logout-Ereignisse je User asynchron aufzuzeichnen. Die asynchrone Observer-Methode #onUserAction delegiert hierbei auf direktem Weg an das synchron implementierte UserActionRepository . Da die gesamte Methode #onUserAction in einem separaten Thread ausgeführt wird, erfolgt die Verarbeitung aus Sicht der Event-Quelle asynchron.
@Stateless
public class StatisticService {
  @Inject
  private UserActionRepository userActionRepository;

  @Asynchronous
  public void onUserAction(@Observes UserActionEvent userActionEvent) {
    userActionRepository.save(userActionEvent.getUserAction());
  }

  //...
}

3.7 JPA mit CDI

In Java EE6 gibt es noch keine mitgelieferte Integration von CDI in JPA. Erst Java EE7 definiert einen rudimentären Funktionsumfang für Entity-Listener. Entity-Listener werden zwar weiterhin nicht direkt von CDI verwaltet, jedoch steht Injizierung von CDI-Beans sowie die Möglichkeit von Lifecycle-Callbacks ( PostConstruct und PreDestroy ) zur Verfügung. Eine Option, welche bereits seit EE6 zur Verfügung steht, ist natürlich das manuelle Auffinden bzw. die manuelle Injizierung. Beide Varianten haben wir bereits kennengelernt und mit der Implementierung von CdiUtils umgesetzt. Dennoch können wir CDI verwenden um Teile von JPA zu vereinfachen und somit besteht der nächste Schritt in der Umstellung von IdeaFork auf JPA. Die bisherigen In-Memory Repositories können wir weiterhin für unsere Unit-Tests verwenden. Hierfür verschieben wir sie in das Test-Verzeichnis des Modules und annotieren sie mit dem selbst erstellten alternativen Stereotyp MockedRepository , welchen wir in der Konfigurationsdatei beans.xml des Testmoduls aktivieren. Dies ist äquivalent zu dem alternativen Stereotyp JacksonConverter , welchen wir bereits erstellt haben. Die neuen JPA basierten Repositories annotieren wir hingegen wie gewohnt mit unserem Stereotyp @Repository . Da wir für MockedRepository aus Listing Stereotyp-Annotation annotiert mit Stereotyp die gleichen Definitionen übernehmen wollen, können wir diese Stereotyp-Annotation ebenfalls mit der @Repository Annotation versehen. All diese Änderungen basieren auf den bisher erworbenen Erkenntnissen. Daher gehen wir an dieser Stelle auf diese Details nicht näher ein. Im Git-Repository von IdeaFork sind die erforderlichen Änderungen in einem Commit zusammengefasst und können einfach nachvollzogen werden.
@Target(TYPE)
@Retention(RUNTIME)

@Alternative

@Stereotype
@Repository
public @interface MockedRepository {
}
Ein zentraler Bestandteil von JPA ist der EntityManager . Seit Java EE6 vereinfacht CDI dessen Verwendung. Mit Java EE5 musste dieser noch überall via @PersistenceContext injiziert werden. Somit wurde diese Annotation mit all den erforderlichen Parametern zur Injizierung über mehrere Klassen der Applikation verstreut. Eine elegante Alternative hierzu ist die Verwendung eines CDI-Producers, wie wir ihn bereits im Kapitel CDI Grundkonzepte kennengelernt haben. Bei der Beschreibung des Grundkonzepts haben wir erfahren, dass Producer-Felder in Kombination mit Ressource-Injection in einem EE-Server ein paar Codezeilen einsparen können. Listing Entity-Manager Producer-Feld zeigt eine solche Ressource-Injection via @PersistenceContext . Gleichzeitig ist dieser Ressource-Injection-Point durch die Verwendung von @Produces ein CDI-Producer. Nach der Instantiierung der Klasse injiziert der EE-Server einen EntityManager -Proxy. Ab diesem Zeitpunkt kann der CDI-Container diesen Proxy durch den Producer als Contextual-Instance verwenden. Da der EE-Server bereits einen Proxy erzeugt, müssen wir keinen zusätzlichen Scope für den EntityManager definieren, um eine sinnvolle Verwendung zu ermöglichen. In Listing Entity-Manager Producer-Methode ist die gleiche Funktionalität mit einer Producer-Methode zu sehen. Auch hier wird ein dependent-scoped Bean definiert. In beiden Fällen ist EntityManagerProducer ein Application-scoped Bean. Diese Definition ermöglicht, dass die Instanz nur einmal erzeugt und der EntityManager -Proxy ebenfalls nur einmal injiziert werden muss.
@ApplicationScoped
public class EntityManagerProducer {
  @Produces
  @PersistenceContext(name = "ideaForkPU")
  private EntityManager entityManager;
}
Im Vergleich zum Producer-Feld bietet die Producer-Methode den Vorteil, dass der Debugging-Prozess vereinfacht wird. Die hierfür zusätzliche Methode sollte nicht weiter ins Gewicht fallen, da diese nur einmal zentral implementiert werden muss. Aus diesen Gründen verwendet auch IdeaFork die nachfolgende Variante mit der Producer-Methode.
@ApplicationScoped
public class EntityManagerProducer {
  @PersistenceContext(name = "ideaForkPU")
  private EntityManager entityManager;

  @Produces
  protected EntityManager exposeEntityManagerProxy() {
    return entityManager;
  }
}
In IdeaFork injizieren wir den so erzeugten EntityManager in die Basisklasse unserer JPA-Repositories, welche in Listing Generisches JPA-Repository auszugsweise dargestellt ist.
public abstract class GenericJpaRepository<T extends BaseEntity>
  implements GenericRepository<T> {

    //...

    @Inject
    protected EntityManager entityManager;

    @Override
    public void save(T entity) {
      if (entity.isTransient()) {
        entityManager.persist(entity);
      } else {
        entityManager.merge(entity);
      }
    }

    //...
}

Tipp: Erwähnenswert im Zusammenhang mit JPA ist die Trennung von CDI-Beans und JPA-Entitäten. Instanzen einer Klasse sollten nur durch einen Container (JPA oder CDI) verwaltet werden, um technische Probleme durch die beteiligten Proxy-Bibliotheken zu vermeiden. Technisch gesehen könnten wir Einschränkungen in diesem Bereich einfach ausweichen, indem wir nur Dependent-scoped Beans für Entitäten einsetzen, welche keine Interceptoren oder Decoratoren verwenden. Dadurch können wir Instanzen von Entitäten einfach injizieren. Um zu vermeiden, dass dieser Aspekt in Vergessenheit gerät, verwenden wir in IdeaFork eine solche fachliche Injizierung nicht. Stattdessen bedienen wir uns dem klassischen Schlüsselwort new .

3.8 Bean-Validation mit CDI

Die bisher behandelten EE-Spezifikationen und deren Integration mit CDI decken bereits einen großen Teil an Anwendungsfällen in Geschäftsapplikationen ab. Ein wichtiger Aspekt fehlt jedoch - die Validierung. Die Spezifikation Bean-Validation (JSR 303) feierte, wie CDI selbst, in EE6 ihr Debut. Wie bereits kurz erwähnt, können Contextual-References auf javax.validation.ValidationFactory und javax.validation.Validator via @Inject injiziert werden. In vielen Fällen ist die manuelle Verwendung dieser Artefakte nicht erforderlich. Beispielsweise integrieren sowohl JSF als auch JPA die damals neue Spezifikation von Anfang an. Im Normalfall müssen somit nur die gewünschten Bean-Validation Constraints für die eigentliche Validierung verwendet werden. Am häufigsten kann es erforderlich werden ein CDI-Bean in einem Constraint-Validator zu verwenden, um für die Validierung bspw. Werte aus der Datenbank abzufragen. Dies wird jedoch erst ab EE7 standardmäßig unterstützt. Mit EE6 kann diese Funktionalität jedoch sehr einfach selbst umgesetzt werden. Wir könnten natürlich CDI-Beans jederzeit manuell suchen. Dies müssten wir jedoch in jedem Constraint-Validator erneut machen. Stattdessen wollen wir an einer zentralen Stelle die Erzeugung von Constraint-Validatoren an den CDI-Container delegieren, sofern dieser ein Bean mit dem entsprechenden Typ kennt. Im Kapitel CDI Grundkonzepte haben wir das manuelle Auffinden von Beans bereits kennengelernt. In einem ersten Schritt erweitern wir CdiUtils um die Methode getContextualReference . Listing Manuelle Integration von Bean-Validation und CDI veranschaulicht, dass null zurückgegeben wird, wenn kein entsprechendes Bean vom CDI-Container gefunden wurde. Diese neue Methode können wir in einer eigenen Implementierung von javax.validation.ConstraintValidatorFactory verwenden. Die Implementierung in Listing Manuelle Integration von Bean-Validation und CDI delegiert an defaultFactory , wenn der CDI-Container kein entsprechendes Bean für einen Constraint-Validator findet.
public class BeanAwareConstraintValidatorFactory
  implements ConstraintValidatorFactory {
    private final ConstraintValidatorFactory defaultFactory;

    public BeanAwareConstraintValidatorFactory() {
      defaultFactory = Validation.byDefaultProvider().configure()
        .getDefaultConstraintValidatorFactory();
    }

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(
      Class<T> validatorClass) {
        T managedConstraintValidator =
          CdiUtils.getContextualReference(validatorClass);

        if (managedConstraintValidator == null) {
          managedConstraintValidator = this.defaultFactory
            .getInstance(validatorClass);
        }
        return managedConstraintValidator;
    }
}

Damit BeanAwareConstraintValidatorFactory aktiv wird, müssen wir die voll qualifiziere Klasse noch in der Datei validation.xml aktivieren. Listing Aktivierung der Validator-Factory veranschaulicht die Konfiguration, welche in IdeaFork verwendet wird.
<validation-config>
  <constraint-validator-factory>at.irian.cdiatwork.ideafork.infrastructure
    .BeanAwareConstraintValidatorFactory</constraint-validator-factory>
</validation-config>

In IdeaFork nutzen wir diese neue Funktionalität im Constraint-Validator UniqueUserNameValidator , welcher ein neu hinzugefügtes Constraint namens @UserName validiert. In unserem Fall delegiert der Constraint-Validator im Listing Constraint-Validator als CDI-Bean die Hauptarbeit an das injizierte UserRepository . Bei der Verwendung von @UserName müssen wir nur berücksichtigen, dass wir eine spezielle Validierungsgruppe verwenden müssen, da sonst die Validierung ebenfalls beim Login durchgeführt wird. Abgesehen davon entspricht der Rest den herkömmlichen Regeln, welche durch die Bean-Validation Spezifikation definiert sind. Konkret annotieren wir die Property nickName mit @UserName(groups = UniqueUserName.class) und erweitern die Komponente formGroup.xhtml mit einem optionalen Attribut, um den ebenfalls neu hinzugefügten Tag f:validateBean von außen zu parametrisieren.
@ApplicationScoped
public class UniqueUserNameValidator
  implements ConstraintValidator<UserName, String> {
    @Inject
    private UserRepository userRepository;

    public void initialize(UserName differentName) {
    }

    public boolean isValid(
      String userName,
      ConstraintValidatorContext constraintValidatorContext) {
        return this.userRepository.loadByNickName(userName) == null;
    }
}

Wir könnten die Validierungslogik in UserService#loadByEmail ebenfalls per Bean-Validation Constraint lösen. Allerdings ist es nicht immer erforderlich sämtliche Konsistenzchecks via Bean-Validation abzubilden, da durch die zusätzlichen Validierungsgruppen auch die Komplexität der Constraint-Logik steigt. Ein Vorteil der Validierungslogik mit Bean-Validation Constraints ist, dass JSF die Validierung der Constraints in der Validierungsphase anstößt und im Falle eines Fehlers diesen direkt neben der Eingabekomponente anzeigt und nicht wie zuvor im allgemeinen Bereich für Nachrichten.

3.9 Bean-Deployment Archive mit Java EE

Eine der wenigen Schattenseiten von CDI 1.0 ist die Definition von Bean-Deployment Archiven (BDAs) und deren Umsetzung in den verschiedenen Servern. Selbst EE-Server mit der gleichen CDI-Implementierung haben nicht immer ein einheitliches Verhalten, weil zentrale Aspekte in diesem Bereich vom Integrationscode des EE-Servers definiert werden können. So kann es selbst bei der Migration von einem Weld basierten Server auf einen anderen zu feinen Unterschieden kommen, wenn die Applikation nicht vollständig portabel implementiert ist. Das Konzept der Bean-Deployment Archive wurde eingeführt, um Modulgrenzen für Beans und Konfigurationen zu definieren. Im restriktivsten Fall sind CDI-Beans und Konfigurationen via beans.xml nur für das aktuelle Archiv gültig. Im ersten Moment hört sich dies evt. sinnvoll an. Sobald jedoch Module, wie bspw. JARs, nicht unter der eigenen Kontrolle stehen oder zur Modularisierung der eigenen Applikation genutzt werden, können verschiedene Anwendungsfälle nur mit zusätzlichem Aufwand oder überhaupt nicht umgesetzt werden. Abhängig von der konkreten Konstellation kann dies in schweren Fällen auch Injizierung über Modulgrenzen hinweg betreffen, sowie die Anpassung von Default-Implementierungen eines Moduls, bei welcher eine alternative Implementierung außerhalb des Moduls zur Verfügung gestellt werden soll. Außerdem müssen bspw. Interceptoren für jedes Archiv erneut konfiguriert werden. Da die Definition der BDA-Regeln viel Interpretationsspielraum lässt und bereits für lange Diskussionen gesorgt hat, wurden teilweise alternative Ansätze geschaffen. Einige Applikationsserver gestehen bspw. der Datei beans.xml eine Sonderstellung zu, wenn diese im Verzeichnis WEB-INF platziert wird. Die genaue Umsetzung ist jedoch nicht durch die CDI-Spezifikation gedeckt und das konkrete Verhalten kann sogar zwischen einzelnen Versionen eines Servers unterschiedlich sein.

 

In Enterprise-Archiven (EARs) verschärft sich die Situation, da es hier von der genauen Strukturierung der Applikation abhängen kann, ob und wie verschiedene Konstellationen funktionieren. Da es in manchen Serverversionen sogar zu Seiteneffekten zwischen Beans mehrerer Web-Archiven (WARs) in Kombination mit Klassen in einem geteilten Modul eines EARs kommen kann, ist es ratsam zumindest in EE6 und EE7 auf EARs möglichst zu verzichten. Seit EE6 haben EARs glücklicherweise nur noch eine untergeordnete Rolle, wodurch sich diese Einschränkung in vielen Fällen kaum oder überhaupt nicht bemerkbar macht. Apache OpenWebBeans im Standalone-Betrieb (= manuell für Java SE oder einen Servlet-Container konfiguriert) deaktiviert sogar die BDA-Regeln per Default komplett, wodurch viele Einschränkungen und Hürden nicht auftreten.

 

Selbst bei einer überschaubaren Applikation wie IdeaFork müssen die BDAs für GlassFish 3 anders strukturiert werden als bspw. für AS7, obwohl beide Server Weld als CDI-Implementierung verwenden. Erst durch unterschiedliche Maven-Build-Profile, welche im Git-Repository im Detail ersichtlich sind, kann zur Laufzeit das gleiche Verhalten erzielt werden.

 

Seit CDI 1.1, welches Bestandteil von EE7 ist, wurde diese Thematik teilweise durch eine neue Annotation namens @Priority gelöst. Artefakte werden für die gesamte Applikation aktiviert, sobald diese mit @Priority annotiert sind. Bei alternativen Beans entscheidet die angegebene Priorität welches Bean effektiv aktiv wird und bei Decorators und Interceptors wird die Reihenfolge dieser definiert.

 

Zusammengefasst bedeutet dies, dass bei Modularisierungen und bei der Wahl des Archivtyps möglichst von der einfachsten Variante ausgegangen werden soll. Je komplexer die Strukturierung der Applikation wird, desto wahrscheinlicher sind Einschränkungen, welche durch BDA- bzw. Classloading-Regeln auftreten.