Java EE (J2EE)
Java EE ist eine Sammlung von Technologien / Schnittstellen.
Kürzel | Name | Links |
---|---|---|
JMS | Java Message Service | Java Message Service (JMS) |
JavaMail | JavaMail | |
CDI | Context and Dependency Injection | Context and Dependency Injection (CDI) |
JSS | Java Servlet API | Servlets |
JSP | JavaServer Pages | Java Server Pages |
JSF | Java Server Faces | Java Server Faces |
JSTL | JavaServer Pages Standard Tag Library | |
JPA | Java Persistence API | DB Zugriff in Java EE |
JTA | Java Transaction API | |
EJB | Enterprise Java Beans | Enterprise JavaBeans |
JNDI | Java Naming and Directory Interface | JNDI |
JAXB | Java Architecture for XML Binding | JAXB |
JAXP | Java API for XML Processing | JAXP |
JAX-RPC | Java API for XML-Based Remote Procedure Calls | |
JAXR | Java API for XML Registries | |
StAX | Streaming API for XML | StAX |
WS | Web Services | Webservices |
- | Web Service Metadata | |
JAX-WS | Java API for XML Web Services | JAX-WS |
JACC | Java Authorization Contract for Containers | |
JAAS | Java Authentication and Authorization Service | Java Authentication and Authorization Service (JAAS) |
JCA | J2EE Connector Architecture | |
JAF | JavaBeans Activation Framework |
Allgemeine Informationen
- Ein sehr praxisorientiertes Buch Java EE 6 with GlassFish 3 Application Server
- Java ist auch eine Insel
- Buch: JavaServer Faces 2.0: Grundlagen und erweiterte Konzepte
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">
</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:
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 && !"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
String messageForMe=jmsClient.doReceive(true);
System.out.println("Receiving Result: "+messageForMe);
Alternativ kann auch eine Callback Methode registriert werden:
Dann den Sender starten
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
aufzurufen, sonst kommen keine Nachrichten an!
Links
- QBrowser for GlassFish JMS ist ein Tool, um JMS Nachrichten auch über eine GUI verschicken und empfangen zu können (praktisch zum Debuggen)
- Aus dem lesenswerte Buch Java EE 6 with GlassFish 3 Application Server steht ein kostenloses Probekapitel online, JMS in GlassFish.
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.
applicationContext.xml
Mit CDI wird das über Annotationen erreicht
@RequestScoped
public class MyBean
{
...
@Inject
private OtherBean otherBean;
}
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.
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
- Jetzt fehlt noch eine web.xml (Deployment-Deskriptor) unter
die so aussieht:
<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>
- Siehe auch jsptutorial.org: Verzeichnisstruktur von Java-Webanwendungen
- Nachdem man den Tomcat mit startup.bat oder startup.sh gestartet hat, kann man hier das Ergebnis bewundern
</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
- Java ist auch eine Insel
- Java ist auch eine Insel
- jsptutorial.org
- Another JSP tutorial
- Buch: Head First Servlets and JSP
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.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.
Integer getAnswer() { return 42; } %>
Kommentare
So kann man einen Kommentar in eine JSP einfügen:
Direktiven
Mit einer Direktive kann man Einfluss darauf nehmen, wie aus JSP eine Servlet wird.
<%@ 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:
oder in der web.xml zentral konfigurieren
<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
request | javax.servlet.http.HttpServletRequest | |
response | javax.servlet.http.HttpServletResponse | |
session | javax.servlet.http.HttpSession | (JSP Session) |
pageContext | javax.servlet.jsp.PageContext | |
application | javax.servlet.ServletContext | |
config | javax.servlet.ServletConfig | |
out | javax.servlet.jsp.JspWriter | |
exception | java.lang.Throwable |
Include
Man hat mehrere jsp Seiten, die im Prinzip das gleiche machen, nur mit anderen Beans.
Ein Lösung,
foo.jsp:
<jsp:include ..../>
</t:aliasBean>
bar.jsp
<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.sh
Tomcat und Eclipse
<ol>
- Nach der Tomcat Installation sollte dieser Link funktionieren, und auf den installieren Tomcat zeigen
Optional kann man den Tomcat Manager unter
aktivieren. Nach der Installation funktioniert er vermutlich noch nicht:
Deshalb die conf/tomcat-users.xml editieren (SECRET durch ein sicheres Passwort ersetzen)
<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:
- Optional: In Eclipse Preferences Tomcat einrichten, auch Tomcat Manager App einrichten:
myTomcatAdmin
SECRET
- Das Plugin bietet sogar die Möglichkeit den Tomcat neu zu starten
- Neues Tomcat Projekt einrichten, nennen wir es z.B. MyTomcatProject1
Das Plugin erzeugt dann eine Konfiguration im Tomcat Installationsverzeichnis unter
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:
- Servlet über das Plugin erstellen:
Unter
eine Klasse erzeugen:
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-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
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: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:
<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
- wikipedia.org
- tutorials.irian.at
- jsptutorial.org
- tutorials.irian.at (online Ausgabe eines Buches)
- roseindia.net
- redhat
- coreservlets.com
- coreservlets.com
- coreservlets.com
- coreservlets.com
- coreservlets Präsentation, sehr gute Einführung
- sun
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
<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:
<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:
<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:
<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
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:
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:
<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?
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 Counter: <h:outputText value="#{myFirstBean.hitCounter}" />
DB Connection: <h:outputText value="#{myFirstBean.myDB.link}" />
Der Client schreibt Werte in einer Bean:
Der Rückgabewerte einer Bean entscheidet, welche Seite als nächstes aufgerufen wird:
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:
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:
@RequestScoped | Die Bean wird verworfen, sobald die nächste Seite komplett geladen wurde (Default) |
@ViewScoped | Die Bean wird verworfen, sobald eine Seite verlassen wird |
@SessionScoped | Jeder User hat seine eigene Bean, auf allen Seiten verfügbar ist |
@ApplicationScoped | Es 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-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:
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:
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) && p.getAge().compareTo(15)<0) ||
("Old".equals(myActiveFilter) && p.getAge().compareTo(60)>0)
)
lResult.add(p);
}
return lResult;
}
}
Das ist die Bean, die in der Liste ausgegeben wird:
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:
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:
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;
}
}
Ä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
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:
Wird normalerweise nur Jahr, Monat und Tag ausgegeben. Mit Filtern kann man das beeinflussen:
<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: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:
converter="FooConverter"
validator="FooValidator"
valueChangeListener="#{myBean.valueChanged}" />
Und in der faces-config.xml:
<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 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 Bestellung findById(Long pID);
public void deleteBestellung(Bestellung pBestellung);
...
}
Konkrete Umsetzungen für eine Persistenzschicht (z.B. Hibernate) implementieren dann das Interface
{
...
}
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 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
TransactionStatus getTransaction(TransactionDefinition definition)
void commit(TransactionStatus status)
void rollback(TransactionStatus status)
dazu das passende TransactionStatus Interface
boolean isNewTransaction();
void setRollbackOnly();
boolean isRollbackOnly();
Man definiert sich eine DataSource und einen TransactionManager je nach Anforderungen und benutzter Resource
<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
<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:
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:
und ein oder mehrere Klassen, die diese Methoden implementieren:
Und per config werden dann die Transaktionen definiert:
<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:
Rollback wird per default nur für ungefangene Runtime Exceptions und Errors ausgelöst. Man kann aber auch nicht Runtime Exceptions konfigurieren:
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
<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
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
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
<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
<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
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:
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:
{
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
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:
Mit stellt man sicher dass die Session autoamtisch erstellt und geschlossen wird? -> Auch mit @Transactional
Ab Hibernate 3.x geht es auch ohne HibernateTemplate:
Damit sichergestellt wird, dass in einer Methode auch wirklich eine Transaktion vorhanden ist, kann man dies per Annotation erzwingen
oder einfach nur
Vielleicht auch noch interessant:
hier wird nicht nur sichergestellt dass möglicherweise bereitsvorhande Transaktionen beendet werden und eine neue zur Verfügung gestellt wird
Fehler:
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:
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:
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:295)
Dann muss man der BeanFactory Scopes unterschieben
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
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"></a> <a href="java_files/GlassfishConnectionPool2.jpg"></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"></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
<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:
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.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.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.
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 Tutorial
- Buch: EJB 3 professionell. Grundlagen- und Expertenwissen zu Enterprise JavaBeans 3 für Einsteiger, Umsteiger und Fortgeschrittene
- Sourcecode für EJB 3 professionell. Grundlagen- und Expertenwissen zu Enterprise JavaBeans 3 für Einsteiger, Umsteiger und Fortgeschrittene
- SVN Server für Sourcecode für EJB 3 professionell. Grundlagen- und Expertenwissen zu Enterprise JavaBeans 3 für Einsteiger, Umsteiger und Fortgeschrittene. Zugangsdaten: "anonymous" / "anon"
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:
import javax.ejb.Local;
@Local
public interface MySessionStatelessNr1Local
{
public String getMessage();
public void setMessage(String message);
public String getInfoString();
}
import javax.ejb.Remote;
@Remote
public interface MySessionStatelessNr1Remote
{
public String getMessage();
public void setMessage(String message);
public String getInfoString();
}
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
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
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 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:
<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:
<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
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.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.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);
}
}
}
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.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.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.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.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
- Wikipedia Webservices
- Webservices Tutorial
- w3schools Webservices
- Java EE 6 with GlassFish 3 Application Server
- In Netbeans enthaltene Beispiele
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:
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"></a> <a href="java_files/WebserviceGlassfishTester.jpg"></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.MyCalculatorWSService;
erzeugt.
Und so kann der Client dann über die automatisch aus der WSDL Datei erzeugten Klassen auf dem Webservice zugreifen
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.
@ApplicationPath("mydata")
public class AnyRandomName extends Application {
}
Jetzt kann man eine Klasse erstellen, die unter einer solchen URI die verschiedenen Operationen anbietet:
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.
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
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:
So kann man einen Server starten:
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
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&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:
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".
In der web.xml des Projektes definiert man jetzt eine Reihe von Sicherheitseinstellungen.
<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).
<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="password" name="j_password"></td>
So kann man einen User wieder ausloggen
Damit man HTTPS benutzt, muss man in der web.xml
...
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</security-constraint>
setzen. Noch strenger ist
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
Truststore kaputt
Ich hatte eine Zeit lange immer mal wieder diese Fehlermeldung:
Ursache: Eine Anwendung hat den Truststore und Keystore von Glassfish benutzt und dabei beschädigt.
Lösung: Diese Datei aus einer Sicherung wiederherstellen:
Und in der bösen Anwendung den Truststore und Keystrore selbst festlegen:
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.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: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
Die Logs liegen dann dort im log Ordner.