Das Prinzip realisiert das MVC-Pattern:

| Vereinbarungen (meist import) | Java-Code | Java-Code: <%= ... %> wird gegen die Ausschriften des Codes ersetzt |
<%@ ... %> |
<% ... %> |
<%= ... %> |
Beispiel:
<% a = 3; if (a < 5) { %>
<p>Der Wert <%= a %> ist kleiner als 5</p>
<% } else { %>
<p>Hallo%</p>
<% } %>
|
erzeugt:
|
Der Wert 3 ist kleiner als 5 |
Das Wort Hallo erscheint nie!
Der Persistenz-Speicher benutzt eine verteilte Architektur um die Skalierbarkeit zu gewährleisten. Er besteht aus 2 Komponenten:
Der Persistenzspeicher speichert so genannte Entities bis zu einer Größe von 1 MB. Die Konfiguratation geschieht über eine Datei:
|
war/WEB-INF/classes/META-INF/jdoconfig.xml
<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">
<persistence-manager-factory name="transactions-optional">
<property name="javax.jdo.PersistenceManagerFactoryClass"
value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
<property name="javax.jdo.option.ConnectionURL" value="appengine"/>
<property name="javax.jdo.option.NontransactionalRead" value="true"/>
<property name="javax.jdo.option.NontransactionalWrite" value="true"/>
<property name="javax.jdo.option.RetainValues" value="true"/>
<property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
</persistence-manager-factory>
</jdoconfig>
|
Die Entities sind Java-Objekte mit so genannten Annotations (seit Java 5 vorhanden), die regeln, welche Teile das Objekts persistent gespeichert werden sollen:
|
src/Kandidat.java (gekürzt)
package wahl;
....
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Kandidat {
@PrimaryKey
@Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
private Key key;
@Persistent
private String name;
@Persistent
private int stimmen = 0;
Kandidat(String name) { this.name = name; }
public int getStimmen() { return this.stimmen; }
public void incrStimme() { this.stimmen++; }
public String getName() { return name; }
public String createButton() {
return "<input type=\"radio\" value=\"" + name + "\" name=\"Wahlbutton\"/>";
}
}
|
Diese Klassen müssen einen Primary Key vom Typ Key haben!
Um den Persistenzspeicher zu nutzen, muss ein Persistenz-Manager erzeugt werden:
|
src/PMF.java
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;
public final class PMF {
private static final PersistenceManagerFactory pmfInstance =
JDOHelper.getPersistenceManagerFactory("transactions-optional");
private PMF() {}
public static PersistenceManagerFactory get() {
return pmfInstance;
}
}
|
Erzeugen eines neuen persistenten Objekts:
PersistenceManager pm = PMF.get().getPersistenceManager();
try {
pm.makePersistent(new Kandidat("Richard Wagner"));
} finally {
pm.close();
}
|
Wenn der Key bekannt ist, kann auf ein spezielles Objekt mit:
Kandidat kand = pm.getObjectById(Kandidat.class, key)
zugegriffen werden. Ansonsten muss eine Liste erzeugt werden. Manipulationen des selektierten Objektes ändern auch das persistente Objekt:
|
src/PMF.java
pm = PMF.get().getPersistenceManager();
query = "select from " + Kandidat.class.getName();
kandidaten = (List<Kandidat>) pm.newQuery(query).execute();
for (Kandidat kandidat : kandidaten) {
if (kandidat.getName().equals(gesuchter_name)) {
kandidat.incrStimme();
pm.close();
return;
}
}
|
Ein Servlet ist der Kern einer Technologie zur Bearbeitung von GET- und POST-Requests.
Java bietet dafür die Klassen HttpServlet, HttpServletRequest und
HttpServletResponse aus der Servlet-API an.
Das HttpServlet bietet die leeren Methoden:
doGet(HttpServletRequest req, HttpServletResponse resp)
und
doPost(HttpServletRequest req, HttpServletResponse resp)
an. Praxis ist es, eine Subklasse von HttpServlet zu
bilden und (eine) diese(r) Methoden zu überschreiben.
Das Objekt req steht für den Request und die Parameter
können mit über die Methode getParameter(String param) erfragt werden.
Das Objekt resp steht für die Antwort an den anfragenden WEB-Browser.
Durch spezielle Methoden kann ein HTTP-Header erzeugt werden und durch
die Methode setContentType(String content_type) kann der
Content-Type: ... Header festgelegt werden. Dieser Header ist Pflicht!
Durch sukzessive Rufe von resp.getWriter().println(String s) wird die Anwort an den WEB-Browser
zusammengestellt. Ist der Content-Type text/html so sollte die
Aneinanderreihung dieser Strings eine gültige HTML-Seite ergeben:
|
src/WahlServlet.java
import java.io.IOException;
import javax.servlet.http.*;
import javax.jdo.PersistenceManager;
import java.util.List;
public class WahlServlet extends HttpServlet {
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String query = null;
PersistenceManager pm = null;
List<Kandidat> kandidaten = null;
String wahlname = req.getParameter("Wahlbutton");
if (wahlname != null) {
pm = PMF.get().getPersistenceManager();
query = "select from " + Kandidat.class.getName();
kandidaten = (List<Kandidat>) pm.newQuery(query).execute();
for (Kandidat kandidat : kandidaten) {
if (kandidat.getName().equals(wahlname)) {
kandidat.incrStimme();
break;
}
}
pm.close();
resp.sendRedirect("/");
return;
}
String neuname = req.getParameter("kandidat");
if (neuname.length() < 1) {
resp.sendRedirect("/");
return;
}
pm = PMF.get().getPersistenceManager();
query = "select from " + Kandidat.class.getName();
kandidaten = (List<Kandidat>) pm.newQuery(query).execute();
for (Kandidat kandidat : kandidaten) {
if (kandidat.getName().equals(neuname)) {
resp.setContentType("text/html");
resp.getWriter().println("<html><body><h1>Wahl</h1><hr/>");
resp.getWriter().println("<p>" + neuname + " steht bereits zur Wahl.</p>");
resp.getWriter().println("<p><a href=\"/\">Zurück</a></p></body></html>");
pm.close();
return;
}
}
try {
pm.makePersistent(new Kandidat(neuname));
} finally {
pm.close();
}
resp.setContentType("text/html");
resp.getWriter().println("<html><body><h1>Wahl</h1><hr/>");
resp.getWriter().println("<p>" + neuname + " wurde als Kandidat aufgenommen.</p>");
resp.getWriter().println("<p><a href=\"/\">Zurück</a></p></body></html>");
}
}
|
Die Java-Server-Page hat folgenden Aufbau:
|
war/wahl.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="javax.jdo.PersistenceManager" %>
<%@ page import="wahl.Kandidat" %>
<%@ page import="wahl.PMF" %>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<h1>Hallo</h1>
<hr/>
<form action="/wahlauswertung" method="post">
<%
boolean kands = true;
PersistenceManager pm = PMF.get().getPersistenceManager();
String query = "select from " + Kandidat.class.getName();
List<Kandidat> kandidaten = (List<Kandidat>) pm.newQuery(query).execute();
if (kandidaten.isEmpty()) {
kands = false;
%>
<p>keine Kandidaten</p>
<% }
else {
%>
<p>Um einen Kandidaten zu wählen, selektieren Sie den Knopf
in der letzten Spalte der Tabelle. Um alle Kandidaten zu
löschen, selektieren Sie den Knopf hinter
<code>alle löschen</code>:
</p>
<table border="1"><tr><th>Name</th>
<th>Stimmen</th><th> </th></tr>
<%
for (Kandidat kandidat : kandidaten) {
%>
<tr><td>
<%=kandidat.getName() %>
</td><td>
<%=kandidat.getStimmen() %>
</td><td>
<%=kandidat.createButton() %>
</td></tr>
<% } %>
</table>
<% } %>
<p>Um einen neuen Kandidaten hinzuzufügen,
<% if (kands) { %>
wählen Sie niemanden und
<% } %>
tragen Sie einfach des Neuen Namen hier ein:</p>
<p>Neuer Kandidat: <input type="text" name="kandidat"/></p>
<div><input type="submit" value="Senden"/></div>
</form>
</body>
</html>
|
Die Zuordnung von URLs zu JSPs geschieht über einen speziellen XML-Dialekt:
|
war/WEB-INF/web.xml
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>wahlservlet</servlet-name>
<servlet-class>wahl.WahlServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>wahlservlet</servlet-name>
<url-pattern>/wahlauswertung</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>wahl.jsp</welcome-file>
</welcome-file-list>
</web-app>
|
Hier wird zum Beispiel wahl.jsp als Willkommens-Seite
festgelegt. Somit wird bei Angabe des URIs / stets
diese JSP geladen. So erklärt sich auch die Wirkung von resp.sendRedirect("/");.
Die URI /wahlauswertung führt zum Servlet wahlservlet, welches
wiederum durch die Klasse WahlServlet aus dem Paket wahl
repräsentiert wird. Dadurch erklärt sich der Action-URL:
<form action="/wahlauswertung" method="post">
Das Betreiben einer Applikation in der Google App Engine ist (bis zu einer bestimmten Größenordnung) kostenlos. Allerdings benötigt man einen Account, welcher wiederum die Angabe einer E-Mail-Adresse und (später) einer Handynummer zwingend erfordert.
Zum Entwickeln kann man entweder mit Eclipse- Plugins arbeiten oder man kann den Apache Ant verwenden. Für letztere Variante lädt man sich das Google App Engine SDK herunter.
Apache Ant erwartet eine spezielle Verzeichnisstruktur:

