Java EE (J2EE)

Java EE ist eine Sammlung von Technologien / Schnittstellen.

KürzelNameLinks
JMSJava Message ServiceJava Message Service (JMS)
JavaMailJavaMail
CDIContext and Dependency InjectionContext and Dependency Injection (CDI)
JSSJava Servlet APIServlets
JSPJavaServer PagesJava Server Pages
JSFJava Server FacesJava Server Faces
JSTLJavaServer Pages Standard Tag Library
JPAJava Persistence APIDB Zugriff in Java EE
JTAJava Transaction API
EJBEnterprise Java BeansEnterprise JavaBeans
JNDIJava Naming and Directory InterfaceJNDI
JAXBJava Architecture for XML BindingJAXB
JAXPJava API for XML ProcessingJAXP
JAX-RPCJava API for XML-Based Remote Procedure Calls
JAXRJava API for XML Registries
StAXStreaming API for XMLStAX
WSWeb ServicesWebservices
-Web Service Metadata
JAX-WSJava API for XML Web ServicesJAX-WS
JACCJava Authorization Contract for Containers
JAASJava Authentication and Authorization ServiceJava Authentication and Authorization Service (JAAS)
JCAJ2EE Connector Architecture
JAFJavaBeans Activation Framework

Allgemeine Informationen

Java Message Service (JMS)

Um JMS benutzen zu können, braucht man als erstes einen JMS Provider. Dafür stehen verschiedene Programme von verschiedenen Anbietern zur Auswahl, in diesem Beispiel wird der Glassfish Application Sever dafür benutzt.

JMS in GlassFish einrichten

Als erstes muss man zwei JMS Resourcen im Glassfish anlegen: In der Admin Konsole des Glassfish anmelden, dann unter Resources / Verbindungsfactory eine Factory anlegen. In diesem Beispiel mit dem Namen jms/ConnectionFactory. Danach unter Resources / Zielresourcen eine Queue und ein Topic anlegen. Hier mit den Namen jms/MyTopic sowie jms/MyQueue. <a href="java_files/jmsConnectionFactory.jpg">

JMS GlassFish

</a>

JMS Beispiel

Das Beispiel soll folgendes leisten können:

  • Senden
  • Empfangen
  • Empfangen über eine CallBackMethode

Dabei kann entweder ein Topic benutzt werden (der Empfänger muss schon laufen wenn die Nachricht gesendet wird) oder aber eine Queue (die Nachrichten werden für den Empfänger aufbewahrt, wenn er während des Sendes noch nicht gestartet war).

Die Referenzen auf den Glassfish JMS werden über Annotationen automatisch eingefügt, wenn man die Anwendung über das appclient Tool startet:

appclient -client yourJMSClientJar.jar
package de.tgunkel.java.EJB;

import javax.annotation.Resource;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Queue;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;

public class MyJMSClass implements MessageListener
{
    @Resource(mappedName="jms/ConnectionFactory")
    private static ConnectionFactory connectionFactory;

    @Resource(mappedName="jms/MyTopic")
    private static Topic topic;

    @Resource(mappedName = "jms/MyQueue")
    private static Queue queue;

    private void doSend(boolean useTopicInsteadOfQueue) throws JMSException
    {
        Connection connection;
        Session session;
        MessageProducer producer;

        connection      = connectionFactory.createConnection();
        session         = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

        if(useTopicInsteadOfQueue)
        {
            producer        = session.createProducer(topic);
        }
        else
        {
            producer = session.createProducer(queue);
        }

        String[] toBeSend={"Hello World", "How are you", "Bye Bye"};

        for(String s : toBeSend)
        {
            TextMessage message = session.createTextMessage();
            message.setText(s);
            producer.send(message);
        }

        producer.close();
        session.close();
        connection.close();
    }

    private String doReceive(boolean useTopicInsteadOfQueue) throws JMSException
    {
        final long TIMEOUT=2L*60L*1000L;

        Connection connection;
        Session session;

        connection              = connectionFactory.createConnection();
        session                 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageConsumer consumer;

        if(useTopicInsteadOfQueue)
        {
            consumer=session.createConsumer(topic, null, false);
        }
        else
        {
            consumer=session.createConsumer(queue, null, false);
        }

        connection.start();

        String result="";
        TextMessage message;
        do
        {
            message=(TextMessage) consumer.receive(TIMEOUT);
            if(message!=null) result+="|"+message.getText();
        } while(message!=null &amp;&amp; !"Bye Bye".equals(message.getText()));

        consumer.close();
        session.close();
        connection.close();
        return result;
    }

    private static void doReceiverViaCallBackMethod(boolean useTopicInsteadOfQueue) throws JMSException
    {
        final long TIMEOUT=10L*60L*1000L;

        Connection connection;
        Session session;

        connection              = connectionFactory.createConnection();
        session                 = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        MessageConsumer consumer;

        if(useTopicInsteadOfQueue)
        {
            consumer=session.createConsumer(topic, null, false);
        }
        else
        {
            consumer=session.createConsumer(queue, null, false);
        }

        consumer.setMessageListener(new MyJMSClass());

        connection.start();
    }

    public void onMessage(Message message)
    {
        TextMessage textMessage=(TextMessage) message;
        String stringFromTextMessage="";
        try
        {
            stringFromTextMessage=textMessage.getText();
        } catch (JMSException ex)
        {
            stringFromTextMessage="Error: "+ex;
        }
        System.out.println("CallBackMethod received: "+stringFromTextMessage);
    }
}

Um die Klasse über ein Topic zu Kommunikation zu benutzen, erst den Empfänger im Hintergrund starten

MyJMSClass jmsClient=new MyJMSClass();
String messageForMe=jmsClient.doReceive(true);
System.out.println("Receiving Result: "+messageForMe);

Alternativ kann auch eine Callback Methode registriert werden:

MyJMSClass.doReceiverViaCallBackMethod(true);

Dann den Sender starten

MyJMSClass jmsSender=new MyJMSClass();
jmsSender.doSend(true);

Will man statt dessen eine Queue benutzen, übergibt man einfach false statt true an die Methoden, dann kann der Empfänger auch nach dem Sender gestartet werden.

Achtung: Der Empfänger darf nicht vergessen

connection.start();

aufzurufen, sonst kommen keine Nachrichten an!

Links

Context and Dependency Injection (CDI)

Damit in einer Java EE Applikation Beans in die Referenzen in den Beans auf andere Resourcen automatisch erzeugt und eingefügt werden können, ist normalerweise eine entsprechende Deklaration in einer XML Datei notwendig. Z.B.

faces-config.xml
applicationContext.xml

Mit CDI wird das über Annotationen erreicht

@Named(name="mySuperBean")
@RequestScoped
public class MyBean
{
 ...

 @Inject
 private OtherBean otherBean;

}

Servlets

Servlets

Ein Servlet ist ein ganz normale Java Klasse, die Interfaces aus javax.servlet implementiert. Ein Servlet Container wie z.B. Tomcat führt diesen Code dann aus, und liefert ihn (meist) als HTML an die Clients. Servlets sind sehr codelastig aber ungeeignet um umfangreiches HTML zu erzeugen.

Servlet Links

Servlet Beispiel

Die Imports sind nur verfügbar, wenn man das jar tomcatlibservlet-api.jar aus einer Tomcat Installation einbindet.

package foo;

import java.io.*;
import java.util.Date;
import javax.servlet.*;
import javax.servlet.http.*;

public class MyHelloWorldClass extends HttpServlet
{
 private static final long serialVersionUID = 1L;

 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
 {
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();

  out.println("<html>");
  out.println("<head>");
  out.println("<title>Hello World!</title>");
  out.println("</head>");
  out.println("<body>");
  out.println("<h1>"+"Hello World!"+"</h1>");
  out.println("Date: "+new Date().toString());
  out.println("</body>");
  out.println("</html>");
 }
}

Wie man ein Servlet in Tomcat startet

Wann man das Servlet foo.MyHelloWorldClass in Tomcat ausführen will, sind folgende Schritte notwendig:

<ol>

  • Man denkt sich einen Namen für seine Web Application aus (die normalerweise mehrere Servlets enthalten wird). Beispielsweise "MyFirstServletApplication"
  • Im Tomcat Installationsordner in den Ordner webapps wechseln. Darunter erzeugt man einen neuen Unterordner MyFirstServletApplication, darunter WEB-INF und darunter classes.
  • Jetzt kompiliert man die Klasse foo.MyHelloWorldClass, die entstandene .class Datei kopiert man nach
webapps/MyFirstServletApplication/WEB-INF/classes/foo/
  • Jetzt fehlt noch eine web.xml (Deployment-Deskriptor) unter
webapps/MyFirstServletApplication/WEB-INF/

die so aussieht:

<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app
  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-app_2_5.xsd"
  version="2.5">

    <servlet>
        <servlet-name>My Super Servlet</servlet-name>
        <servlet-class>foo.MyHelloWorldClass</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>My Super Servlet</servlet-name>
        <url-pattern>/SayHelloWorld</url-pattern>
    </servlet-mapping>

</web-app>
 
http://localhost:8080/MyFirstServletApplication/SayHelloWorld

</ol>

Java Server Pages (JSP)

Wie wir schon gesehen haben, sind Servelets sind sehr codelastig und ungeeignet um größere HTML Ausgeben zu erzeugen. Viel besser sind dafür Java Server Pages (JSP). Diese haben selbst eine HTML Struktur, können HTML und Java Elemente enthalten und werden dann z.B. von Tomcat automatisch wieder in Servlets übersetzt. Siehe auch: Wie eine JSP in ein Servlet übersetzt wird.

JSP Links

JSP Syntax

Link zur Syntax von JSPs

Eine JSP Seite kann aus folgenden Sprachelementen bestehen

Skripting

Durch Skripting kann man in eine JSP Seite direkt Java Code integrieren. Scripting Elemente:

Expressions

Eine Expression wird ausgewertet, und in der JSP durch Ihre Ausgabe ersetzt. Beispiele:

<%= request.getHeader("User-Agent") %>
<%= request.getParameter("username") %>

Achtung, kein ; am Ende der Expression verwenden.

Skriptlets

Durch Skriptlets werden Bereiche in einer JSP markiert, in der ganz normaler Java Code verwendet werden kann. Das ähnlich wie <?PHP in einer HTML Datei. Beispiel:

<%
   int x=5;
%>



<%=
   x++;
%>

Zwischen den beiden Skriptlets steht normales HTML, die Änderungen am Wert der Variablen im ersten Skriptlet bestehen auch im zweiten noch fort.

Deklarationen

Mit einer Deklaration kann man Variablen und komplette Methoden definiert, die dann z.B. von Skriptlets aufgerufen werden können.

