Archiv für die Kategorie ‘Programmieren’

Bildergallerie mit Apache Wicket, Teil 2

Am Freitag, 13. März 2009 veröffentlicht unter Programmieren

Im vorherigen Teil haben wir uns mit der Verbindung zur Datenbank beschäftigt, jetzt wollen wir tatsächlich uns die Gallerieansicht vornehmen und erste Seiten mit Wicket dann darstellen, so dass am Ende von diesem Tutorial-Teil die Gallerie wie folgt ausschaut.

Zwischenergebnis Gallerie

Zwischenergebnis Gallerie

Einheitliches Layout

Im Verlauf des Tutorials werden mehrere Seiten erstellt. Schön wäre es da, wenn man ein einheitliches Layout einfach produzieren könnte, so dass z.B. auch überall die selbe Navigation erscheint oder die selben CSS-Dateien eingebunden werden. In Wicket läuft dieser Prozess unter dem Namen “Markup Inheritance”.

Bei Wicket ist es so, dass zu jeder sichtbaren Seite eine Java-Klasse und eine HTML-Datei gleichen namens existieren müssen, die auch im selben Verzeichnis liegen, bei mir im Package page. Zudem muss die Klasse von WebPage erben, das ist die Standardklasse für Webseiten von Wicket.

Wir erstellen uns also zunächst eine abstrakte Layout-Klasse, die die Funktionalität zu Verfügung stellt, welche alle anderen Klassen von ihr erben sollen. Zunächst wollen wir nur, dass jede Seite einen Titel bekommt und in der Fusszeile das aktuelle Datum erscheint.

public abstract class Layout extends WebPage {
	private String title = "Layout title";
	private Date date;
	public Layout() {
		date = new Date();
		add(new Label("title", new PropertyModel(this, "title")));
		add(new Label("pageTitle", new PropertyModel(this, "title")));
		add(new Label("date", new PropertyModel(this, "date")));
	}

	public void setTitle(String t) {
		title = t;
	}