Zum Erzeugen der ausführbaren Dateien benötigt man eine Datei build.xml:
|
build.xml
<project>
<property name="sdk.dir" location="../appengine-java-sdk-1.2.6" />
<import file="${sdk.dir}/config/user/ant-macros.xml" />
<path id="project.classpath">
<pathelement path="war/WEB-INF/classes" />
<fileset dir="war/WEB-INF/lib">
<include name="**/*.jar" />
</fileset>
<fileset dir="${sdk.dir}/lib">
<include name="shared/**/*.jar" />
</fileset>
</path>
<target name="copyjars"
description="Copies the App Engine JARs to the WAR.">
<copy
todir="war/WEB-INF/lib"
flatten="true">
<fileset dir="${sdk.dir}/lib/user">
<include name="**/*.jar" />
</fileset>
</copy>
</target>
<target name="compile" depends="copyjars"
description="Compiles Java source and copies other source files to the WAR.">
<mkdir dir="war/WEB-INF/classes" />
<copy todir="war/WEB-INF/classes">
<fileset dir="src">
<exclude name="**/*.java" />
</fileset>
</copy>
<javac
srcdir="src"
destdir="war/WEB-INF/classes"
classpathref="project.classpath"
debug="on" />
</target>
<target name="datanucleusenhance" depends="compile"
description="Performs JDO enhancement on compiled data classes.">
<enhance_war war="war" />
</target>
<target name="runserver" depends="datanucleusenhance"
description="Starts the development server.">
<dev_appserver war="war" />
</target>
</project>
|
Dieses gibt dem Apache Ant die zu erzeugenden Ziele, wie Compilieren oder Starten und deren Abhängigkeiten vor. Eine genauere Erläuterung findet men unter http://code.google.com/intl/de-DE/appengine/docs/java/tools/ant.html
Um das Programm lokal zu testen, gibt man das Kommando:
ant runserver
Das Google App Engine SDK hat einen eigenen WEB-Server. Deshalb kann man die Anwendung durch Laden folgenden URLs ausprobieren:
http://localhost:8080
Um die Datei bei Google zu platzieren, bringt das Google App Engine SDK ein eigenes Upload-Script mit:
../appengine-java-sdk-1.2.6/bin/appcfg.sh update war
Voraussetzung ist ein Google-Account. Um diesen anzulegen, muss man
dem Link Sign up auf der Google App Engine
Seite folgen.
Dasselbe Script dient auch dem Ändern der Anwendung.
Die Quelltexte der diesem Abschnitt vorgestellten Beispielapplikation stehen unter http://vsr.informatik.tu-chemnitz.de/staff/jan/gappen/wahl/wahl.xhtml zur Verfügung.
Das Programm selbst kann unter http://diewahlderwahl.appspot.com ausprobiert werden.