<%! int x = 7;
 Integer getAnswer() { return 42; } %>

Kommentare

So kann man einen Kommentar in eine JSP einfügen:

<%-- myComment --%>

Direktiven

Mit einer Direktive kann man Einfluss darauf nehmen, wie aus JSP eine Servlet wird.

<%@ include file="someFile" %>

<%@ page pageEncoding="UTF-8" %>
<%@ page contentType="text/html" %>
<%@ page import="java.math.BigInteger" %>

<%-- Import JSTL-Tags <c:tagname attribute="..." ... >
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

Fehlerbehandlung

Entweder so:

<%@ page errorPage="/WEB-INF/error.jsp" %>

oder in der web.xml zentral konfigurieren

<error-page>
    <error-code>404</error-code>
    <location>/WEB-INF/jsp/jsp/errorHandler404.jsp</location>
</error-page>

Die Expression Language (EL)

Implizite Objekte

Spezielle Objekte, die implizit zur Verfügung stehen

requestjavax.servlet.http.HttpServletRequest
responsejavax.servlet.http.HttpServletResponse
sessionjavax.servlet.http.HttpSession(JSP Session)
pageContextjavax.servlet.jsp.PageContext
applicationjavax.servlet.ServletContext
configjavax.servlet.ServletConfig
outjavax.servlet.jsp.JspWriter
exceptionjava.lang.Throwable

Include

Man hat mehrere jsp Seiten, die im Prinzip das gleiche machen, nur mit anderen Beans.

Ein Lösung,

foo.jsp:

<t:aliasBean value="#{myFooBean}" alias="#{my_super_bean}">
    <jsp:include ..../>
</t:aliasBean>

bar.jsp

<t:aliasBean value="#{myBarBean}" alias="#{my_super_bean}">
    <jsp:include ..../>
</t:aliasBean>

Und über das jsp:include kann man dann eine gemeinsame Datei einlesen und dort my_super_bean benutzen.

Tomcat

Tomcat ist ein Servletcontainer, also ein (Web-) Server, der Javacode ausführen kann.

Tomcat installieren

Nach der selbsterklärenden Installation, wird Tomcat einfach so gestartet und gestoppt (Windows / Linux)

startup.bat
startup.sh

Tomcat und Eclipse

<ol>

  • Nach der Tomcat Installation sollte dieser Link funktionieren, und auf den installieren Tomcat zeigen
http://localhost:8080/

Optional kann man den Tomcat Manager unter

http://localhost:8080/manager/

aktivieren. Nach der Installation funktioniert er vermutlich noch nicht:

The requested resource (/manager/) is not available.

Deshalb die conf/tomcat-users.xml editieren (SECRET durch ein sicheres Passwort ersetzen)

<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="tomcat"/>
  <role rolename="manager"/>
  <role rolename="admin"/>
  <user username="myTomcatAdmin" password="SECRET" roles="tomcat,manager,admin"/>
</tomcat-users>

Tomcat neu starten, jetzt sollte er erreichbar sein (falls nicht, mal Browser Cache leeren / Laden ohne Cache erzwingen). Hier kann man sich jetzt mit myTomcatAdmin und dem ausgedachten Passwort anmelden

  • Ein Eclipse Tomcat Plugin installieren, z.B. dieses Tomcat Plugin.

Danach sollte Eclise das hier anbieten: Eclipse Tomcat Projekt erzeugen

  • Optional: In Eclipse Preferences Tomcat einrichten, auch Tomcat Manager App einrichten:
http://localhost:8080/manager
myTomcatAdmin
SECRET
  • Das Plugin bietet sogar die Möglichkeit den Tomcat neu zu starten
Tomcat neu starten
  • Neues Tomcat Projekt einrichten, nennen wir es z.B. MyTomcatProject1

Das Plugin erzeugt dann eine Konfiguration im Tomcat Installationsverzeichnis unter

confCatalinalocalhostMyTomcatProject1.xml

welches auf den Workspace des Projektes zeigt.

  • JSP über das Plugin erstellen: Über das Kontextmentü des Projekts eine neue Datei erstellen und z.B. Test1.jsp nennen. Diese wird automatisch hochgeladen und ist sofort testbar:
Struktur eines neuen Tomcat Projektes in Eclipse
http://localhost:8080/MyTomcatProject1/Test1.jsp
  • Servlet über das Plugin erstellen:

Unter

WEB-INF/src

eine Klasse erzeugen:

package invalid.test.mypackage;
import javax.servlet.*;

public class MyServerlet1 extends HttpServlet
{
 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
 {
  response.setContentType("text/html");
  PrintWriter out = response.getWriter();
  out.println("<html>");
  // ...
 }
}

Jetzt muss man sich noch eine URL ausdenken, die auf diese Klasse in diesem Package gemappt werden soll. Z.B.

<servlet>
   <servlet-name>Foo</servlet-name>
   <servlet-class>invalid.test.mypackage.MyServerlet1</servlet-class>
</servlet>

<servlet-mapping>
   <servlet-name>Foo</servlet-name>
   <url-pattern>/helloWorld</url-pattern>
</servlet-mapping>
 

Damit es unter

http://localhost:8080/MyTomcatProject1/HelloWorld

erreichbar ist. Man sogar in der Java Klasse einen Brakepoint setzen. </ol>

Java Beans und JSP

Java Bean sind normale Java Objekte, die (mindestens) einen parameterlosen Konstruktor haben und für ihre Attribute Getter- und Settermethoden bereitstellen, deren Namen sich an gewissen Konventionen halten (damit man den Namen eines Getters für ein Attribut vorhersagen kann).

Java Beans und JSP Links

Java Beans und JSP Beispiel

Angenommen man hat so eine Bean MyPersonBean, die ein Attribut vorname hat und einen Status verheiratet. Wie schreiben eine jsp Seite, die testMyPersonBean.jsp heißt. In dieser zeigen wir ein Formular an, welches nach Vornamen und Verheiratet Ja/Nein fragt. Beim Absenden ruft die JSP sich selbst wieder auf und zeigt die Werte an, die abgesendet wurden. Über <jsp:useBean> wird dabei festgelegt, welche Java Bean benutzt werden soll und damit auch welche Getter und Setter aufgerufen werden sollen.

<jsp:useBean id="person" class="MyPersonBean" scope="page"/>
<jsp:setProperty name="person" property="*"/>

<html>
 <body>
  Vorname: <jsp:getProperty name="person" property="vorname"/><br/>
  Verheiratet? <jsp:getProperty name="person" property="verheiratet"/><br/>
  <br/>
  <form name="beanTest" method="POST" action="testMyPersonBean.jsp">
   Vorname?
   <input type="text" name="vorname" size="50"><br/>
   Verheiratet?
   <select name="verheiratet">
    <option value="false">No</option>
    <option value="true">Ja</option>
   </select>
  <input type="submit" value="Absenden">
  </form>
 </body>
</html>

JSP Tags / Taglib

In einer JSP stehen über Taglibs zusätzliche Elemente mit vielen zusätzlichen Funktionen zur Verfügung.

JSP Tags / Taglib Beispiel

Core JSTL-Tags:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>

<c:forEach items="${myBean.myList}" var="myTopic">

JSP Tags / Taglib Links

Struts

Man möchte bei einer JSP Anwendung das Model View Controller Muster umsetzen. Dann müsste eigentlich jede JSP Seite umfangreiche Controller Funktionalität bereitstellen. Zum Beispiel müsste jede JSP Logik enthalten, bei welcher Eingabe welche andere JSP aufgerufen wird. Struts übernimmt viele diese Aufgaben für die JSPs.

Struts Links

JavaServer Faces

Die JavaServer Faces, übernehmen wie Struts viele Controller Aufgaben, die sonst die jede JSP beinhalten müsste, um das MVC Muster umzusetzen. Hinzu kommen u.a. neue Tags

JavaServer Faces Links

JavaServer Faces Einführung