	public String getTitle() {
		return title;
	}

Ein Label ist in Wicket einfach ein ganz normales Textobjekt, welches durch add() als sichtbar markiert wird und dann auch ein Äquivalent im HTML-Code besitzen muss. Generell benötigt jede Komponente, die man zu einer Seite hinzufügt, mindestens zwei Angaben. Erstens den Namen, über den das Objekt im HTML-Code referenziert wird (hier z.B. “title” oder “date”) und ein Model, von welchem der Inhalt der Komponente stammt. Über die Angabe von new PropertyModel(this, "title") wird ein PropertyModel erstellt, welches in dem aktuellen Objekt (this) eine Objektvariable mit dem Namen “title” sucht uns dieses dort einbindet. Genauso geht das mit dem Datum.

Ich habe gesagt, dass zu jeder Klasse, die von WebPage erbt, eine gleich benannte HTML-Datei existieren muss. Diese gucken wir uns nun an und schauen, wie wir ganz simpel Objekte, die per oben beschriebener add() Methode hinzugefügt wurden, im HTML-Quellcode referenzieren und einbinden können.

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title wicket:id="pageTitle">Insert title here</title>
</head>
<body>
<div id="header"><h1 wicket:id="title">title here</h1></div>

<!-- Begin main -->
<div id="main">
	<wicket:child />
</div>
<!-- End main -->

<div id="footer"><small wicket:id="date">date</small></div>
</body>
</html>

Es ist zu erkennen, dass es sich bei der Layout.html um eine ganz normale HTML-Datei handelt. Das einzige bisschen mehr, was wir noch kennen müssen, um Wicket an entsprechender Stelle Inhalte einfügen zu lassen, ist der Zusatz wicket:id="{hier die entsprechende Bezeichnung}", die als Zusatz in jeden x-beliebigen HTML-Tag eingebaut werden kann. Es funktioniert so, dass man z.B. wie unten im Footer in den Small-Tag das aktuelle Datum rein generieren lassen möchte. So fügt man also wicket:id="date" hinzu. Dann wird automatisch beim Aufruf der Wert der add() Methode mit dem Bezeichner “date” hier eingebunden. Das date, was im HTML-Code schon zwischen dem Anfang- und Ende-Tag steht, dient nur als Platzhalter und zur besseren Übersicht, er kann auch weggelassen werden.

So simpel ist das! Interessant zu erwähnen ist noch der neue Wicket-Tag <wicket:child /> mit dem dann an entsprechender Stelle generierter Code aus einer anderen Wicket-Klasse eingefügt wird. Wie es genau funktioniert, wird später deutlich.

Seiten: 1 2 3

Letztes Update am 6.07.2009 10:02

Bildergallerie mit Apache Wicket, Teil 1

Am Dienstag, 07. Oktober 2008 veröffentlicht unter Programmieren

Ausgehend vom Softwarepraktikum, wo wir das Webframework Click benutzt haben, habe ich mich mehr mit J2EE beschäftigt und unter anderem noch mit Tapestry rumgespielt, bis ich dann jetzt bei dem Framework Wicket von Apache hängen geblieben bin.

An Wicket mag ich besonders, dass wie bei Click HTML-Code und Java-Quellcode komplett getrennt sind, und es nur eine Handvoll HTML-Befehle gibt, die man braucht um Wicket-Klassen anzusprechen. Im Gegensatz zu Click ist Wicket aber wesentlich besser zu konfigurieren und stellt mehr Funktionalität zu Verfügung. Genauso wie bei Click gibt es zudem Online-Beispiele, bei denen man schon mal einige simple Anwendungen direkt am Quellcode nachvollziehen kann.

Meine erste Anwendung besteht aus einer Bildergallerie, die die Bilder in einer (Postgres)-Datenbank speichert. Es gibt natürlich auch eine Upload-Funktion zum einfachen Einfügen neuer Bilder. Im Folgenden will ich also einen kurzen Überblick über die (recht simple) Gallerie bieten und daran ein wenig zeigen, wie Wicket funktioniert.

Jetzt noch kurz was zur Vorbereitung, ehe es dann richtig los geht.

Vorbereitungen

Ich benutze Eclipse 3.4 mit integrierten JEE Tools. Dort habe ich mir ein neues dynamisches Web Projekt namens “ImageGallery” angelegt mit Tomcat 5.5 als Umgebung. Im erzeugten Ordner WebContent/WEB-INF/lib fügt man dann noch neben den ganzen Wicket 1.3.4 Bibliotheken die commons-dbcp.jar und commons-pool.jar für das Connection Pooling und die Postgres-Treiber (oder Treiber für eine andere Datenbank) ein.

Image-Objekt und Datenbank

Dann kann man soweit loslegen und die Datenbank anlegen, in der die Bilder und deren Metainformationen gespeichert werden sollen. Da es sich um eine ganz einfache Anwendung handelt, wird nur eine eindeutige ID, die Bilddatei mit Mime-Typ, Größe und Namen, der Name des Uploaders und der Zeitstempel des Uploads gespeichert.

CREATE TABLE images
(
  id serial NOT NULL,
  file bytea NOT NULL,
  mime_type character varying(20) NOT NULL,
  uploader character varying(50),
  date timestamp with time zone NOT NULL,
  name character varying(50),
  size integer NOT NULL,
  CONSTRAINT images_pkey PRIMARY KEY (id)
)

Um in Java nun so ein Bild abbilden zu können, baue ich mir ein Objekt Image zusammen, welches alle Spalten der Datenbank als Felder enthält. In Wicket werden solche Klassen auch als Model bezeichnet, daher wird die Klasse auch bei mir im Java-Package model abgelegt. Zudem implementiert sie das Interface IModel, da hierdurch die Anwendung ganz einfach mit den Objekten umgehen kann.

public class Image implements IModel {
	private static final long serialVersionUID = 6292744495006923192L;
	private Image image; // das Objekt selber!
	private int id;
	private String name;
	private byte[] file;
	private long size;
	private String mime;
	private String uploader;
	private Timestamp date;

	public Object getObject() {
		return image;
	}

	public void setObject(Object image) {
		this.image = (Image) image;
	}