<ol>

  • Ein neues Tomcat Projekt starten
  • Die Java Server Faces jars herunterladen und nach WEB-INF/lib kopieren
  • jsf-impl.jar<li>jsf-api.jar</li></li>
  • Diese Datei anlegen: WEB-INF/faces-config.xml (hier kann man später z.B. konfigurieren welche Seite aufgerufen wird, wenn man eine andere Seite mit einem bestimmten Rückgabewert verlassen hat
<?xml version="1.0"?>
<faces-config
  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"
  version="2.0">

</faces-config>
 
  • Jetzt braucht man noch eine WEB-INF/web.xml Datei:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee <http://java.sun.com/xml/ns/javaee> " xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd <http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd> " xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

<servlet>
 <servlet-name>MyJavaServerFacesServlets</servlet-name>
 <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
</servlet>

<servlet-mapping>
 <servlet-name>MyJavaServerFacesServlets</servlet-name>
 <url-pattern>*.jsf</url-pattern>
</servlet-mapping>

<context-param>
 <param-name>javax.faces.PROJECT_STAGE</param-name>
 <param-value>Development</param-value>
</context-param>

<welcome-file-list>
 <welcome-file>index.html</welcome-file>
</welcome-file-list>

</web-app>

Dieses web.xml verknüpft alle URLs, die der Client aufruft und die mit .jsf enden mit Java Server Faces. Das bedeutet aber nicht;, dass die Dateien auf dem Server auf .jsf enden, sondern nur die URLs (z.B. im Browser). Zwischen URL- und Dateiname findet noch einmal eine Abbildung statt!

</ol>

JavaServer Faces Beispiel

Dieses Beispiel zeigt, wie man über JavaServer Faces konfiguriert, welche Seite aufgerufen wird, je nachdem mit welchem Rückgabewerte eine Seite verlassen wird.

Als erstes erzeugen wir drei JavaServer Faces Dateien. Alle sollten parallel zum WEB-INF Ordner abgelegt werden

test.xhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html">
<h:head>
<title>JSF 2.0: Server Test</title>
</h:head>
<h:body>
Hello World
<h:graphicImage url="helloworld.gif" />

<h:form>

<fieldset>
<legend>Good</legend>
 Click button. You should get success page.<br/>
 <h:commandButton value="Click the good button" action="oh-yes" />
</fieldset>

<fieldset>
<legend>Bad</legend>
Click button. You should get error message about not being able to find matching navigation case.<br/>
<h:commandButton value="Do not click the bad button" action="no-way" />
</fieldset>

</h:form>

</h:body>
</html>

page-for-bad-case.xhtml:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html">
<h:head><title>OK</title></h:head>
<h:body>OK</h:body>
</html>

page-for-good-case.xhtml (wie bad case nur statt OK mit einem anderen Text)

Alle drei Dateien kann man sich jetzt schon direkt im Browser ansehen

http://localhost:8080/myJSFProject1/test.xhtml

Allerdings werden dann die JSF Tags wie z.B. h:graphicImage noch nicht "übersetzt", sie werden also im Browser nicht das gewünschte Ergebnis erzielen.

Ruft man allerdings diese URL auf:

http://localhost:8080/myJSFProject1/test.jsf

greift das Mapping in der web.xml und die JSF Tags werden ersetzt, es wird versucht das Bild anzuzeigen.

Java Server Faces verknüpfen In der WEB_INF/faces-config.xml eine Navigation definieren:

<navigation-rule>
 <from-view-id>/test.xhtml</from-view-id>
 <navigation-case>
  <from-outcome>oh-yes</from-outcome>
  <to-view-id>/page-for-good-case.jsp</to-view-id>
 </navigation-case>
 <navigation-case>
  <from-outcome>no-way</from-outcome>
  <to-view-id>/page-for-bad-case.jsp</to-view-id>
 </navigation-case>
</navigation-rule>

Je nachdem ob die test.xhtml mit "oh-yes" oder mit "no-way" verlassen wird, rufen wir die page-for-good-case.jsp oder page-for-bad-case.jsp auf.

Pfad zum webapps Verzeichnis finden

In welchem Ordner auf der Serverfestplatte befindet sich das webapps Verzeichnis?

FacesContext facesContext=FacesContext.getCurrentInstance();

ServletContext servletContext = (ServletContext) facesContext.getExternalContext().getContext();
String lPath=servletContext.getRealPath("/");

Java Server Faces und Beans

Auch in einer JSF Seite kann man auf eine Bean zugreifen, und z.B. den Inhalt eines Attributes der Bean oder den Rückgabewert eine Methode der Bean ganz einfach auslesen.

Attribute auslesen:

Hit Message: <h:outputText value="#{myFirstBean.yourMessage}" />
Hit Counter: <h:outputText value="#{myFirstBean.hitCounter}" />
DB Connection: <h:outputText value="#{myFirstBean.myDB.link}" />

Der Client schreibt Werte in einer Bean:

<h:inputText value="#{myFirstBean.yourMessage}" />

Der Rückgabewerte einer Bean entscheidet, welche Seite als nächstes aufgerufen wird:

<h:commandButton value="Click the bean" action="#{myFirstBean.doIt}" />

Damit das die Zuordnung automatisch funktioniert, muss der erste Buchstabe der Beans und Attribute in der JSF klein geschrieben werden, in der Bean selbst muss es aber einen Getter und Setter mit Großen Buchstaben am Anfang geben. Man kann den Namen der Bean aber auch in der @ManagedBean Annotation manuell angeben.

Die Bean sieht dann so aus:

package invalid.test.mypackage;

import java.io.Serializable;
import javax.faces.bean.*;

@ManagedBean(name="myFirstBean")
@SessionScoped
public class MyFirstBean implements Serializable
{
  private Integer hitCounter=0;

  public Integer getHitCounter() { return this.hitCounter; }
  public void setHitCounter(Integer pNew) { this.hitCounter=pNew; }

  public String doIt()
  {
   this.hitCounter++;
   return "reload";
  }
}

Die Lebenszeit einer Java Server Faces Bean

Die Lebenszeit der Bean lässt sich über eine Annotation steuern:

@RequestScopedDie Bean wird verworfen, sobald die nächste Seite komplett geladen wurde (Default)
@ViewScopedDie Bean wird verworfen, sobald eine Seite verlassen wird
@SessionScopedJeder User hat seine eigene Bean, auf allen Seiten verfügbar ist
@ApplicationScopedEs gibt nur eine einzige Bean für alle User, die sich diese Bean teilen

Java Server Faces ManagedProperty

Man kann sich auch Attribute per Annoation (oder Konfigurationsdatei) belegen lassen

@ManagedProperty(value="#{myDatabaseConnectionBean}") private MyDatabaseConnectionBean myDB;

Dann wird dieses Attribut mit einer Bean gefüllt, die myDatabaseConnectionBean heisst.

Das macht vor allem bei der Nutzung der faces-config.xml Sinn:

<managed-bean>
  <managed-bean-name>myDatabaseConnectionBean</managed-bean-name>
  <managed-bean-class>foo.bar.myDatabaseConnectionBean</managed-bean-class>
  <managed-bean-scope>request</managed-bean-scope>
  <managed-property>
    <property-name>hitCounter</property-name>
    <value>42</value>
  </managed-property>
</managed-bean>

JavaServer Faces Daten ausgeben und bearbeiten

JavaServer Faces DataTable Ausgeben und Filtern

So kann man Listen von Beans ausgeben und filtern:

<html
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
>

<h:head>
<title>JSF 2.0: Server Test</title>
</h:head>

<h:body>

Neuen Filter setzen:

<h:form>
 <h:selectOneMenu value="#{myPersonHolderBean.myActiveFilter}">
 <f:selectItems value="#{myPersonHolderBean.myAvailableFilterEntries}"/>
 <f:ajax render="MyFirstDataTable"/>
</h:selectOneMenu>

<h:dataTable
 var="aPerson"
 value="#{myPersonHolderBean.myFilteredList}"
 border="1"
 id="MyFirstDataTable"
>

<f:facet name="caption">
 Meine tolle Tabelle. Aktiver Filter: "#{myPersonHolderBean.myActiveFilter}"
</f:facet>

<h:column>
 <f:facet name="header">Name der Person</f:facet>
 #{aPerson.name}
</h:column>

<h:column>
 <f:facet name="header">Alter der Person</f:facet>
 #{aPerson.age}
</h:column>

</h:dataTable>
</h:form>

</h:body>
</html>

Das ist die Bean die die Liste enthält und sich merkt was gefiltert ist:

package invalid.test.mypackage;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.model.SelectItem;

@ManagedBean(name="myPersonHolderBean")
@RequestScoped
public class MyPersonHolder implements Serializable
{
 private static final long serialVersionUID = 1L;
 private List<MyPerson> myList;
 private String myActiveFilter;
 private List<SelectItem> myAvailableFilterEntries;

 public MyPersonHolder()
 {
  myList=new ArrayList<MyPerson>();

  for(int i=0; i<100; i++)
  {
   myList.add(new MyPerson("Person"+i, i, ((i % 5)==0)));
  }

  this.myAvailableFilterEntries=new ArrayList<SelectItem>();
  this.myAvailableFilterEntries.add(new SelectItem("ALL", "Alle Personen"));
  this.myAvailableFilterEntries.add(new SelectItem("Young", "Die jungen"));
  this.myAvailableFilterEntries.add(new SelectItem("Old", "Die alten" ));
  this.myActiveFilter="ALL";
 }

 public String getMyActiveFilter() {
  return myActiveFilter;
 }

 public void setMyActiveFilter(String myActiveFilter) {
  this.myActiveFilter = myActiveFilter;
 }

 public List<SelectItem> getMyAvailableFilterEntries() {
  return myAvailableFilterEntries;
 }

 public List<MyPerson> getMyList() {
  return myList;
 }

 public List<MyPerson> getMyFilteredList() {
  List<MyPerson> lResult=new ArrayList<MyPerson>();

  for(MyPerson p : getMyList())
  {
   if(myActiveFilter==null || ("ALL".equals(myActiveFilter)) ||
     ("Young".equals(myActiveFilter) &amp;&amp; p.getAge().compareTo(15)<0) ||
     ("Old".equals(myActiveFilter) &amp;&amp; p.getAge().compareTo(60)>0)
     )
     lResult.add(p);
  }
  return lResult;
 }

}

Das ist die Bean, die in der Liste ausgegeben wird:

package invalid.test.mypackage;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;

@ManagedBean(name="myPersonBean")
public class MyPerson implements Serializable
{
private static final long serialVersionUID = 1L;
private String name;
private Integer age;

public MyPerson()
{
}

public MyPerson(String pName, Integer pAge, Boolean pSelected)
{
 this.setName(pName);
 this.setAge(pAge);
}

public String getName() {
 return name;
}

public void setName(String name) {
 this.name = name;
}

public Integer getAge() {
 return age;
}

public void setAge(Integer age) {
 this.age = age;
}

}

JavaServer Faces DataTable Editieren

Das ist eine Datentabelle, die man auch noch editieren kann:

<html
 xmlns="http://www.w3.org/1999/xhtml"
 xmlns:h="http://java.sun.com/jsf/html"
 xmlns:f="http://java.sun.com/jsf/core"
>

<h:head>
 <title>JSF 2.0: Server Test</title>
</h:head>

<h:body>
Neuen Filter setzen:
<h:form>
 <h:selectOneMenu value="#{myPersonHolderBean.myActiveFilter}">
  <f:selectItems value="#{myPersonHolderBean.myAvailableFilterEntries}"/>
  <f:ajax render="MyFirstDataTable"/>
 </h:selectOneMenu>

 <h:dataTable
  var="aPerson"
  value="#{myPersonHolderBean.myFilteredList}"
  border="1"
  id="MyFirstDataTable"
 >

  <f:facet name="caption">Meine tolle Tabelle. Aktiver Filter: "#{myPersonHolderBean.myActiveFilter}"</f:facet>

  <h:column>
   <f:facet name="header">Editierbar?</f:facet>
   <h:selectBooleanCheckbox value="#{aPerson.updateable}">
    <f:ajax render="@form"/>
   </h:selectBooleanCheckbox>
  </h:column>

  <h:column>
   <f:facet name="header">Name der Person</f:facet>
   #{aPerson.name}
  </h:column>

  <h:column>
   <f:facet name="header">Alter der Person</f:facet>
   <h:outputText value="#{aPerson.age}" rendered="#{!aPerson.updateable}" />
   <h:inputText value="#{aPerson.age}" size="12" rendered="#{aPerson.updateable}" />
  </h:column>

  <h:column>
   <h:commandButton value="Update" rendered="#{aPerson.updateable}">
    <f:ajax render="@form" execute="@form"/>
   </h:commandButton>
  </h:column>


 </h:dataTable>

</h:form>
</h:body>
</html>

Geändert hat sich gegenüber dem vorangehenden Beispiel nur noch die Person Bean, die sich jetzt merkt, ob man sie editieren kann oder nicht:

package invalid.test.mypackage;

import java.io.Serializable;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;

@ManagedBean(name="myPersonBean")
@SessionScoped
public class MyPerson implements Serializable
{
 //....

 public Boolean getUpdateable() {
  return updateable;
 }

 public void setUpdateable(Boolean updateable) {
  this.updateable = updateable;
 }

}
JSF DataTable

Änderungen in einer Data Table erkennen

Wenn man in einem Data Table alle Daten editierbar anbietet, hat man das Problem, dass man nicht erkennen kann, welche Daten der User verändert hat. Dafür gibt es demm valueChangeListener

<h:inputText value="#{row.data.myvalue}" valueChangeListener="#{row.myValueChangeListener}" />
public void myValueChangeListener(ValueChangeEvent event) { ... }

Aus dem Event läßt sich leider nicht erkennen, welche Zeile im Data Table diesen Event ausgelöst hat. Eine Möglichkeit wäre, diese Methode in die Bean zu packen, die einer Zeile im Data Table entspricht. Dabei vermischt man allerdings GUI und Datenanspekte (unelegant). Besser: Eine Box Klasse, die die Bean enthält wird benutzt. Die Box Klasse beinhaltet neben der Bean die valueChangeListener Methode (und bei Bedarf andere GUI relevanten Attribute z.B. ob eine Bean in der GUI gerade markiert ist).

Ausgabe formatieren und validieren

Wenn man ein Datumsfeld ausgeben möchte:

<h:outputText value="#{row.myDate}" />
 

Wird normalerweise nur Jahr, Monat und Tag ausgegeben. Mit Filtern kann man das beeinflussen:

<h:outputText value="#{row.myDate}">
 <f:convertDateTime pattern="HH:mm:ss dd.MM.yyyy" />
 <f:convertDateTime timeZone="Europe/Berlin" pattern="HH:mm:ss dd.MM.yyyy" />
</h:outputText>

Andere vorgefertigte Filter

<f:convertDateTime pattern="MM/yyyy"/>
<f:converter id="javax.faces.Short"/>
<f:convertNumber maxFractionDigits="2" currencySymbol="$"/>
<f:validateLongRange maximum="150" minimum="42"/>

Man kann aber auch seine eigenen Converter und Validator schreiben:

<h:inputText  value="#{myBean.foo}"
           converter="FooConverter"
           validator="FooValidator"
           valueChangeListener="#{myBean.valueChanged}" />

Und in der faces-config.xml:

<converter>
 <converter-id>FooConverter</converter-id>
 <converter-class>de.tgunkel.de.bar.gui.converter.FooConverter</converter-class>
</converter>
<validator>
 <validator-id>FooValidator</validator-id>
 <validator-class>de.tgunkel.de.bar.gui.converter.FooValidator</validator-class>
</validator>
 

Der Java Code sieht dann so aus

public class FooConverter implements Converter
{
 ...
}

public class FooValidator implements Validator
{
 ...
}

Spring

<a href="../../..//it/software/doc/java_dependency_injection#h2-Spring">Spring</h2>

DB Zugriff in Java EE

DAO (Data Access Object)

Damit die Webanwendung von der verwendeten Persistenslösung isoliert ist, kapselt man den Zugriff der Webanwendung auf die Persistenzschicht über sogenannte DAO.

public Interface BestellungDAO
{
 public Bestellung findById(Long pID);
 public void deleteBestellung(Bestellung pBestellung);
 ...
}

Konkrete Umsetzungen für eine Persistenzschicht (z.B. Hibernate) implementieren dann das Interface

public class BestellungDAOHibernate implements BestellungDAO
{
 ...
}

Solange wir im Code nur das Interface benutzen ist die Persistenzschicht leicht austauschbar.

Achtung: Da es in einer Webanwendung leicht passieren kann, dass mehrere Benutzer gleichzeitig versuchen ein Objekt zu verändern, sollte man Schritte unternehmen, um konkurierende Änderungen zu bemerken. Mit Hibernate z.B. eine Spalte für die Version des Objektes benutzen.

In den DAO sollte in der Regel keine Verwaltung der Transaktionen stattfinden, da Transaktionen DAO häufig übergreifen durchgeführt werden sollen.

Aus einer DAO Factory kann man sich dann neue DAO liefern lassen, die dann z.B. gleich eine Referenz auf eine Hibernate Session Factor enthalten.

public class DAOFactory
{
 ...
 public static getBestellungDAO()
 {
  return new BestellungDAOHibernate(this.hibernateSessionFactory);
 }
 ...
}

Wenn man Spring benutzt kann man die DAO als Beans konfigurieren und die Referenz auf die Hibernate Session Factory per Spring Konfiguriation setzen lassen. Man braucht hier also keine solche Factory.

Da Methoden wie findById(Long pID) für alle DAO nützlich sind, macht es Sinn eine generische Klasse zu schreiben in die diese Methoden ausgelagert werden. Dafür kann man dann Generics benutzen.

Transaktionsarten

Es gibt globale und lokale Transaktionen. Lokale umschließen nur eine Resource (z.B. eine Datenbankverbindung) globale umschließen ein oder mehrere Resourcen (z.B. eine Datenbankverbindung und eine Netzwerkverbindung).

Transaktionen mit Spring

Dabei muss es sich nicht zwangsweise um eine Hibernate oder eine Datenbank Transaktion handeln. Spring Transaktionen

Es gibt ein Interface

PlatformTransactionManager
 TransactionStatus getTransaction(TransactionDefinition definition)
 void commit(TransactionStatus status)
 void rollback(TransactionStatus status)

dazu das passende TransactionStatus Interface

TransactionStatus
 boolean isNewTransaction();
 void setRollbackOnly();
 boolean isRollbackOnly();

Man definiert sich eine DataSource und einen TransactionManager je nach Anforderungen und benutzter Resource

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
  <property name="driverClassName" value="${jdbc.driverClassName}" />
  <property name="url" value="${jdbc.url}" />
  <property name="username" value="${jdbc.username}" />
  <property name="password" value="${jdbc.password}" />
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>

Einschub: So kann das z.B. fertig mit Hibernate aussehen

<!-- Hibernate Property -->
<bean id="oracleHibernateProperties"
      class="org.springframework.beans.factory.config.PropertiesFactoryBean">
  <property name="properties">
    <props>
      <prop key="hibernate.dialect">
        org.hibernate.dialect.Oracle9Dialect
      </prop>
      <prop key="hibernate.cache.provider_class">
        org.hibernate.cache.NoCacheProvider
      </prop>
      <prop key="hibernate.jdbc.fetch_size">50</prop>
      <prop key="hibernate.show_sql">true</prop>
    </props>
  </property>
</bean>

<!-- contains DB username and passwort -->
<bean id="hsqlDataSource"
      class="de.allianzgi.ic.data.AppADSDataSource">
  <!-- public class AppADSDataSource extends BasicDataSource implements BeanFactoryPostProcessor -->
</bean>

<!-- Hibernate SessionFactory  -->
<bean id="hsqlSessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">

  <property name="dataSource" ref="hsqlDataSource" />
  <property name="hibernateProperties" ref="oracleHibernateProperties" />

  <property name="annotatedClasses">
    <list>
      <value>de.foo.bar</value>
    </list>
  </property>

  <property name="mappingResources">
    <list>
      <value>de/foo/blub.hbm.xml</value>
    </list>
  </property>
</bean>

<!-- Transaction Manager for Hibernate SessionFactory -->
<bean id="hibernateTransactionManager"
      class="org.springframework.orm.hibernate3.HibernateTransactionManager">
  <property name="sessionFactory" ref="hsqlSessionFactory" />
</bean>

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="hibernateTransactionManager" mode="proxy" />

Spring und Transaktionsverwaltung

High-level approach

Das ist der empfohlene Weg. Man benutzt Template Klassen, wie JdbcTemplate, HibernateTemplate, JdoTemplate und ruft die dortigen Methoden auf, die sich selbst um die Verwaltung der Resourcen und der Transaktionen auf diesen Resourcen kümmern.

Low-level approach

Man kann aber auch DataSourceUtils (for JDBC), SessionFactoryUtils (for Hibernate), PersistenceManagerFactoryUtils (for JDO) benutzen. Man holt sich z.B. so eine JDBC Verbindung:

Connection conn = DataSourceUtils.getConnection(dataSource);

Vorteil: Man kann direkter auf die Resourcen zugreifen.

TransactionAwareDataSourceProxy

Man benutzt einen TransactionAwareDataSourceProxy um auf die DataSource zuzugreifen. Nicht empfohlen.

Declarative transaction management

Auch eine empfohlene Variante, die Konfiguration findet über Aspekt-orientierte Programmierung statt, der Code muss kaum oder nicht angepasst werden.

Man hat eine Interface mit den gewünschten Methoden:

interface FooService

und ein oder mehrere Klassen, die diese Methoden implementieren:

class DefaultFooService implements FooService

Und per config werden dann die Transaktionen definiert:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
...
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
>

  <!-- this is the service object that we want to make transactional -->
  <bean id="fooService" class="Foo.service.DefaultFooService"/>

  <tx:advice id="txAdvice" transaction-manager="txManager">
   <tx:attributes>
     <!-- what methods should be treated and how: -->
     <tx:method name="get*" read-only="true"/>
     <tx:method name="*"/>
   </tx:attributes>
  </tx:advice>

  <!-- Connect the txAdvice with the Service class -->  
  <aop:config>
   <aop:pointcut id="fooServiceOperation" expression="execution(* Foo.service.FooService.*(..))"/>
   <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
  </aop:config>

  <bean id="dataSource" class="..." destroy-method="close">
   ...
  </bean>

  <bean id="txManager" class="....DataSourceTransactionManager">
   <property name="dataSource" ref="dataSource"/>
  </bean>

</beans>

Man kann natürlich auch gleich alle Methoden aller Klassen in einer bestimmten Klassenhierachie per Spring bearbeiten lassen:

<aop:pointcut id="fooServiceMethods" expression="execution(* Foo.service.*.*(..))"/>

Rollback wird per default nur für ungefangene Runtime Exceptions und Errors ausgelöst. Man kann aber auch nicht Runtime Exceptions konfigurieren:

<tx:method name="get*" read-only="true" rollback-for="MyException" no-rollback-for="MyOtherException"/>

Nachteil: Der Einsatz von AOP muss gut dokumentiert werden, wird sonst leicht vergessen.

@Transactional

Statt AOP zu benutzen, kann man die Methoden, für die die Transaktionen verwaltet werden sollen, auch per Annotation markiert werden

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
...
     xmlns:aop="http://www.springframework.org/schema/aop"
     xmlns:tx="http://www.springframework.org/schema/tx"
     xsi:schemaLocation="
...
     http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
     http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
>
...  
<tx:annotation-driven transaction-manager="txManager"/>
...

@Transactional sollte vor die konkrete Klasse oder Methode gesetzt werden, die die Transaktionen benötigt (nicht an deren Interface)

Achtung, die Methoden einer Klasse, die von außen zu erreichen sind, müssen direkt mit der Annotation markiert werden. Macht man das nicht können innerhalb der Klasse keine Transaktionen verwendet werden, selbst wenn man andere Methoden der eigenen Klasse aufruft, die die Annotation hat.

Data Access Object (DAO) support in Spring

Spring DAO

Vereinfachen Transaktionen wenn man z.B. JDBC oder Hibernate benutzt. Spring bietet abstrakte Klassen an (JdbcDaoSupport für JDBC bietet eine JdbcTemplate an, HibernateDaoSupport für Hibernate bietet ein HibernateTemplate an), von denen man entsprechend erben kann.

DAO JDBC

Spring Data access using JDBC

Object Relational Mapping (ORM) data access

Spring Object Relational Mapping (ORM) data access

Object Relational Mapping (ORM) data access Hibernate

So kann man sich ansehen wann in Hibernate welche Transactionen erzeugt werden: log4j.logger.org.hibernate.transaction=debug

Man definiert neben der DataSource noch eine Hibernate Session Factory

<beans>

  <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
    <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
    <property name="username" value="foo"/>
    <property name="password" value="secret"/>
  </bean>

  <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource"/>
    <property name="mappingResources">
      <list>
        <value>product.hbm.xml</value>
      </list>
    </property>
    <property name="hibernateProperties">
      <value>
        hibernate.dialect=org.hibernate.dialect.HSQLDialect
      </value>
    </property>
  </bean>

</beans>

Eine Bean, die einen DB Hibernate benutzen möchte bekommt eine Referenz auf die Session Factory

<beans>

  <bean id="myProductDao" class="product.ProductDaoImpl">
    <property name="sessionFactory" ref="mySessionFactory"/>
  </bean>

</beans>

Die Session Factor speichert sie dann aber nicht direkt ab, sondern packt sie in ein HibernateTemplate

public class ProductDaoImpl implements ProductDao {

    private HibernateTemplate hibernateTemplate;

    public void setSessionFactory(SessionFactory sessionFactory) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
    }
}

Hat die Klasse solch ein HibernateTemplate, wird es z.B. so benutzt:

this.hibernateTemplate.find("from test.Product product where product.category=?", category);

Aber Achtung: Diese Methode haben offenbar seltsame Default Einstellungen z.B. bezüglich der maximalen Anzahl an Zeilen, die zurückgeliefert werden.

Falls man direkt mit der Session arbeiten möchte geht das so:

this.hibernateTemplate.execute(new HibernateCallback()
{
  public Object doInHibernate(Session session)
  {
   Query q=session.createQuery(...);
   return q.list();
  }
};
 

Achtung: Auch wenn man Zugriff auf die Session erhalten hat, die Transaktionen sollten weiterhin von Spring kontrolliert werden. Also NICHT

session.beginTransaction();

benutzen.

Damit man den Setter nicht immer wieder neu implementieren muss, kann man statt dessen aber auch von der Spring Klasse HibernateDaoSupport erben, diese beinhaltet bereits diesen Setter.

Zusätzlich bietet sie auch gleich eine Methode an, um an die Session zu kommen:

Session session = getSession(false);

Mit stellt man sicher dass die Session autoamtisch erstellt und geschlossen wird? -> Auch mit @Transactional

Ab Hibernate 3.x geht es auch ohne HibernateTemplate:

Session sessionFactory.getCurrentSession();

Damit sichergestellt wird, dass in einer Methode auch wirklich eine Transaktion vorhanden ist, kann man dies per Annotation erzwingen

@Transactional(propagation = Propagation.REQUIRED)

oder einfach nur

@Transactional
Dabei istREQUIRED der Default)
 