	public void detach() {
	}
}

Natürlich hat jede Variable noch entsprechende Getter-und Settermethoden, die aus Platzgründen einfach mal weggelassen werden. Die drei Methoden detach(), getObject() und setObject() stammen übrigens aus dem implementierten Interface. Durch diese Methoden wird es möglich ganz einfach dann die Wicket-internen Mechanismen zu benutzen, um später die Tabelle für die Gallerieansicht zu füllen.

Jetzt kümmern wir uns um die Verbindung zur Datenbank und das reibungslose Übertragen von Bildern in die Datenbank und von der Datenbank in unsere Applikation.
Als erstes muss natürlich eine Verbindung zu der Datenbank hergestellt werden, auf der dann die ganzen SQL-Befehle abgesetzt werden. Damit das ganze auch gut konfigurierbar ist und mit beliebigen Datenbanken funktioniert, verwende ich eine Properties-Datei gallery.properties, die bei mir im Package config liegt. Eine minimale Properties-Datei könnte z.B. so aussehen:

#Einstellungen für die DB
url=jdbc:postgresql://localhost:5432/imagegallery
username=postgres
password=
driverClassName=org.postgresql.Driver

Jetzt kann in der Klasse, welche die Verbindung managen wird, einfach die Properties-Datei eingelesen werden und auf den Parameter gearbeitet. Die Klasse heißt bei mir ConnectionPooling und liegt im Package service. Ich habe ganz frech einfach mal die Version übernommen, die ich mit meiner Gruppe für das Datenbankprojekt verwendet habe.

public class ConnectionPooling {
	private static ConnectionPooling cp=null;
	private DataSource dataSource;
	private Properties dbProp;
	private PoolableConnectionFactory pcf;
	private GenericObjectPool gPool;