Vielleicht auch noch interessant:

Propagation.REQUIRES_NEW

hier wird nicht nur sichergestellt dass möglicherweise bereitsvorhande Transaktionen beendet werden und eine neue zur Verfügung gestellt wird

Fehler:

javax.el.PropertyNotFoundException: javax.el.PropertyNotFoundException: Property 'foo' not found on type $Proxy24

Man hat @Transactional in einer Klasse verwendet

Besser: Ein Interface Bar und eine implementierende Klasse BarImpl entwerfen, die nur dafür benutzt werden zu persisitieren.

Fehlermeldung:

org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

applicationContext.xml:

...
  <bean id="hibernateTransactionManager"

class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="hsqlSessionFactory" />
  </bean>

  <!-- enable the configuration of transactional behavior based on
annotations -->
  <tx:annotation-driven
transaction-manager="hibernateTransactionManager" mode="proxy" />
...

JUnit Tests Spring Fehlermeldung:

java.lang.IllegalStateException: No Scope registered for scope 'session' at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:295)

Dann muss man der BeanFactory Scopes unterschieben

import static org.junit.Assert.*;

import org.hibernate.Query;
import org.hibernate.classic.Session;
import org.hibernate.impl.SessionFactoryImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import
org.springframework.beans.factory.config.ConfigurableListableBeanFactory
;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.orm.hibernate3.HibernateTemplate;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import
org.springframework.test.context.transaction.TransactionConfiguration;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.RequestScope;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.context.request.SessionScope;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/applicationContext.xml")
@TransactionConfiguration(transactionManager="hibernateTransactionManage
r"
, defaultRollback=false)
@Transactional
public class MyDBTestClass
{
        private static ConfigurableListableBeanFactory beanFactory;
        private static SessionFactoryImpl sessionFactory;

        @BeforeClass
        public static void beforeClass()
        {
                System.out.println("This is before the class is started");
                XmlBeanFactory lBeanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));

                /* Scope does not work well within non web applications like for example unit tests
                 * Add empty scopes to make Spring happy, might have strange effects
                 */
   
               lBeanFactory.registerScope(WebApplicationContext.SCOPE_SESSION, new SessionScope() );

               lBeanFactory.registerScope(WebApplicationContext.SCOPE_REQUEST, new RequestScope() );
               MockHttpServletRequest request = new
               MockHttpServletRequest();
               ServletRequestAttributes attributes = new
               ServletRequestAttributes(request);
               RequestContextHolder.setRequestAttributes(attributes);
               beanFactory=lBeanFactory;
               System.out.println("Bean Factory created");
               ....
               sessionFactory=(SessionFactoryImpl)
               beanFactory.getBean("hsqlSessionFactory");
        }

    @BeforeTransaction
    public void beforeTransactionMethod()
    {
        System.out.println("This is before the transaction is started");
    }

    @Before
    public void beforeMethod()
    {
        System.out.println("This is before the test method is called");
    }

    @Test
    @Rollback(true)
    public void modifyDatabaseWithinTransaction()
    {
        System.out.println("This is a DB test");
        HibernateTemplate hibernateTemplate=new HibernateTemplate(sessionFactory);
        Session s=hibernateTemplate.getSessionFactory().getCurrentSession();
        Query q=s.createSQLQuery("SELECT * FROM dual");
        q.list();
        System.out.println("Finished DB test");
    }

    @Test
    public void failMe()
    {
        fail("Fail me");
    }

    @After
    public void afterMethod
    ()
    {
        System.out.println("This is after the test method is called");
    }

    @AfterTransaction
    public void afterTransactionMethod()
    {
        System.out.println("This is before the transaction is started");
    }
}

DB Zugriff über Applikation Server (Glassfish)

Damit man über den Applikation Server überhaupt auf eine Datenbank zugreifen kann, muss man diese im Applikation Server erst mal konfigurieren. Hier am Beispiel von Glassfish.

Als erstes ist es notwendig, Programmcode vom Datenbankanbieter in den lib Ordner der Domäne zu kopieren. Z.B. für eine Oracle Datenbank

cp ojdbc6.jar glassfish/domains/MyDomain/lib/databases

Damit Glassfish das neue jar anzieht, muss mindestens die Domaine neu gestartet werden.

Dann muss man im Glassfish einen Connection Pool einrichten

<a href="java_files/GlassfishConnectionPool1.jpg">Glassfish Connection Pool</a> <a href="java_files/GlassfishConnectionPool2.jpg">Glassfish Connection Pool</a>

Über die GUI kann man einen Ping auf die Datenbank absetzten, der sollte fehlerfrei funktionieren.

Jetzt fehlt nur noch eine DataSource, die auf den neuen ConnectionPool verweist. <a href="java_files/GlassfishDataSource.jpg">Glassfish Data Source</a>

JPA

Ab jetzt können Java EE Anwendungen auf die Datenbank zugreifen. Dafür müssen sie eine Datei persistence.xml beinhalten. Diese sieht minimal etwa so aus

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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_1_0.xsd">
  <persistence-unit name="MyPersistenceUnit" transaction-type="JTA">
    <jta-data-source>jdbc/MyOracleDB</jta-data-source>
    <properties>
    </properties>
  </persistence-unit>
</persistence>

und beinhaltet eine Referenz auf die gerade angelegte DataSource.

Angenommen man hat in der Datenbank solch eine Tabelle:

create table foo.product (id number, name varchar2(255), descr varchar2(255));

Dann sieht ein entsprechendes Mapping in Java dazu z.B. so aus (im Mapping wird absichtlich der Spaltenname descr auf description umgebogen, um zu zeigen wie das geht):

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "foo.product")
public class Product {

    private Long id;

    private String name;

    private String description;

    public Product() {
    }

    public Product(Long id, String name, String desc) {
        this.id=id;
        this.name=name;
        this.description=desc;
    }

    @Id
    @Column(name = "ID")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name = "descr")
    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

An anderer Stelle im Code kann dann so aus der Tabelle in das Java Objekt gelesen werden

import javax.persistence.PersistenceUnit;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityManager;

public class Foo
{

    @PersistenceUnit
    private EntityManagerFactory emf;

    @Resource
    private UserTransaction transaction;

 ...  
        EntityManager em = emf.createEntityManager();
        List<Product> products = em.createQuery("select p from Product p").getResultList();
 ...
        Product product = new Product(id, name, descr);
        transaction.begin();
        {
         em.persist(product);
        }
        transaction.commit();
 ...

EJB3

Glassfish herunterladen und installieren. Und / oder Eclipse mit eingebauten Glassfish (Eclipse IDE for Java EE Developers) herunterladen und installieren.

Glassfish Tutorial

Eclipse mit eingebautem Glassfish starten. Dann Window -> Open Perspecitve -> Java EE Unten erscheint ein Server Reiter, Rechtsklick, New Server, Download additional server adapters, Glassfish Java EE Server, die gewünschte Version aussuchen. Auf Nachfrage auf das bereits installierte Glassfish verweisen. Achtung, der Server braucht ein jdk kein jre!

Sobald der Server läuft:

Glassfish Java Webandwendung

File -> New -> Dynamic Web Project

Rechtsklick auf dem neuen Projekt, Run as -> Run on server und den konfigurierten Server auswählen

EJB Glassfish

EJB Links

EJB Glassfish Beispiel

File -> New -> EJB Project

Als Name z.B. "MyEJBProjectNr1" wählen, x Add project to an EAR, als Namen z.B. "MyEJBProjectNr1EAR" nehmen. x Create an EJB Client JAR module to hold the interfaces and classes

Dann per Rechtsklick auf das neue Projekt eine Session Bean hinzuügen. Als Typ Stateless, Remote und Local Interface erzeugen. Eclipse sollte die eigentliche Bean im neuen Projekt anzeigen, die beiden Interfaces allerdings nur im Client Teil.

Im Server Reiter jetzt über Add das Projekt dem Server hinzufügen.

Source Code:

package de.tgunkel.java.MyEJBProjectNr1;
import javax.ejb.Local;
@Local
public interface MySessionStatelessNr1Local
{
        public String getMessage();

        public void setMessage(String message);

        public String getInfoString();
}
package de.tgunkel.java.MyEJBProjectNr1;
import javax.ejb.Remote;

@Remote
public interface MySessionStatelessNr1Remote
{
        public String getMessage();

        public void setMessage(String message);

        public String getInfoString();
}
package de.tgunkel.java.MyEJBProjectNr1;

import java.util.Date;

import javax.ejb.LocalBean;
import javax.ejb.Stateless;

@LocalBean
@Stateless
public class MySessionStatelessNr1 implements MySessionStatelessNr1Remote, MySessionStatelessNr1Local
{
        private String message;
        private Date startdate;


    public MySessionStatelessNr1()
    {
        startdate=new Date();
    }

        public String getMessage()
        {
                return message;
        }

        public void setMessage(String message)
        {
                this.message = message;
        }

        public String getInfoString()
        {
                return "MySessionStatelessNr1 [Created on startdate=" + startdate + ", with message="+ message + "]";
        }
}
EJB Client zum Testen

Jetzt zum Testen einen standalone Client schreiben. Ein neues normales Java Project starten, nennen es z.B. "MyEJBProjectNr1StandAloneClient". Wichtig ist, dass wir unbedingt die "glassfishmodulesgf-client.jar" aus dem Glassfish Installationsordner im Buildpfad (Classpath sollte auch reichen?) haben. Zusätzlich binden wir den Client Teil des EJB Projects als Teil des Buildpfades ein. Entweder über Eclipse Java Build Path -> Projects -> Add oder indem man das EJB Client JAR einbindet.

Weitere Informationen: EJB Standalone Client

package de.tgunkel.java.MyEJBProjectNrStandAloneClient;

import javax.naming.InitialContext;
import javax.naming.NamingException;

import de.tgunkel.java.MyEJBProjectNr1.MySessionStatelessNr1Remote;

public class MyMainClient
{

        public static void main(String[] args)
        {
                MySessionStatelessNr1Remote myBean=null;

                InitialContext ic;
                try
                {
                        ic = new InitialContext();
                        myBean  = (MySessionStatelessNr1Remote) ic.lookup("de.tgunkel.java.MyEJBProjectNr1.MySessionStatelessNr1Remote");
                } catch (NamingException e)
                {
                        e.printStackTrace();
                }

                if(myBean!=null)
                {
                        myBean.setMessage("Hallo World");
                        System.out.println("Bean message: "+myBean.getInfoString());
                }
        }
}

Java Naming and Directory Interface (JNDI)

Ein Beispiel für JNDI über einen LDAP Server

package de.tgunkel.java.JNDIDemo;

import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.NamingException;

class MyJNDIDemo
{
        DirContext ctx;

        public MyJNDIDemo(String pURL, String pUser, String pPassword) throws NamingException
        {
                final String SUN_LDAP="com.sun.jndi.ldap.LdapCtxFactory";
                Hashtable<String, String> lHashTable = new Hashtable<String, String>();
                lHashTable.put(Context.INITIAL_CONTEXT_FACTORY, SUN_LDAP);
                lHashTable.put(Context.PROVIDER_URL,            pURL);
                lHashTable.put( Context.PROVIDER_URL, pURL);
                lHashTable.put( Context.SECURITY_PRINCIPAL,   pUser);
                lHashTable.put( Context.SECURITY_CREDENTIALS, pPassword);
                ctx=new InitialDirContext(lHashTable);
        }

        public String searchPerson(String pSearchString) throws NamingException
        {
                Attributes attrs = ctx.getAttributes(pSearchString);
                return attrs.get("sn").get().toString();
        }

        public Integer getMyInteger(String pID) throws NamingException
        {
                return (Integer) ctx.lookup( pID );
        }

        public void putMyInteger(Integer pInteger, String pID) throws NamingException
        {
                ctx.bind( pID, pInteger );
        }

        public void closeConnection() throws NamingException
        {
                ctx.close();
        }
}

Und so sucht man nach einem Text oder schreibt ein Objekt und liest es dann wieder aus (man braucht natürlich einen funktionierenden LDAP Server dafür)

final String lURL="ldap://myldapserver.example.com:389/o=MyJNDIDemo";
final String lSearchTerm="cn=Mr Foo, ou=People";

MyJNDIDemo demo = new MyJNDIDemo(lURL, "cn=DemoUser, o=MyJNDIDemo", "secret");

String result=demo.searchPerson(lSearchTerm);

demo.putMyInteger(new Integer(42), "cn=MySuperInteger");
Integer y=demo.getMyInteger("cn=MySuperInteger");

XML

In diesem Abschnitt werden XML relevante Teile von Java EE behandelt. Um die Technologien an einem einfachen Beispiel ausprobieren zu können, ist hier eine XML Datei, die ein sehr einfaches Adressbuch abbildet:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<MyContatcs>
    <ContactsOwner>John Doe</ContactsOwner>
    <People>
        <Person>
            <Name>Bill</Name>
            <Age>39</Age>
        </Person>
        <Person>
            <Name>Jane</Name>
            <Age>56</Age>
        </Person>
        <Person>
            <Name>Frank</Name>
            <Age>33</Age>
        </Person>
    </People>
</MyContatcs>

Hier noch ein XML Schema:

<?xml version="1.0" encoding="UTF-8"?>
 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:jxb="http://java.sun.com/xml/ns/jaxb" jxb:version="2.0">

 <xsd:element name="MyContatcs" type="MyContactsType" />

 <xsd:complexType name="MyContactsType">
   <xsd:sequence>
    <xsd:element name="ContactsOwner" type="xsd:string" maxOccurs="1" minOccurs="1"/>
    <xsd:element name="People" type="PeopleListType"    maxOccurs="1" minOccurs="1"/>
   </xsd:sequence>
 </xsd:complexType>

 <xsd:complexType name="PeopleListType">
   <xsd:sequence>
     <xsd:element name="Person" type="PersonType" maxOccurs="unbounded"/>
   </xsd:sequence>
 </xsd:complexType>

 <xsd:complexType name="PersonType">
   <xsd:sequence>
        <xsd:element name="Name" type="xsd:string" maxOccurs="1"
                minOccurs="1" />
        <xsd:element name="Age" type="xsd:integer" maxOccurs="1"
                minOccurs="1" />
   </xsd:sequence>
 </xsd:complexType>

JAXB

Die ermöglicht es, JAVA Klassen mit XML Dateien zu verknüpfen. Die Klassen werden mit den Inhalten des XML gefüllt oder umgekehrt.

Als erstes eine xsd Definition für die XML Datei schreiben und im Dateisystem ablegen. Aus der xsd Definition kann dann mit dem Tool xjc (im JDK enthalten) Code für entsprechende Java Klassen erzeugt werden

# xjc -d src/ -p de.tgunkel.JavaXMLDemo contacts.xsd
parsing a schema...
compiling a schema...
detgunkelJavaXMLDemoObjectFactory.java
detgunkelJavaXMLDemoPeopleListType.java
detgunkelJavaXMLDemoPersonType.java

Jetzt kann man entsprechende Java Objekte erzeugen lassen und mit Inhalten füllen. Aus diesen Objekten kann dann jederzeit das entsprechende XML generiert werden

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;

import de.tgunkel.JavaXMLDemo.MyContactsType;
import de.tgunkel.JavaXMLDemo.ObjectFactory;
import de.tgunkel.JavaXMLDemo.PeopleListType;
import de.tgunkel.JavaXMLDemo.PersonType;


public class XMLTest
{
 ...
                ObjectFactory objFact=new ObjectFactory();

                MyContactsType myContacts=objFact.createMyContactsType();
                JAXBElement<MyContactsType> myContactsRootNode = objFact.createMyContatcs(myContacts);

                myContacts.setContactsOwner("John Doe");               
                myContacts.setPeople(objFact.createPeopleListType());
                PeopleListType peopleList=myContacts.getPeople();

                for(int i=0; i<3; i++)
                {
                        PersonType person=objFact.createPersonType();
                        person.setName(names.get(i));
                        person.setAge(ages.get(i));
                        peopleList.getPerson().add(person);
                }

                JAXBContext jaxbContext = JAXBContext.newInstance( "de.tgunkel.JavaXMLDemo" );
                Marshaller marshall = jaxbContext.createMarshaller();
                marshall.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );

                FileOutputStream foStream = new FileOutputStream("xml/output.xml");

                marshall.marshal( myContactsRootNode, foStream );
                // marshall.marshal( myContactsRootNode, System.out );
                foStream.close();
 ...
}

Und auch dem umgekehrte Weg, wenn man schon XML hat und möchte die verknüpften Java Objekte erhalten ist sehr leicht:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import de.tgunkel.JavaXMLDemo.MyContactsType;
import de.tgunkel.JavaXMLDemo.ObjectFactory;
import de.tgunkel.JavaXMLDemo.PeopleListType;
import de.tgunkel.JavaXMLDemo.PersonType;


public class XMLTest
{
 ...           
                FileInputStream fiStream=new FileInputStream("xml/output.xml");
                Unmarshaller unmarshaller=jaxbContext.createUnmarshaller();
                JAXBElement<MyContactsType> unMarshalledContactsRootNode =(JAXBElement<MyContactsType>) unmarshaller.unmarshal(fiStream);

                String owner=unMarshalledContactsRootNode.getValue().getContactsOwner();
                System.out.println("Owner: "+owner);
                for(PersonType p : unMarshalledContactsRootNode.getValue().getPeople().getPerson())
                {
                        String    name=p.getName();
                        BigInteger age=p.getAge();
                        System.out.println("Name: "+name+" Age: "+age);
                }
        }

}
Owner: John Doe
Name:  Bill  Age: 39
Name:  Jane  Age: 56
Name:  Frank Age: 33

JAXP

In JAXP gibt es verschiedene Ansätze, XML zu parsen.

JAXP DOM

Man kann das komplette XML in ein DOM Dokument laden