	//der Konstruktor ist private, weil wir ein Singleton nehmen.
	private ConnectionPooling() {
		dbProp = new Properties();
		try {
			ClassLoader loader = this.getClass().getClassLoader();
			InputStream stream = loader.getResourceAsStream("config/gallery.properties");
			//Properties einlesen
			dbProp.load(stream);
			dataSource = BasicDataSourceFactory.createDataSource(dbProp);
			//Pooling machen
			gPool = new GenericObjectPool();
			pcf = new PoolableConnectionFactory(new DataSourceConnectionFactory(dataSource),gPool,null,null,false,false);
			//ein Paar Verbindungen vorbereiten
			for (int i=0 ; i &lt; 5 ; i++) {
				   gPool.addObject();
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Wir benutzen ein Singleton-Pattern für den ConnectionPool
	 * @return Einen ConnectionPool. Immer den selben.
	 */
	public static synchronized ConnectionPooling getInstance() {
			if (cp==null) {
				cp=new ConnectionPooling();
			}
			return cp;
	}

	/**
	 * @return Eine neue Verbindung aus dem Pool
	 */
	public Connection getConnection(){
		Connection conn = null;
		try {
			conn = (Connection) gPool.borrowObject();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
}

Wir haben hier ein Singleton-Pattern, damit nur eine Instanz erzeugt wird, mehr brauchen wir ja auch nicht. Da wir das Apache Commons Pooling benutzen, reicht es aus einfach nur die Properties-Datei einzulesen und dann dem DataSource-Objekt zu übergeben, das einrichten erledigt dann die BasicDataSourceFactory für uns. Mit der PoolableConnectionFactory bauen wir dann die poolbare Verbindung auf, die auf die DataSource, die durch die Properties konfiguriert ist, zugreift und im GenericObjectPool gPool die entsprechenden zu poolenden Objekte findet.
Mit dem Aufruf von ConnectionPooling.getInstance().getConnection() wird dann immer eine vorbereitete Verbindung aus dem Pool zurückgeliefert.

Den Datenzugriff erledigt dann aber die ImageDAO-Klasse (DAO für Data Access Object). Hier zeige ich nur kurz das implementierte Interface, da es sich nur um einfache INSERT und SELECT Befehle handelt und es hier einfach zu lang würde.

public interface ImageDAO {

	/**
	 * Erstellt eine Liste aller Images in der Datenbank
	 * @return Liste der Bilder.
	 */
	public List<img alt="" /> listImages();

	/**
	 * Erstellt eine Liste aller Images sortiert nach orderBy
	 * @param orderBy Befehl in SQL-Syntax, wie "id asc", "name desc" ...
	 * @return sortierte Liste
	 */
	public List<img alt="" /> listImagesOrdered(String orderBy);

	/**
	 * Fügt ein neues Bild der Datenbank hinzu
	 * @param img einzufügendes Bild.
	 * @return true, wenn alles glatt geht, false bei einem Fehler
	 */
	public boolean addImage(Image img);

	/**
	 * Holt das entsprechende Image aus der Datenbank
	 * @param id
	 * @return Image-Objekt
	 */
	public Image getImage(int id);

	/**
	 * Löscht das Bild mit der entsprechenden ID
	 * @param id
	 * @return true, falls erfolgreich, false sonst
	 */
	public boolean deleteImage(int id);

	/**
	 * Das bereits geänderte Image-Objekt soll in der Datenbank aktualisiert werden.
	 * @param img das geänderte Objekt
	 * @return true, falls erfolgreich, false sonst
	 */
	public boolean editImage(Image img);
}

So, dass war’s fürs erste. Wir können nun schon die Datenbankverbindung herstellen und theoretisch mittels der ImageDAO auch einfügen, sortieren, editieren. Wie dann die Wicket-Applikation aufgebaut wird, sehen wir im nächsten Teil, der hoffentlich schnell folgt.

Das bisherige Eclipse-Projekt mit allen Klassen und Bibliotheken kann hier heruntergeladen werden.

Forsetzung:
Bildergallerie mit Apache Wicket, Teil 2

Letztes Update am 11.05.2009 18:57

Webserver in Java

Am Freitag, 11. Januar 2008 veröffentlicht unter Programmieren

Als Erweiterung zu dem vorhergehenden Beitrag Sockets in Java gibt es nun auch passend zum letzten TI Aufgabenblatt eine simple Webserver-Implementierung, die einfache GET Anfragen bearbeitet. Dabei lauscht der Server auf dem angegebenen Port und sucht um angegebenen Verzeichnis nach den angeforderten Dateien.

Natürlich kann der Webserver auch Bilder … Ich glaube, man könnte durch entsprechende Änderung in der Methode typ auch jeden anderen Mime-Typ definieren.

import java.io.*;
import java.net.*;
import java.util.regex.Pattern;

public class Webserver {

  final static int BUFFER = 512; 

  public static void main(String[] args) {
    if(args.length < 2)
      throw new RuntimeException("Zuwenig Parameter!");
    webserver(Integer.parseInt(args[0]),args[1]);
    System.out.println("Das hier sollte nie erreicht werden!");
  }

  public static void webserver(int port, String pfad) {
    ServerSocket server;
    Socket client;
    InputStream in;
    String angefragteDatei, inStr = "";
    String[] get;
    File datei;
    byte[] buffer = new byte[BUFFER];

    try {
      server = new ServerSocket(port);
      while(true) {
        client = server.accept();
        System.out.println("Erfolgreich Verbindung zum Client aufgebaut.");
        in = client.getInputStream();
        in.read(buffer);
        inStr = byteToString(buffer);
        get = inStr.split(" ", 3);
        if(get[0].equals("GET")) {
          angefragteDatei = get[1];
        }
        else
          throw new RuntimeException("Keine GET Anfrage!");
        if(angefragteDatei.equals("/"))
          angefragteDatei = "/index.html";
        System.out.println("Es wurde die Datei " + angefragteDatei + " angefragt.");
        datei = new File(pfad+angefragteDatei);
        if(!datei.exists()) {
          System.out.println("Datei wurde nicht gefunden, schreibe 404 Meldung:");
          schreibe404(client);
        }
        else {
          System.out.println("Folgendes wurde geschrieben:");
          schreibeDatei(datei,client);
        }

        client.close();
        System.out.println("Verbindung zum Client erfolgreich beendet.\nBereit für neue Anfrage...");
      }
    } catch (IOException e) {
      e.printStackTrace();
    }

  }

  // liest die angeforderte Datei f ein und schreibt sie in den Clientsocket c.
  private static void schreibeDatei(File f, Socket c) {
    FileInputStream fileIn;
    try {
      OutputStream out = c.getOutputStream();
      fileIn = new FileInputStream(f);
      byte[] buffer = new byte[BUFFER];
      String header = "HTTP/1.1 200 OK\r\nContent-Type: " + typ(f) + "\r\n\n";
      System.out.print(header);
      out.write(stringToByte(header));
      while (fileIn.read(buffer) != -1) {
        out.write(buffer);
        System.out.print(byteToString(buffer));
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  // schreibt die 404 Fehlermeldung in den Clientsocket
  private static void schreibe404(Socket c) {
    String error = "HTTP/1.1 404 Not Found\r\n\n<html>\n<head><title>404 - Not Found</title></head>\n<body><h1>404 - Not Found</h1></body>\n</html>\n";
    try {
      OutputStream out = c.getOutputStream();
      byte[] outByte = new byte[error.length()];
      for(int i=0;i<outByte.length;i++)
        outByte[i] = (byte) error.charAt(i);
      System.out.println(error);
      out.write(outByte);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  // Findet den Dateityp von File f raus.
  private static String typ(File f) {
    String tmp = f.getAbsolutePath();
    Pattern p = Pattern.compile( "[.]" ); // Damit er nach dem "." Splitten kann. Funktionierte mit split(".") nicht. Warum auch immer.
    String[] a = p.split(tmp);
    if(a[1].equals("html"))
      return "text/html";
    if(a[1].equals("jpg"))
      return "image/jpeg";
    else
      return "text/plain";
  }

  // Wandelt byte-Array in String um
  private static String byteToString(byte[] b) {
    String tmp = "";
    for(int i=0;i<b.length;i++)
      tmp += (char) b[i];
    return tmp;
  }

  // Wandelt String in byte-Array um
  private static byte[] stringToByte(String s) {
    byte[] b = new byte[s.length()];
    for(int i=0;i<b.length;i++)
      b[i] = (byte) s.charAt(i);
    return b;
  }
}

webserver.java
Ach, das Teil ist natürlich mal wieder logischer und kürzer und überhaupt cooler als selbiges in C …

Letztes Update am 4.05.2009 10:13

Sockets mit Java

Am Samstag, 08. Dezember 2007 veröffentlicht unter Programmieren

In TI ist momentan eine Aufgabe in C eine Socketabfrage zu starten. Adrian hat ja bereits gezeigt, wie das toll in Haskell geht, und da ich C auch absolut nicht mag und ewig gebraucht habe, um es hinzubekommen, wollte ich schauen, wie das in Java geht – natürlich geht es hier viel einfacher und logischer:

import java.io.*;
import java.net.*;

public class GetString {
  public static void main(String[] args) {
    if(args.length == 2) {
      try {
      // Wandelt 2. Parameter in int
      int port = Integer.parseInt(args[1]);
      // neuer Socket mit Hostname und Port wird erschaffen und geöffnet
      Socket s = new Socket(args[0],port);
      // wir lauschen am Socket über den InputStream
      InputStream in = s.getInputStream();
      byte[] b = new byte[1024];
      in.read(b); // wir lesen vom InputStream ins Bytearray ein
      String out = "";
      // solange noch was im Array steht, wandle byte in char um
      // und hänge es an die Ausgabe an
      for(int i=0; i<b.length;i++)
        if(b[i] != 0)
          out += (char)b[i];
      }
      // String ausgeben und Verbindung schließen
      System.out.println(out);
      s.close();
      }
      catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}

Wenn man jetzt also über die Konsole java GetString time.fu-berlin.de 13 aufruft erhält man die aktuelle Zeit Sat Dec 8 00:24:06 2007

Letztes Update am 4.05.2009 10:12

Naja's Blog
© 2007-2010 Naja's Corner
Artikel (RSS) und Kommentare (RSS).
Creative Commons License