import java.io.File;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XMLTest2
{
        private void echoNodes(Node pNode)
        {
                if(pNode!=null)
                {
                        System.out.println(pNode.getNodeName()+": "+pNode.getNodeValue());
                        NodeList children=pNode.getChildNodes();
                        for(int i=0; i<children.getLength(); i++)
                        {
                                Node child=children.item(i);
                                echoNodes(child);
                        }
                }
        }

        public void XMLParser() throws ParserConfigurationException, SAXException, IOException
        {
          String fileName="xml/output.xml";
          DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder db = dbf.newDocumentBuilder();
        Document doc = db.parse(new File(fileName));
        Node n=doc.getFirstChild();
        echoNodes(n);
        }

}

JAXP Callback

Der Callback Ansatz über SAX

import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.SAXException;

...

 String fileName="xml/output.xml";
 SAXParserFactory factory = SAXParserFactory.newInstance();            
 factory.setValidating(true);
 factory.setNamespaceAware(false);
 SAXParser parser = factory.newSAXParser();
 parser.parse(new File(fileName), new MyHandler());

...

Der interessante Teil ist hier in der Klasse MyHandler. Die dort enthaltenten Methoden werden immer dann aufgerufen, wenn bestimmte Ergebnisse eintreten (z.B. ein neues Element wird im eingelesenen XML geöffnet)

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class MyHandler extends DefaultHandler
{
        @Override
        public void startDocument() throws SAXException
        {
                super.startDocument();
                System.out.println("Starting document ...");
        }

        @Override
        public void endDocument() throws SAXException
        {
                super.endDocument();
                System.out.println("Ending document.");
        }

        @Override
        public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
        {
                super.startElement(uri, localName, qName, attributes);
                System.out.println("<"+qName+">");
        }

        @Override
        public void endElement(String uri, String localName, String qName) throws SAXException
        {
                super.endElement(uri, localName, qName);
                System.out.println("</"+qName+">");
        }

        @Override
        public void characters(char[] ch, int start, int length) throws SAXException
        {
                super.characters(ch, start, length);
                String content=new String(ch, start, length);
                System.out.println(content);
        }
}

StAX

Eine Mischung zwischen dem Event basierten JAXP Ansatz und der DOM Lösung. Es gibt weiterhin Events (z.B. wenn ein neues Element gelesen wird) aber die Abarbeitung der Elemente kann selbst gesteuert werden

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;

public class XMLTest3
{
        private void echoElement(XMLStreamReader x) throws XMLStreamException
        {              
                switch(x.getEventType())
                {
                case XMLEvent.START_ELEMENT:
                        System.out.println("<"+x.getName()+">");
                        break;
                case XMLEvent.END_ELEMENT:
                        System.out.println("</"+x.getName()+">");
                        break;
                case XMLEvent.CHARACTERS:
                        System.out.println(x.getText());
                        break;
                default:
                        break;
                }
        }

        public void readXML() throws FileNotFoundException, XMLStreamException
        {
                String filename="xml/output.xml";
                XMLInputFactory xmlif = XMLInputFactory.newInstance();
            XMLStreamReader xmlr = xmlif.createXMLStreamReader(filename, new FileInputStream(filename));

            while(xmlr.hasNext())
        {
                xmlr.next();
                echoElement(xmlr);
        }
    }
}

Webservices

Webservices sind für Programme etwa das, was Webseiten für menschliche Benutzer sind. Über Webservices könnnen Programme plattformübergreifen kommunizieren. Webservices basieren auf XML und häuft HTTP. Trotzdem kann man im Allgemeinen Webservices über einen Browser nicht benutzen.

Anwendungsbeispiele: Eine Fluggesellschaft bietet über Webservices Flüge an, in Reisebüros läuft ein Programm, welches die Fluginformationen ausliest, einem Benutzer anzeigt, der die Flüge darüber dann auch kaufen kann.

Damit der Klient weiß, welche Schnittstellen der Webservice anbietet, gibt es die Web Services Description Language (WSDL) http://de.wikipedia.org/wiki/WSDL. Der Webservice stellt dazu eine WSDL Datei bereit, in der über XML die Schnittstellen definiert sind.

Die eigentliche Kommunikation findet zwischen Klient und dem Server findet dann über das Simple Object Access Protocol (SOAP) http://de.wikipedia.org/wiki/SOAP statt. SOAP besteht selbst ebenfalls aus XML welche z.B. über HTTP übertragen werden.

Weder die die WSDL Datei noch die SOAP Nachrichten muss man selbst erstellen, das leisten entsprechenden Frameworks transparent im Hintergrund.

Java API für XML WebServics (JAX-WS)

JAX-WS ist ein solches Framework für Java. Damit wird die Bereitstellung eines Webservices sehr einfach.

So erstellt man z.B. einen Webservice der "Hallo Welt" ausgeben und zwei Zahlen addieren kann:

package de.tgunkel.JAVAEE.WS.calculator.server;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;

@WebService
public class MyCalculatorWS
{

    @WebMethod(operationName = "sayHelloWorld")
    public String mySayHelloWorldMethod()
    {
        return "Hello World";
    }

    @WebMethod(operationName = "addTwoNumber")
    public int myAddMethod(@WebParam(name = "i") int pNumber1,
                           @WebParam(name = "j") int pNumber2)
    {
        return (pNumber1 + pNumber2);
    }

}

Die Annoationen WebMethod und WebParam sind optional, per Default werden alle public Methoden mit ihrem Namen in den Webservice aufgenommen.

Daraus eine WAR Datei erstellen und in den Application Server laden. In Glassfish kann man den Webservice jetzt schon ausprobieren. In die Admin Console, Anwendungen, unsere Webservice Anwendung auswählen, Endpunkt anzeigen.

<a href="java_files/WebserviceGlassfish1.jpg">Java EE Webservice Glassfish</a> <a href="java_files/WebserviceGlassfishTester.jpg">Java EE Webservice Glassfish</a>

Dort kann man auch die erzeugte WSDL herunterladen.

Wenn man jetzt einen Client für den Webservice schreiben möchte, kann man mit dem Tool wsimport (bzw. dem in Glassfish enthaltenen ant Task) aus der WSDL direkt entsprechenden Code erzeugen. In unserem Beispiel wurde dann unter anderem die beiden Klassen

de.tgunkel.JAVAEE.WS.client.autogenerated.MyCalculatorWS;
de.tgunkel.JAVAEE.WS.client.autogenerated.MyCalculatorWSService;

erzeugt.

Und so kann der Client dann über die automatisch aus der WSDL Datei erzeugten Klassen auf dem Webservice zugreifen

package de.tgunkel.JAVAEE.WS.calculator.client;

import de.tgunkel.JAVAEE.WS.client.autogenerated.MyCalculatorWS;
import de.tgunkel.JAVAEE.WS.client.autogenerated.MyCalculatorWSService;
import java.io.*;

import javax.servlet.*;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import javax.xml.ws.WebServiceRef;

@WebServlet(name="ClientServlet", urlPatterns={"/ClientServlet"})
public class ClientServlet extends HttpServlet
{
    @WebServiceRef(wsdlLocation = "http://localhost:8080/CalculatorApp/MyCalculatorWSService?wsdl")
    public MyCalculatorWSService service;

    public void client()
    {
        try
        {
                MyCalculatorWS port = service.getMyCalculatorWSPort();

                int i = 5;
                int j = 42;

                String message = port.sayHelloWorld();
                int    result  = port.addTwoNumber(i, j);

                ((Closeable)port).close();            
        }
        finally
        {
            out.close();
        }
    }
 ...
}

Damit das Einfügen der Resourcen in den Client auch außerhalb des Application Servers funktioniert, muss er mit appclient aufgrufen werden.

JAX-RS / RESTful Webservice

Mit REST kann man über das Netzwerk zustandslos Informationen austauschen. Die Kommunikation läuft dabei sehr ähnlich wie mit einem Webserver. Eine REST Anfrage besteht aus einer URI und einer Operation (GET, POST, PUT und DELETE). Die URI besteht aus dem Protokoll (http://), dem Servernamen (www.example.com) dem Port (:80), context root (/myrestapplication) und dem REST resource path (/mydata) und schließlich noch dem Pfad zur eigentlichen Resource (/item).

Den REST resource path kann man für JAX-RS auf zwei Arten angeben. 1. Über die web.xml, indem man den Pfad (/mydata) auf ein Servlet mappt, das auf com.sun.jersey.spi.container.servlet.ServletContainer zeigt. 2. Über eine leere Klasse, die eine bestimmte Annotation trägt und von javax.ws.rs.core.Application erbt.

import javax.ws.rs.core.Application;

@ApplicationPath("mydata")
public class AnyRandomName extends Application {
}

Jetzt kann man eine Klasse erstellen, die unter einer solchen URI die verschiedenen Operationen anbietet:

@Path("item")
public class Shop
{

 @GET
 @Produces("text/xml")
 public String getItem()
 {
   ...
   return ...
 }

 @PUT
 @Consumes("text/xml")
 public void createItem(String pItemXML)
 {
  ...
 }

 @POST
 @Consumes("text/xml")
 public void updateItem(String pItemXML)
 {
  ...
 }

 @DELETE
 @Consumes("text/xml")
 public void deleteItem(String pItemXML)
 {
  ...
 }

}

Es ist noch etwas unhandlich dabei immer mit XML Strings zu hantieren. Aber auch dafür gibt es über JAXB eine praktische Lösung.

@XmlRootElement
public class Item implements Serializable
{

 ...

 private String itemName;

 ...

 public String getItemName()
 {
  return itemName;
 }

 ...

}

Jetzt kann die Shop Klasse umgeschrieben werden und statt XML Strings die Item Klasse benutzen

@Path("item")
public class Shop
{

 @GET
 @Produces("text/xml")
 public Item getItem()
 {
   ...
   return ...
 }

 @PUT
 @Consumes("text/xml")
 public void createItem(Item pItemObject)
 {
  ...
 }

 ...
}

REST Beispielsprogramm mit Maven erstellen pom.xml


...
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <jersey.version>2.1</jersey.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.glassfish.jersey</groupId>
        <artifactId>jersey-bom</artifactId>
        <version>${jersey.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>
  <dependencies>
...
    <dependency>
      <groupId>org.glassfish.jersey.containers</groupId>
      <artifactId>jersey-container-grizzly2-http</artifactId>
    </dependency>

    <!-- uncomment this to get JSON support:
         <dependency>
           <groupId>org.glassfish.jersey.media</groupId>
           <artifactId>jersey-media-moxy</artifactId>
         </dependency>
         -->

    <dependency>
      <groupId>org.glassfish.jersey.core</groupId>
      <artifactId>jersey-client</artifactId>
      <scope>test</scope>
    </dependency>

  </dependencies>
...

So erstellt man ein lauffähiges Demo Programm:

mvn archetype:generate -DarchetypeArtifactId=jersey-quickstart-grizzly2  -DarchetypeGroupId=org.glassfish.jersey.archetypes -DinteractiveMode=false -DgroupId=de.tgunkel.java.rest -DartifactId=MyRestProgram -Dpackage=de.tgunkel.java.rest -DarchetypeVersion=2.1

So kann man einen Server starten:

import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory;
import org.glassfish.jersey.server.ResourceConfig;
import java.io.IOException;
import java.net.URI;

...
public static final String BASE_URI = "http://localhost:8080/myapp/"
...
final ResourceConfig rc = new ResourceConfig().packages("your.package.name.where.the.classes.are.which.deal.with.requests");
...
HttpServer myserver=GrizzlyHttpServerFactory.createHttpServer(URI.create(BASE_URI), rc);
...
myserver.stop();

Und so sieht eine Klasse aus, die die Anfragen erhält

package your.package.name.where.the.classes.are.which.deal.with.requests;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;

/**
* What to do for REST requests to .../mig21_violations/
*/

@Path("foo_bar")
public class Foo
{
    /**
     * Method handling HTTP GET requests.
     * This this like this: http://localhost:8080/myapp/foo_bar?x=58&amp;y=32074
     *
     * @return String that will be returned as a text/plain response.
     */
 
    @GET
    @Produces(MediaType.APPLICATION_XML)
    public String getIt(@QueryParam("x") String pPortfolio, @QueryParam("y") Integer pMinAgeInDays)
    {
        return ...;
    }
}

So kann man es mit Parametern testen:

String responseMsg = myserver.getTarget().path("foo_bar").queryParam("id", "123456").request().get(String.class);

Java Authentication and Authorization Service (JAAS)

In der Glassfish Admin Console legt man unter Security in der File Realm einen neuen User an. Der User wird mindestens einer Gruppe zugefügt. Hier z.B. der User "MyDemoUserInGlassfish" und die Gruppe "MyVIPGroupInGlassfish".

Glassfish security new user

In der web.xml des Projektes definiert man jetzt eine Reihe von Sicherheitseinstellungen.

<security-role>
  <role-name>myRole</role-name>
</security-role>

<security-constraint>
  <display-name>My Security Constraint</display-name>
  <web-resource-collection>
     <web-resource-name>My Protected Area</web-resource-name>
      <url-pattern>/topsecret/*</url-pattern>
  </web-resource-collection>
  <auth-constraint>
     <role-name>myRole</role-name>
  </auth-constraint>
</security-constraint>

<login-config>
  <auth-method>FORM</auth-method>
  <realm-name>Example Form-Based Authentication Area</realm-name>
  <form-login-config>
    <form-login-page>/login.jsp</form-login-page>
    <form-error-page>/error.jsp</form-error-page>
  </form-login-config>
</login-config>

Damit legt man fest, dass URLs, die mit /topsecret/* anfangen, nur von Benutzern besucht werden dürfen, die die Rolle "myRole" inne haben. Falls sich der Besucher noch nicht als User ausgewiesen hat, wird er auf die Seite login.jsp umgeleitet.

Damit das funktioniert, muss es noch eine Abbildung zwischen den Usern und Gruppen in Glassfish und den Rollen geben. Das kann über die Datei glassfish-web.xml erreicht werden (angeblich auch über sun-web.xml, hat aber nicht funktioniert).

<security-role-mapping>
  <role-name>myRole</role-name>
  <group-name>MyVIPGroupInGlassfish</group-name>
</security-role-mapping>

In der login.jsp muss es mindestens diesen beiden Felder geben

<input type="text"     name="j_username"></td>
<input type="password" name="j_password"></td>

So kann man einen User wieder ausloggen

session.invalidate();

Damit man HTTPS benutzt, muss man in der web.xml

<security-constraint>
  ...
  <transport-guarantee>CONFIDENTIAL</transport-guarantee>
</security-constraint>

setzen. Noch strenger ist

<transport-guarantee>INTEGRAL</transport-guarantee>

damit werden nicht HTTPS Anfragen nicht nur umgeleitet, sondern abgelehnt.

Statt dem File Realm kann man auch den Certificate Realm benutzen, dort authentifizieren sich die User über ein Client Zertifikat. Die Zertifikate können mit diesem Tool erzeugt werden

keytool

Truststore kaputt

Ich hatte eine Zeit lange immer mal wieder diese Fehlermeldung:

java.io.IOException: Keystore was tampered with, or password was incorrect

Ursache: Eine Anwendung hat den Truststore und Keystore von Glassfish benutzt und dabei beschädigt.

Lösung: Diese Datei aus einer Sicherung wiederherstellen:

glassfishdomainsdomain1configcacerts.jks

Und in der bösen Anwendung den Truststore und Keystrore selbst festlegen:

System.setProperty("javax.net.ssl.trustStore",        "mystore.jks");
System.setProperty("javax.net.ssl.trustStorePassword","supersecret");
System.setProperty("javax.net.ssl.keyStore",          "mystore.jks");
System.setProperty("javax.net.ssl.keyStorePassword",  "supersecret");

Oder über Parameter der Java Anwendung

-Djavax.net.ssl.keyStore=mystore.jks
-Djavax.net.ssl.keyStorePassword=supersecret
-Djavax.net.ssl.trustStore=mykeys.jks
-Djavax.net.ssl.trustStorePassword=supersecret

Hier noch mal der komplette Stacktrace für Google

 at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:553)
 at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:544)
 at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:362)
 at org.springframework.web.context.ContextLoader.createWebApplicationContext(ContextLoader.java:255)
 at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:199)
 at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:45)
 at org.apache.catalina.core.StandardContext.contextListenerStart(StandardContext.java:4664)
 at com.sun.enterprise.web.WebModule.contextListenerStart(WebModule.java:535)
 at org.apache.catalina.core.StandardContext.start(StandardContext.java:5266)
 at com.sun.enterprise.web.WebModule.start(WebModule.java:499)
 at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:928)
 at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:912)
 at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:694)
 at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1947)
 at com.sun.enterprise.web.WebContainer.loadWebModule(WebContainer.java:1619)
 at com.sun.enterprise.web.WebApplication.start(WebApplication.java:90)
 at org.glassfish.internal.data.EngineRef.start(EngineRef.java:126)
 at org.glassfish.internal.data.ModuleInfo.start(ModuleInfo.java:241)
 at org.glassfish.internal.data.ApplicationInfo.start(ApplicationInfo.java:236)
 at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:339)
 at com.sun.enterprise.v3.server.ApplicationLifecycle.deploy(ApplicationLifecycle.java:183)
 at org.glassfish.deployment.admin.DeployCommand.execute(DeployCommand.java:272)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl$1.execute(CommandRunnerImpl.java:305)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:320)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl.doCommand(CommandRunnerImpl.java:1176)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl.access$900(CommandRunnerImpl.java:83)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1235)
 at com.sun.enterprise.v3.admin.CommandRunnerImpl$ExecutionContext.execute(CommandRunnerImpl.java:1224)
 at com.sun.enterprise.v3.admin.AdminAdapter.doCommand(AdminAdapter.java:365)
 at com.sun.enterprise.v3.admin.AdminAdapter.service(AdminAdapter.java:204)
 at com.sun.grizzly.tcp.http11.GrizzlyAdapter.service(GrizzlyAdapter.java:166)
 at com.sun.enterprise.v3.server.HK2Dispatcher.dispath(HK2Dispatcher.java:100)
 at com.sun.enterprise.v3.services.impl.ContainerMapper.service(ContainerMapper.java:245)
 at com.sun.grizzly.http.ProcessorTask.invokeAdapter(ProcessorTask.java:791)
 at com.sun.grizzly.http.ProcessorTask.doProcess(ProcessorTask.java:693)
 at com.sun.grizzly.http.ProcessorTask.process(ProcessorTask.java:954)
 at com.sun.grizzly.http.DefaultProtocolFilter.execute(DefaultProtocolFilter.java:170)
 at com.sun.grizzly.DefaultProtocolChain.executeProtocolFilter(DefaultProtocolChain.java:135)
 at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:102)
 at com.sun.grizzly.DefaultProtocolChain.execute(DefaultProtocolChain.java:88)
 at com.sun.grizzly.http.HttpProtocolChain.execute(HttpProtocolChain.java:76)
 at com.sun.grizzly.ProtocolChainContextTask.doCall(ProtocolChainContextTask.java:53)
 at com.sun.grizzly.SelectionKeyContextTask.call(SelectionKeyContextTask.java:57)
 at com.sun.grizzly.ContextTask.run(ContextTask.java:69)
 at com.sun.grizzly.util.AbstractThreadPool$Worker.doWork(AbstractThreadPool.java:330)
 at com.sun.grizzly.util.AbstractThreadPool$Worker.run(AbstractThreadPool.java:309)
 at java.lang.Thread.run(Thread.java:662)
Caused by: java.net.SocketException: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
 at javax.net.ssl.DefaultSSLSocketFactory.throwException(SSLSocketFactory.java:179)
 at javax.net.ssl.DefaultSSLSocketFactory.createSocket(SSLSocketFactory.java:192)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.sun.jndi.ldap.Connection.createSocket(Connection.java:317)
 at com.sun.jndi.ldap.Connection.<init>(Connection.java:187)
 ... 70 more
Caused by: java.security.NoSuchAlgorithmException: Error constructing implementation (algorithm: Default, provider: SunJSSE, class: com.sun.net.ssl.internal.ssl.DefaultSSLContextImpl)
 at java.security.Provider$Service.newInstance(Provider.java:1245)
 at sun.security.jca.GetInstance.getInstance(GetInstance.java:220)
 at sun.security.jca.GetInstance.getInstance(GetInstance.java:147)
 at javax.net.ssl.SSLContext.getInstance(SSLContext.java:125)
 at javax.net.ssl.SSLContext.getDefault(SSLContext.java:68)
 at javax.net.ssl.SSLSocketFactory.getDefault(SSLSocketFactory.java:102)
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
 at java.lang.reflect.Method.invoke(Method.java:597)
 at com.sun.jndi.ldap.Connection.createSocket(Connection.java:273)
 ... 71 more
Caused by: java.io.IOException: Keystore was tampered with, or password was incorrect
 at sun.security.provider.JavaKeyStore.|#]

Webstart

Wie kann man webstart debuggen? Systemsteuerung, Java, Advanced, Enable Tracing C:Users...AppDataLocalLowSunJavaDeployment In die deployment.properties

deployment.trace.level=all

Die Logs liegen dann dort im log Ordner.