Warning: Constant ABSPATH already defined in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php on line 28 Warning: Cannot modify header information - headers already sent by (output started at /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-config.php:28) in /customers/e/7/0/florian-oeser.de/httpd.www/wordpress/wp-includes/feed-rss2.php on line 8 How-To’s – Development-Blog von Florian Oeser http://www.florian-oeser.de "Die Wissenschaft hat keine Heimat, denn das Wissen gehört der Menschheit und ist ein Leuchtturm, der die Welt erhellt." (Louis Pasteur) Thu, 18 Jun 2009 17:46:38 +0000 de-DE hourly 1 https://wordpress.org/?v=6.0.8 HowTo: Extension Methods http://www.florian-oeser.de/2009/02/17/howto-textlokalisierung-mit-extension-methods/ http://www.florian-oeser.de/2009/02/17/howto-textlokalisierung-mit-extension-methods/#comments Tue, 17 Feb 2009 21:01:23 +0000 http://www.florian-oeser.de/?p=217 Dieses How-To wurde ebenfalls auf XNA.mag veröffentlicht und kann auch dort diskutiert werden.

Einleitung

In diesem kleinen How-To möchte kurz erklären was Extension Methods sind, wie man sie anwendet und auf was man achten muss.

Umsetzung

Was sind überhaupt Extension Methods?

Extension Methods sind integraler Bestandteil von C# 3.0 und dem .Net-Framework  3.5!Diese Methoden erlauben es vorhandene Typen zu erweitern, ohne eine abgeleitete Klasse zu implementieren oder den ursprünglichen Typ erneut kompilieren zu müssen. Extension Methods sind spezielle statische Methoden werden aber wie Instanzmethoden aufgerufen. Instanzmethoden haben aber Vorrang gegenüber Extension Methods, dass heißt eine vorhandene Methode kann nicht durch eine Extension Method ersetzt werden. Weiterhin können sie nur auf öffentliche Member und Methoden des Types den sie erweitern zugreifen, da sie sonst das Prinzip der Kapselung verletzt werden  würden.

Kleines Beispiel gefällig?
namespace ExtensionMethods
{
   public static class Extensions
  {
      public static string EliminateWhiteSpace(this string param)
     {
        return param.Replace(' ', string.Empty);
     }
  }
}

Der erste Paramter in einer Extension Method wird mit dem Operator this versehen und entspricht dem Typ auf dem die Extension Method angewendet wird. In diesem Falle string. Die Klasse und die eigentliche Methode muss static sein.

Wie verwendet man jetzt diese Methode:

using ExtensionMethods;

namespace ConsoleApplication
{
    class Program
   {
      static void Main(string[] args)
      {
         Console.WriteLine("Hallo Welt!".EliminateWhiteSpace());
         Console.ReadLine();
      }
   }
}

Das  funktioniert da der Compiler die Extension Methods auflöst wenn der Namespace, in dem die Extension Method definiert wurde, an der gewünschten Stelle eingebunden wurde.  Das Einbinden des Namespaces ist wichtig und muss explizit geschehen. Wie erwähnt wird also die Extension Method wie eine Instanzmethode aufgerufen, liegt aber nach dem kompilieren  in der IL (intermediate language) als statischer Aufruf vor. Im Endeffekt wird also daraus:

Console.WriteLine(Extensions.EliminateWhiteSpace("Hallo Welt!"));

Ein weiteres Beispiel, welches nicht ganz so konstruiert ist. Bei der Spiele/Grafikprogrammierung ist es wichtig zu wissen wieviel Zeit zwischen zwei Frames (Bildern) vergangen ist. Das sieht folgendermaßen aus:

float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;

Die Extension Method dazu:

public static float GetElapsedSeconds(this GameTime gameTime)
{
    return (float)gameTime.ElapsedGameTime.TotalSeconds;
}

Und nun der neue Aufruf:

float dt = gameTime.GetElapsedSeconds();

Conclusion

Extension Methods bieten eine gute Möglichkeit vorhande Funktionlität zu erweitern und lassen sich im ‚Client-Code‘ intuitiv einsetzen. Weiterhin verbessert sich zumeist die Lesbarkeit des Codes (syntaktischer Zucker). Trotzdem sollte man sparsam mit ihnen umgehen und sie nur anwenden wenn sie unbedingt benötigt werden, da es schnell zu Wildwuchs kommen kann und der Code schlecht wartbar wird.

Interessante Links/Quellen

MSDN Prgramming Guide zu Extension Methods

C# 3.0 Extension Methods

Extension Methods und genauerer IL-Betrachtungen

Extension Methods in Bezug auf XNA

Extension Methods in Bezug auf XNA 2

Extension Methods in Bezug auf XNA 3

]]>
http://www.florian-oeser.de/2009/02/17/howto-textlokalisierung-mit-extension-methods/feed/ 1
How-To: Textlokalisierung mit .NET http://www.florian-oeser.de/2008/07/11/how-to-textlokalisierung-mit-net/ http://www.florian-oeser.de/2008/07/11/how-to-textlokalisierung-mit-net/#comments Fri, 11 Jul 2008 12:16:35 +0000 http://www.florian-oeser.de/?p=57 Dieses Tutorial wurde ebenfalls auf myCSharp und xnamag.de veröffentlicht und kann auch dort diskutiert werden!

Einleitung

Hallo und Herzlich Willkommen zu einem weiterem How-To. Diesmal geht es um die Textlokalisierung. Ein effizientes und zugleich einfaches System zur Lokalisierung, zumindest von Texten, muss in jeder Anwendung, die mehrsprachig ausgeliefert werden soll, vorhanden sein.

Wichtig dabei ist das alle Texte in externen Dateien liegen, damit sie sich problemlos übersetzen lassen. Texte gehören keinesfalls in den Programmcode. Es ist dann auch eminent wichtig das diese Textdateien eine gute Struktur haben, damit sich diese komfortabel übersetzten lassen und in die Anwendung eingebunden werden können. Deshalb sollten alle Texte übersichtlich formatiert und gleich in der richtigen Reihenfolge angeordnet sein. Es kann auch von Vorteil sein an diversen Stellen Zusatzinformationen an den Übersetzer mitzugeben. Wie so eine Datei aussehen kann sehen wir gleich, nur vorweg, es ist eh nicht die optimale Lösung.

Ich möchte euch bevor wir zur Umsetzung mit .NET kommen noch zwei andere Möglichkeiten zeigen wie man Texte lokalisieren kann. Danach wird dann auch deutlich warum die Umsetzung mit .NET das Beste ist.

Umsetzung

Alle drei Umsetzungen möchte ich anhand von diesem kleinen Beispiel verdeutlichen:


MenuEntry optionsMenuEntry = new MenuEntry("Options"));

Generell wird alles mit C# umgesetzt!

Lokalisierung über Stringexternalisierung

Die Möglichkeit ist die einfachste aber zugleich auch die Schlechteste. Zum Einem liegt der Text direkt im Programmcode und zum Anderen muss die Sprache zur Kompilierung feststehen. Für eine kleine Anwendung mit wenig Text ist es aber trotzdem eine schönere Lösung als die Texte direkt zu schreiben.

Prinzipiell beruht das Verfahren auf einer statischen Klasse in der die Strings externalisiert werden.

using System;
using System.Collections.Generic;
using System.Text;

namespace MarioWorldWars.Text
{
sealed class GermanText
{
public static String OPTIONSMENU = "Optionen";
}
}

MenuEntry optionsMenuEntry = new MenuEntry(GermanText.OPTIONSMENU));

Lokalisierung über externe Dateien

Kommen wir zu der Möglichkeit die ich in der Einleitung erwähnt habe. Nun kommen alle Texte sauber in eine externe Datei. Die könnte zum Beispiel so ausehen:

MENU_OPTS                         „Einstellungen“
MENU_CONTROLLEROPTS    „Steuerungseinstellungen“ ; bitte auf Plural achten

Hinter dem Semikolon könnte jetzt für den Übersetzter noch eine Bemerkung angebracht werden.

Diese Datei wird jetzt einfach eingelesen und der entsprechende String kann zurückgeben werden. Der Wechsel zwischen den verschiedenen Dateien ist natürlich problemlos zur Laufzeit möglich. Ein kleines Beispiel zum Einlesen:

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace ConsoleApplication1
{
class FileReader
{

private StreamReader sr;

public FileReader()
{
//read the file
sr = new StreamReader(@"test.txt", System.Text.Encoding.Default);

}

public String getString(String patter)
{
String line = string.Empty;
try
{
while ((line = sr.ReadLine()) != null)
{

if (line.Contains(patter)) //else read next line
{
int first = line.IndexOf(‚"‘);
int last = line.LastIndexOf(‚"‘);
//return the correct text related to the pattern
return line = line.Substring(++first, last – first);
}
else line = "Not found";
}
}
catch (Exception e)
{
Console.WriteLine("The file could not be read:");
Console.WriteLine(e.Message);
}

return line;
}
}
}

Ist natürlich so nicht zu gebrauchen, da die Funktion fehlt, um die Datei zu wechseln.

FileReader fr = new FileReader();
MenuEntry optionsMenuEntry = new MenuEntry(fr.getString("MENU_OPTS"));

Im Übrigen wurde dieses System in der /GameStar/Dev Ausgabe 01/08 vorgestellt in der es auch generell um die Lokalisierung von Spielen ging.

Lokalisierung mit .NET

Kommen wir nun zu der elegantesten Lösung. Die Textlokalisierung mit Ressourcendateien durch .NET!Das System ist sehr einfach und elegant. Auch wenn diese Dateien ja eigentlich zum Projekt gehören, lassen sie sich extern öffnen und so von Lokalisierungsagenturen bearbeiten. Somit gehören sie nicht direkt zum Programmcode und widersprechen deshalb auch nicht der wichtigen Regel zur Lokalisierung.

Hierzu legt man einfach für jede Sprache eine entsprechende Ressourcendatei an, in der dann die verschiedenen Texte in der jeweiligen Sprache verfasst werden. Auch da ist es direkt möglich ein entsprechenden Kommentar hinzuzufügen. Die Datei könnte dann wie folgt aussehen:

bsp_ressourcendatei

Der Name bleibt logischerweise in jeder Ressourcendatei der gleiche lediglich das Value ändert sich. Wichtig ist die Benennung der Datei. Das heißt das .NET Framework erkennt anhand des Suffixes um welche Sprachressource es sich handelt.

Das heißt beispielsweise das die Ressouce für den deutschen Text den Suffix *.de-DE am Ende des Dateinamens tragen muss. Es ist auch möglich eine Datei ohne Suffix anzulegen. Dann wird diese ausgewählt wenn ein Suffix, der übergeben wird, nicht explizit definiert wurde.

Alle Ressourcendateien müssen im Übrigen vor dem Suffix den gleichen Namen haben, damit diese von dem ResourceManager erkannt werden. Hier in dem Beispiel ResStrings.

ResStrings.resx

ResStrings.de-DE.resx

ResString.cs-CZ.resx

Alle Suffixe gibt es in der CultureInfo-Klasse nachzulesen.

Nun schreiben wir eine Klasse die zwei statische Methoden enthält um global auf diese zugreifen zu können. Zum Einen eine Methode die die jeweilige Textressource auswählt und zum Anderen eine die uns den entsprechenden String zurückgibt.

using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Threading;
using System.Resources;
using System.Reflection;

namespace Application.Text
{
sealed class Localization
{

private static ResourceManager resMgr;

public static void UpdateLanguage(string langID)
{
try
{
//Set Language
Thread.CurrentThread.CurrentUICulture = new CultureInfo(langID);

// Init ResourceManager
resMgr = new ResourceManager("Application.Text.ResStrings", Assembly.GetExecutingAssembly());

}
catch (Exception ex)
{
}
}

public static string getString(String pattern)
{
return resMgr.GetString(pattern);
}

}
}

Nun noch ein Beispiel wie diese Klasse im Zusammenhang mit unserem Beispiel funktioniert:

Localization.UpdateLanguage("de-DE");

MenuEntry optionsMenuEntry = new MenuEntry(Localization.getString("MENU_MAINOPTS"));

Der Aufruf zumindest einmalige Aufruf der UpdateLangugage() Methode ist erforderlich, weil in ihr der ResourceManager initialisiert wird.

Im Übrigen ist es auch möglich sich die String so zurückgeben zu lassen:

MenuEntry optionsMenuEntry = new MenuEntry(ResStrings.MENU_MAINOPTS);

Man ist also nicht gezwungen sich eine Methode zu schreiben, die den String zurückgibt. Das funktioniert weil hinter der eigentlichen Ressourcendatei eine Klasse steht und man so auf deren statischen Felder zugreifen kann.

Wollen wir die Sprache wechseln genügt der Aufruf der UpdateLangugage() Methode:

Localization.UpdateLanguage("de-DE");

Würden wir jetzt zum Beispiel den String „en-GB“ übergeben, würde die Ressource ohne Suffix aufgerufen werden, da ja „en-GB“ von uns nicht explizit angeben wurde.

Ich möchte noch darauf hinweisen, das wenn ihr mit Systemen wie den WinForms arbeitet, nicht vergessen dürft etwaige Buttons, Labels usw. dann auch zu updaten. Dies könnte gleich in UpdateLangugage() erfolgen oder besser in einer separaten Methode die dann von UpdateLangugage() aufgerufen wird.

Meine Quelle für diese Umsetzung war das kompakte Tutorial von Martin H.!

Hilfreiche Tools

Hier möchte ich noch kurz zwei wunderbare Tools erwähnen, die die Arbeit mit den Ressourcendateien erheblich vereinfachen.

Es kann ganz schön nervig sein bei mehreren Ressourcen und viel Text immer zwischen den Reitern hin- und herzuschalten. Abhilfe schafft hier der Zeta Resource Editor von Uwe Keim. Mit diesem Tool lassen sich mehrere Ressourcen nebeneinander betrachten und bearbeitet. Sehr sehr praktisch. Die Dateien einfach speichern und im Visual Studio die externen Änderungen bestätigen.

Das zweite Tool, Resource Refactoring Tool, ist ebenfalls sehr praktisch und erlaubt es Strings, die im Code stehen, schnell in eine Ressourcendatei zu packen. Weiterhin werden dann gleich die Strings durch den Ressourcenaufruf ersetzt.

Conclusion

Das war’s dann auch schon. Ich denke ihr habt eine schicke und effiziente Methode mit der Umsetzung durch .NET gesehen. Sicherlich gibt es noch andere Wege aber dies dürfte eine gute Basis darstellen eigene Anwendungen erfolgreich zu lokalisieren.

Ich möchte noch erwähnen dass das .NET Framework einen sehr großen, hier nicht weiter erörterten Funktionsumfang zur Lokalisierung von Anwendungen bereitstellt. Darunter zählen zum Beispiel Zeitangaben, Satzbau, Formatierungen, weitere Kulturinformationen und vieles mehr. Informationen dazu findet ihr unter anderem in der MSDN hier und hier.

Bei Fragen und Anregungen könnt ihr mir gerne mailen oder ein Kommentar hinterlassen!

]]>
http://www.florian-oeser.de/2008/07/11/how-to-textlokalisierung-mit-net/feed/ 3
HowTo: FDS-Tomcat + Datamanagement + Hibernate http://www.florian-oeser.de/2008/07/08/howto-fds-tomcat-datamanagement-hibernate/ http://www.florian-oeser.de/2008/07/08/howto-fds-tomcat-datamanagement-hibernate/#comments Tue, 08 Jul 2008 12:14:19 +0000 http://www.florian-oeser.de/?p=51 Einleitung

Das wird das letzte HowTo rund um die RIA-Entwicklung mit Flex, den LCDS und Hibernate auf den beiden Servern BlazeDS und FDS-Tomcat. Bisher haben wir gesehen wie man diese auf einem Linuxsystem installiert, mit Servlets Datenbankoperationen handelt und wie man dann mit Hibernate und und Flex ein Front- und Backend entwickeln kann.

Bis jetzt haben wir die Daten an den Client per RPC(HTTP Service und RemoteObject) gesendet und empfangen. Jetzt benutzen wir das wohl innovativste Element der Live Circle Data Services. Den synchronen Remote-Zugriff. Im Gegensatz dazu verlief alles bis jetzt asynchron und musste über das entspechende Result-Event gelöst werden. Jetzt werden Änderungen an den Daten zwischem dem Server und (wahlweise) allen Clients instant aktualisiert. Das heißt wenn ein Client Daten ändert wird automatisch das Backend informiert, welches sich sofort um die persistente Speicherung durch Hibernate kümmert und alle anderen Clients über die entsprechenden Änderungen informiert(push).

Wir verwenden dazu nur noch den FDS-Tomcat da dieser Service nicht im BlazeDS integriert ist. Sicherlich würde sich das nachrüsten lassen, jedoch habe ich mich damit nicht beschäftigt. Prinziepell halte ich mich an das Sample 8: Data Management Services aus dem Testdrive des FDS-Tomcat’s. Es kann also nicht schaden sich die Sachen dazu und das direkte Tutorial anzuschauen, da ich lediglich den bekannten HibernateHandler erweitere und nicht weiter auf Konfigurationen eingehe.

Im Übrigen benutzen wir auch nicht den Hibernate Assembler/Adapter sondern bleiben bei der direkten Hibernate-Implementierung. Falls ihr euch für diesen Assembler interessiert gibt es bei den Testdrive-Tutorials ebenfalls ein Beispiel. Das ist sehr ähnlich dem Sample 8 benutzt dann aber Hibernate.

Um die Umsetzung nachvollziehen zu können, möchte ich euch wieder gleich den Source geben. Darin erhalten ist dann ebenfalls gleich wieder ein SQL-Dump.

Die Umsetzung

Das Backend

Für die Umsetzung bedienen wir uns wieder der Stream-Tabelle aus dem Servlets-HowTo.

Nun werden wir ähnlich wie im Sample 8 die Tabelle auslesen und in ein DataGrid schreiben. Es wird nicht viel Arbeit, da wir nur eine neue Klasse einführen werden und ansonsten nur vorhandene Sachen erweitern.

Anfangen möchte ich mit dem sogenannten Streamassembler. Dieser implemtiert den AbstractAssembler aus dem LCDS. Das heißt wir müssen die Methoden fill(), getItem(), update() und deleteItem() überschreiben. Der Assembler ist sehr wichtig, da seine Methoden aufgerufen werden wenn es zu Änderungen am Tabelleninhalt kommt. Das heißt unsere Client arbeitet nicht mehr direkt mit dem HibernateAssembler. Das macht jetzt der Streamassembler direkt in den überschriebenen Funktionen. Da zum Aktualisieren der Daten aber erstmal nur fill() und update() wichtig ist, beschränke ich mich auf deren Implementierung. Die anderen könnt ihr sicher alleine implementieren 🙂

package flex.radioSiTY;

import java.util.List;
import java.util.Collection;
import java.util.Map;

import flex.data.DataSyncException;
import flex.data.assemblers.AbstractAssembler;

public class StreamAssembler extends AbstractAssembler {

	public Collection fill(List fillArgs) {
		HibernateHandler service = new HibernateHandler();
		return service.getStreams();
	}

	/*public Object getItem(Map identity) {
		
		}
	}*/

	public void createItem(Object item) {
		
	}

	public void updateItem(Object newVersion, Object prevVersion, List changes) {
		HibernateHandler service = new HibernateHandler();
		service.update((Stream) newVersion);		
	}

	public void deleteItem(Object item) {
		
	}
	
}

Nun kommen die Änderungen an unserem bekannten HibernateHandler. Diesem fügen wir die zwei neuen Funktionen, die der StreamAssembler benötigt, hinzu:

public List getStreams() throws DAOException {

		List list = new ArrayList();
		Session sess = null;
		Transaction trx = null;

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			System.out.println("\nStreams:");
			List streams = sess.createQuery("from Stream").list();
			for (int i = 0; i < streams.size(); i++) {
				Stream stream = (Stream) streams.get(i);
				list.add(new Stream(stream.getId(), stream
						.getUrl(), stream.getHomepage(), stream.getName(),
						stream.getgenreId(), stream.getImage()));
			}
				trx.commit();
			} catch (HibernateException ex) {
				if (trx != null)
					try {
						trx.rollback();
					} catch (HibernateException exRb) {
					}
				throw new RuntimeException(ex.getMessage());
			} finally {
				try {
					if (sess != null)
						sess.close();
				} catch (Exception exCl) {
				}
			}
		return list;

	}
	
	public void update(Stream stream) throws DAOException {

		Session sess = null;
		Transaction trx = null;
		
		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			
			sess.update(stream);
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
					System.out.println(exRb.toString());
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}

	}
&#91;/sourcecode&#93;

Damit das ganze funktionieren kann brauchen wir in der Streamklasse noch zwei Konstruktoren:

&#91;sourcecode language='java'&#93;
package flex.radioSiTY;

import java.util.*;

public class Stream 
{
  private int    id;
  private String url;
  private String homepage;
  private String name;
  private int genreId;
  private String image;
  private Set    user     = new HashSet();
  private Set    comments = new HashSet();
  
  public Stream() {name = "test"; }
  
  public Stream(int id, String url, String homepage, String name, int genreId, String image) {
		this.id = id;
		this.url = url;
		this.homepage = homepage;
		this.image = image;
		this.name = name;
		this.genreId = genreId;		
	}
  
  public Stream(Stream stream) {
		this.id = stream.getId();
		this.url = stream.getUrl();
		this.homepage = stream.getHomepage();
		this.image = stream.getImage();
		this.name = stream.getName();
		this.genreId = stream.getgenreId();	
	}

  public int    getId()         { return id; }
  public String getUrl()    { return url; }
  public String getHomepage()    { return homepage; }
  public String getName()      { return name; }
  public int getgenreId()    { return genreId; }
  public String getImage()      { return image; }
  public Set    getUser()     { return user; }
  public Set    getComments() { return comments; }

  public void setId(         int id         ) { this.id = id; }
  public void setUrl( String url    ) { this.url = url; }
  public void setHomepage( String homepage    ) { this.homepage = homepage; }
  public void setName( String name    ) { this.name = name; }
  public void setgenreId ( int genre    ) { this.genreId = genre; }
  public void setImage(   String image      ) { this.image = image; }
  public void setUser(     Set user     ) { this.user = user; }
  public void setComments( Set comments ) { this.comments = comments; }
}
&#91;/sourcecode&#93;

Das waren jetzt auch schon alle Änderungen die das Backend betreffen. 

<h5>Das Frontend</h5>

Für das Frontend erstellen wir einfach ein Datagrid, welches nun durch den StreamAssembler über die Methode fill() gefüllt wird. Das Datagrid liest dann die Collection automatisch ein. Ganz wichtig ist jetzt hier das wir den DataService und nicht mehr das RemoteObject benutzen. Hier der Source:


<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" backgroundColor="#FFFFFF" viewSourceURL="srcview/index.html">
	
	<mx:ArrayCollection id="streams"/> 
	<mx:DataService id="ds" destination="stream"/>
	<Stream/>
	
	<mx:DataGrid dataProvider="{streams}" editable="true" width="100%" height="100%">
		<mx:columns>
			<mx:DataGridColumn dataField="url" headerText="Url"/>
			<mx:DataGridColumn dataField="homepage" headerText="Homepage"/>
			<mx:DataGridColumn dataField="name" headerText="Name"/>
			<mx:DataGridColumn dataField="genreId" headerText="Genre"/>
			<mx:DataGridColumn dataField="image" headerText="Image"/>
		</mx:columns>
	</mx:DataGrid>
	
	<mx:Button label="Get Data" click="ds.fill(streams)"/> 
	
</mx:Application>

Die Methode updateItem() des StreamAssemblers muss nicht aufgerufen werden, da diese bei Änderungen an der Tabelle automatisch aufgerufen wird 🙂

Damit das ganze überhaupt funktionieren kann müssen wir natürlich noch die Destination für den DataService anlegen. Das passiert ähnlich wie bei den RemoteObjects. Nur benutzen wir jetzt nicht mehr die remoting-config.xml sondern die datamanagement-config.xml!Einfach folgenden Eintrag hinzufügen:

<destination id="stream">

        <adapter ref="java-dao" />

        <properties>
            <source>flex.radioSiTY.StreamAssembler</source>
            <scope>application</scope>

            <metadata>
                <identity property="id"/>
            </metadata>

            <network>
                <session-timeout>20</session-timeout>
                <paging enabled="false" pageSize="10" />
                <throttle-inbound policy="ERROR" max-frequency="500"/>
                <throttle-outbound policy="REPLACE" max-frequency="500"/>
            </network>
        </properties>
    </destination>

Conclusion

So das wars dann auch schon. Öffnet die Tabelle einfach mal in zwei Tab’s und ändert in der einen etwas. Siehe da die andere Tabelle wird instant aktualisiert 🙂

Leider habe ich mich nicht mit den verschiedenen Einstellungsmöglichkeiten was das Datamanagement betrifft auseinandersetzten können. Da gibt es einige Sachen die sich konfigurieren lassen. Zum Beispiel auf welchen Clients überhaupt aktualisiert werden soll etc. Dazu findet ihr aber in dem FDMS-Tutorial wenn ich micht recht entsinne einiges.

So das wars für diese How-To Reihe. Ich hoffe bei euch hat alles geklappt und hattet damit genausoviel Spass wie ich 🙂

Bei Fragen und Anregungen könnt ihr mir wie immer mailen oder ein Kommentar hinterlassen!

]]>
http://www.florian-oeser.de/2008/07/08/howto-fds-tomcat-datamanagement-hibernate/feed/ 1
HowTo: BlazeDS/FDS-Tomcat + Servlets + MySQL http://www.florian-oeser.de/2008/06/08/howto-part-2-blazedsfds-tomcat-servlets-mysql/ http://www.florian-oeser.de/2008/06/08/howto-part-2-blazedsfds-tomcat-servlets-mysql/#comments Sun, 08 Jun 2008 11:36:35 +0000 http://www.florian-oeser.de/?p=44 Einleitung

In dem HowTo-Reihe möchte ich euch kurz zeigen wie einfach es ist, Daten mit Hilfe eines Servlets zu lesen und zu schreiben. Ich werde nicht alles in Detail erklären. Deshalb wäre es sinnvoll, wenn ihr schonmal mit einem der beiden Server gearbeitet habt, ein Servlet geschrieben habt und ein klein wenig Ahnung von SQL habt 😉

Als kleines Beispiel haben wir in Anlehnung an Projekt radioSiTY eine Datenbank, in der es eine Tabelle User gibt. Ich zeige zuerst wie man die einzelnen Felder ausliest und wie man anschließent einen neuen Benutzer hinzufügt.

servlets_tbluser

Für die Entwicklung der Servlets kann ich übrigens Eclipse for JEE empfehlen. Dort kann man dann ein Dynamic Web Project anlegen und kann direkt Servlets erstellen. Zum Einen hat man so ein fertiges Codegerüst und zum Anderen braucht man sich nicht mehr um die Servlet jar’s kümmern. Und man kann zusätzlich Server anlegen auf dennen kompiliert wird.

Umsetzung

Auslesen der Datenbank

Um überhaupt mit Java bzw. einem Servlet und einer Datenbank zu kommunizieren, sind die sogenannten JDBC Treiber erforderlich. Diesen Treiber gibt es für viele Datenbanken egal ob PostgreSQL, HSQL oder MySQL. Die Treiber nennt MySQL den Connector/J. Zum Download gibt es den natürlich auf der Homepage von MySQL. Diese *jar muss jetzt in das JDK Verzeichniss unter jre/lib/ext kopiert werden.

Die Umsetzung ist relativ trivial. Um eine Spalte einer Tabelle auszulesen ist nicht mehr erforderlich als eine Verbindung zur Datenbank aufzubauen, den Query zu übergeben und das Ergebnis in einer Schleife auszugeben. Folgendes Servlet realisiert genau das:

import java.io.IOException;
import javax.servlet.*;
import java.io.*;
import java.sql.*;

import javax.servlet.*;
import javax.servlet.http.*;

public class FirstExample extends HttpServlet {
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {

		Connection con = null;
		Statement stmt = null;
		ResultSet rs = null;

		try {
			res.setContentType("text/html");
			PrintWriter out = res.getWriter();

			Class.forName("com.mysql.jdbc.Driver").newInstance();
			con = DriverManager.getConnection("jdbc:mysql:///radiositiy_db1",
					"root", "root");

			stmt = con.createStatement();
			rs = stmt.executeQuery("SELECT * FROM tbl_user");

			while (rs.next()) {

				out.println(rs.getString(2) + "<br>");

			}
			out.close();

			if (!con.isClosed())
				System.out.println("Successfully connected to "
						+ "MySQL server using TCP/IP...");

		} catch (Exception e) {
			System.err.println("Exception: " + e.getMessage());
		} finally {
			try {
				if (con != null)
					con.close();
			} catch (SQLException e) {
			}
		}

	}
}

Um eine Datenbankverbindung herzustellen brauchen wir eine Connection und ein Statement welches das Query ausführt und in ein Resultset zurückgibt. Um eine Verbindung aufzubauen sind die Angaben des Treibers, der Datenbank selbst, des Benutzernames und des Passwortes nötig. Bis auf Ersteres muss natürlich alles entsprechend angepasst werden.

Als Query übergeben wir ein einfaches SELECT Statement. In dem ResultSet steht jetzt das komplette Ergebniss der Abfrage. Dieses wird dann in einer Schleife ausgeben. Das Argument bei rs.getString() gibt dabei den Spaltenname an. Da der ResultSet wie gesagt alles speichert, ist es problemlos möglich auch noch zum Beispiel die id sich ausgeben zu lassen. Unabhänig davon ob es zu Fehler kam oder nicht, muss natürlich die Verbindung wieder geschlossen werden.

Noch ein paar Worte zum kompilieren. Für den BlazeDS kann man in Eclipse einen neuen Server analog zu einem normalen Tomcat anlegen. Angegeben werden muss hier nur der Tomcat Ordner im BlazeDS Verzeichnis.

servlets_serverconf

Der FDS-Tomcat hat dieses Verzeichniss nicht und arbeitet aus dem /bin Ordner herraus. Leider erkennt das Eclipse nicht an und kann somit auch nicht für den FDS-Tomcat kompilieren. Alles was ich noch in der Richtung probiert hatte, endete mit mit Fehlern wie das der Server nicht die 2.5 J2EE Module unterstützt. Naja vielleicht liegt es auch daran das der FDS nur Version 5.5 benutzt. Oder ich hab mich einfach zu dusselig angestellt 😀 Über Lösungen von euch würde ich mich freuen…Es gibt trotzdem eine Möglichkeit für den FDS-Tomcat zu kompileren. Einfach die gewohnte main() einfügen und fertig. So lässt sich nämlich wieder in der nomalen Java Perspektive von Eclipse kompilieren.

Nun kopiert ihr das kompilierte Servlet direkt nach WEB-INF/classes auf den Server. Damit der Server weiß wo euer Servlet liegt, gibt es die sogenannte web.xml. In der fügen wir einfach folgendes unten an:

<servlet>
<servlet-name>FirstExample</servlet-name>
<servlet-class>FirstExample</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FirstExample</servlet-name>
<url-pattern>/FirstExample</url-pattern>
</servlet-mapping>

Nun braucht ihr noch noch den Server zu starten und das Servlet so aufrufen: http://localhost:8400/samples/FirstExample . Ich habe mich dabei wie ihr seht für den BlazeDS entschieden und den WEB-INF unter samples benutzt. Wenn alles geklappt hat seht ihr jetzt die Benutznamen in einer Spalte ausgegeben. Im Übrigen seht ihr jetzt auch in der Konsole das erfolgreich zur MySQL Datenbank verbunden wurde. Alle Ausgaben die über System.out normalerweise in der IDE-Konsole ausgeben werden, sind jetzt in der Serverkonsole zu sehen.

So kommen wir zum zweiten Teil. Nämlich dem Schreiben in die Datenbank. Das ist ebenfalls ziemlich einfach:

import java.io.IOException;
import javax.servlet.*;
import java.io.*;
import java.sql.*;


import javax.servlet.*;
import javax.servlet.http.*;



public class FirstExample extends HttpServlet {
	
	private String name = new String("second");
	private String username= new String("example");
	private String password = new String("geheim");
	private String mail = new String("test@web.de");
	private String birthdate = new String("1965-01-23");
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res)
			throws ServletException, IOException {
		
		Connection con = null;
		Statement stmt = null;
		
		
		try {
			res.setContentType("text/html");
			PrintWriter out = res.getWriter();

			Class.forName("com.mysql.jdbc.Driver").newInstance();
			con = DriverManager.getConnection("jdbc:mysql:///radiositiy_db1", "root",
					"root");
			
			stmt = con.createStatement();
			stmt.execute("INSERT INTO tbl_user (fld_name,fld_username,fld_password,fld_email,fld_birthdate)" +
					" VALUES ('"+name+"','"+username+"','"+password+"','"+mail+"','"+birthdate+"')");
						

			if (!con.isClosed())
				System.out.println("Successfully connected to "
						+ "MySQL server using TCP/IP...");

		} catch (Exception e) {
			System.err.println("Exception: " + e.getMessage());
		} finally {
			try {
				if (con != null)
					con.close();
			} catch (SQLException e) {
			}
		}

	}
}

Aus Platzgründen hab ich die Imports mal weggelassen. Da wir beim Schreiben logischerweise kein Ergebniss bekommen brauchen wir das ResultSet nicht mehr. Ausserdem heißt die aufzurufende Methode nicht mehr executeQuery sondern nur noch execute(). Der Rest ist denk ich mal relativ selbsterklärend.

Conclusion

Wir haben gelernt wie einfach es doch ist mit Servlets/Java Datenbankoperationen durchzuführen. Ich denke das war nicht allzuviel und dürfte leicht verdaubar sein 😉

Wenn ich Zeit habe formuliere ich einige Sachen noch aus und werde noch eine kleine Flexapplikation dazu schreiben.

Für Anregungen, Verbesserungen etc. könnt ihr mir gerne mailen oder ein Kommentar hinterlassen!

]]>
http://www.florian-oeser.de/2008/06/08/howto-part-2-blazedsfds-tomcat-servlets-mysql/feed/ 1
HowTo: BlazeDS/FDS-Tomcat + Flex + Hibernate + MySQL http://www.florian-oeser.de/2008/06/07/howto-blazedsfds-tomcat-flex-hibernate-mysql/ http://www.florian-oeser.de/2008/06/07/howto-blazedsfds-tomcat-flex-hibernate-mysql/#respond Sat, 07 Jun 2008 12:41:19 +0000 http://www.florian-oeser.de/?p=41 Einleitung

In diesem HowTo möchte ich euch zeigen wie man mit den beiden Servern eine persistente Datenhaltung realisiert. Dabei werden wir mit MySQL/JDBC und die LiveCycle Data Services (kurz LCDS) arbeiten. Die LCDS sind in beiden Servern bereits integriert. Wir werden alle Datenkommunikationen per RemoteObject betreiben. Ich werde nicht auf Basics eingehen, deshalb ist es empfehlenswert die Testdrive Tutorials schon einmal angeschaut und nachvollzogen zu haben. Ich werde auch nicht auf die Installation der beiden Server eingehen. Wie man

com.mysql.jdbc.Driverbeide auf einem Linux-System installiert habe ich in diesem HowTo bereits gezeigt.

Am Ende des Tutorials haben wir als Frontend eine minimale Flexanwendung(mxml-Anwendung) geschrieben, die über Data Access Objects (DAO) Daten in die Datenbank schreibt und auch ausliest. Für das HowTo verwende ich Teile aus unserem Projekt radioSiTY.

Gleich vorweg der Source. Da ich nicht alle Klassen und Config’s zeige könnt ihr so besser mitarbeiten. Dort findet ihr auch den SQL-Dump für unsere kleine Datenbank und müsste diese so nicht selbst erstellen 😉

Source (.rar, 271KB)

Vorbereitungen Datenhaltung

In radioSiTY haben wir verschiedene Funktionen mit Hibernate und RPC umgesetzt. Für dieses HowTo modellieren wir die Kommentar- und Streamfunktionen. Das heißt konkret das wir folgende Funktionen implementieren:

  • addStream()
  • addComment()
  • showComments()
  • showStreams()

Da zu jedem Kommentar auch ein Benutzer gehört dürfen wir natürlich eine User-Tabelle nicht vergessen. Unsere Datenbank sieht also wie folgt aus:

howto_datamodel

Damit ihr das Tutorial möglichst schnell nachvollziehen könnt habe ich hier mal den SQL-Dump. Denn braucht ihr nur mit folgenden Befehl in die Datenbank einschleusen:

mysql.exe -u username -p radiositiy_db1 < db.sql

Die Datenbank muss vorher natürlich angelegt sein. Auch der username muss entsprechend angepasst werden.

Die Entity-Klassen in Java haben schließlich folgende Struktur:

howto_classmodel

Wir haben zum einen einen Hibernatehandler der Hibernate ansich initialisiert und alle Funktionen hält. Zum Anderen haben wir die Klassen User, Comments und Stream. Diese Klassen stellen das DAO dar und haben lediglich Getter- und SetterMethoden. Aus Gründen der Übersichtlichkeit habe ich aber nicht alle Methoden aufgeführt.

Vorbereitung zu Hibernate

Hibernate braucht um Arbeiten zu können die Verbindungsangaben zur Datenbank. Desweiteren muss Hibernate auch die einzelnen Tabellen und deren Felder kennen. Diese Angaben macht man in sogenannten Mappingfiles(DAO<->Tabelle) im XML-Format. Zunächst legen wir aber erstmal die Konfigurationsdatei, mit dem Namen hibernate.cfg.xml, für Hibernate an:

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
<session-factory>

<!-- Database connection settings -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost/radiositiy_db1</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>

<!-- JDBC connection pool (use the built-in) -->
<property name="connection.pool_size">1</property>

<!-- SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

<!-- Enable Hibernate's automatic session context management -->
<property name="current_session_context_class">thread</property>

<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>

<!-- Disable the second-level cache  -->
<property name="cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>        <!-- Echo all executed SQL to stdout -->

<property name="show_sql">true</property>

<!-- Load the database table mapping file -->
<mapping resource="Product.hbm.xml"/>
<mapping resource="Stream.hbm.xml"/>
<mapping resource="User.hbm.xml"/>
<mapping resource="comments.hbm.xml"/>
</session-factory>

</hibernate-configuration>

Die erste property ist die Treiberangabe. In unserem Fall wäre das MySQL. Hier könnte man dann auch andere Treiber, wie etwa für HSQL, angeben. Die nächsten drei property Angaben sind selbsterklärend und benötigen lediglich eine Anpassung an eure Datenbank. Wichtig ist weiterhin das man bei der dialect property den MySQLDialect wählt. Analog zum Treiber sind auch hier andere Angaben möglich. Auf die property show_sql komme ich bei der Implementierung des HibernateHandlers noch einmal zu sprechen. Sehr wichtig sind ganz unten die mapping resource’s. Das sind unsere oben angesprochenen Mappingfiles. Sie enthalten im Namen immer *.hbm.

Wobei wir auch schon beim Thema wären: Die Implementierung dieser Files. Aus Gründen der Übersichtlichkeit poste ich nur die Stream.hbm.xml.:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="flex.radioSiTY.Stream" table="tbl_streams">
<id name="id" column="id" type="integer">
<generator class="native"/>
</id>
<property name="url"  column="fld_url"  type="string"/>
<property name="homepage"   column="fld_homepage"   type="string"/>
<property name="name"   column="fld_name"   type="string" />
<property name="genreId" column="genre_id" type="integer"/>
<property name="image" column="fld_image" type="string"/>
<set name="user" table="tbl_stream_comments" lazy="true" inverse="true" >
<key column="stream_id"/>
<many-to-many class="flex.radioSiTY.User" column="id"/>
</set>
</class>
</hibernate-mapping>

Wie man sieht steht in jeder Mappingfile die Klasse/Tabelle die gemappt werden soll. Danach folgen die Angaben zu den einzelnen Spalten. Der type ist immer auf die Javadatentypen bezogen und nicht auf die MySQL-Typen. Ihr solltet auch immer zweimal schauen ob der Spaltenname mit dem in der Datenbank übereinstimmt. Zu guter letzt muss dann noch in einem set die Beziehung zu der anderen Tabelle hergestellt werden. Wichtig zu erwähnen wären noch die many-to-one Relationen in der comments.hbm.xml. Diese Angaben sind essentiel da eine Kommentar schließlich die Angaben beinhalten, von welchen Benutzer, zu welchen Stream ein Kommentar geschrieben wurde.

Alle *.hbm’s und die hibernate.cfg.xml kommen direkt unter das \WEB-INF\classes Verzeichniss.

Ich möchte an dieser Stelle noch die Beispielanwendung zu Hibernate von Thorsten Horn erwähnen. Das ist ebenfalls ein schönes Tutorial und geht genauer auf die Sachen rund um Hibernate ein. Jedoch ohne Flex-Frontend und mit HSQL 😉

Wenn ihr den FDS-Tomcat verwendet könnt ihr getrost zum nächsten Kapitel springen. Falls ihr den BlazeDS verwendet müssen wir noch ein paar Hibernate *.jar’s kopieren. Dazu müsst ihr aber zuerst den sogenannten Hibernate Core herunterladen. Das Archiv entpacken und folgenden *.jar’s nach BlazeDS\tomcat\webapps\samples\WEB-INF\lib kopieren: asm.jar, cglib-2.1.3.jar, checkstyle-all.jar, commos-collections-2.1.1.jar, dom4j-1.6.1.jar, hibernate3.jar, jta.jar und die log4j-1.2.11.jar.

Das wars dann auch schon 😀

Implementierung Backend (Hibernatehandler und DAO’s)

So jetzt gehts ans Eingemacht. Zunächst die Entity-Klassen. Auch hier poste ich nur die Stream.java, da die Anderen zu 90% gleich sind.

package flex.radioSiTY;

import java.util.*;

public class Stream 
{
  private int    id;
  private String url;
  private String homepage;
  private String name;
  private int genreId;
  private String image;
  private Set    user     = new HashSet();
  private Set    comments = new HashSet();
  
  public Stream() { }
  

  public int    getId()         { return id; }
  public String getUrl()    { return url; }
  public String getHomepage()    { return homepage; }
  public String getName()      { return name; }
  public int getgenreId()    { return genreId; }
  public String getImage()      { return image; }
  public Set    getUser()     { return user; }
  public Set    getComments() { return comments; }

  public void setId(         int id         ) { this.id = id; }
  public void setUrl( String url    ) { this.url = url; }
  public void setHomepage( String homepage    ) { this.homepage = homepage; }
  public void setName( String name    ) { this.name = name; }
  public void setgenreId ( int genre    ) { this.genreId = genre; }
  public void setImage(   String image      ) { this.image = image; }
  public void setUser(     Set user     ) { this.user = user; }
  public void setComments( Set comments ) { this.comments = comments; }
}


[/Sourcecode]

Jetzt unser <em>HibernateHandler</em>:

/*!
 * @brief Hibernate Handler
 * 
 * Der HibernateHandler stellt die Schicht zwischen der Flexanwendung und der Datenbank dar. Hibernate ist ein 
 * objektorientiertes Persistence Framework. Das heißt alle Daten liegen als Objekte vor und werden auch so in die Datenbank geschrieben und gelesen.
 * Aufgabe des Handlers ist es die Verbindung zur Datenbank herzustellen, eine Session und Transaction aufzubauen.
 *  
 * Wichtig: Hibernate jar's müssen dem BuildPath hinzugefügt werden!
 */

package flex.radioSiTY;

import java.util.*;

import org.hibernate.*;
import org.hibernate.cfg.Configuration;

import java.security.MessageDigest;
import java.sql.SQLException;
import java.io.IOException;
import java.io.PrintWriter;
import java.math.BigInteger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HibernateHandler {
	public SessionFactory sessionFactory = null;

	// public Session sess = null;
	// public Transaction trx = null;

	// ! @brief Hibernate wird im Konstruktor initialisiert
	public HibernateHandler() {
		/*
		 * Die Konfiguration für die Datenbankverbindung und das Mapping der
		 * einzelnen Tabellen liegt in der 'hibernate.conf'
		 */
		if (sessionFactory == null) {
			try {
				System.out
						.println("------------------------------------------------------");
				System.out.println("Initializing Hibernate");
				sessionFactory = new Configuration().configure()
						.buildSessionFactory();
				System.out.println("Finished Initializing Hibernate");
				System.out
						.println("------------------------------------------------------");
			} catch (HibernateException ex) {
				ex.printStackTrace();
				System.exit(5);
			}
		}
	}

	public static void main(String[] args) {

		/*
		 * ! Dies sind lediglich Tests die man lokal durchführen kann. Der
		 * Output erfolgt hier über die Console. Die eigentlichen Aufrufe der
		 * Mehtoden erfolgt durch Flex über RPC. Somit ist die main() eigentlich
		 * hinfällig und dient lediglich weiteren lokalen Tests.
		 */

		 HibernateHandler handler = new HibernateHandler();
		// String test = new String();
		// handler.showCommentsFromStream(78, false);
		// Date date = new Date(1922,110,23);
		// handler.updateUser(25, "Hans Test", "nickname" , "egal",
		// "test@test.de", date);
		// handler.addStream("http://test.de", "http://test.de", "testStream",
		// 5, "C:/test2.image");
		// egal = handler.showUsers();
		// egal =handler.showStreams();
		// handler.showComments();
		// System.out.println(test);
		// User user = new User();
		// Stream stream = new Stream();
		// user = handler.getUser(25);
		// stream = handler.getStream(88);
		// handler.addComments(6, 9, "test4mitch", date, 0);
		// System.out.println(user.getName() );
		// System.out.println(stream.getName());
		
		 //LCDS
		 /*List testListe = new ArrayList(handler.getStreams());
		 for(int i = 0; i< testListe.size(); i++) System.out.println((Stream)testListe.get(i));*/
		 
		 /*Stream testStream = new Stream(handler.getStream(16));		
		 testStream.setName("test me");		
		 handler.update(testStream);*/
		 
	}

	
	/*
	 * ! @brief Diese Methode fügt eine Stream in die DB ein. Verwendet wird sie
	 * wenn ein Benutzer einen Stream hinzufügt.
	 */
	public void addStream(String url, String homepage, String name,
			int genreId, String image) {
		Session sess = null;
		Transaction trx = null;
		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			Stream stream = new Stream();
			// stream.setId(id); wird automatisch inkrementiert
			stream.setUrl(url);
			stream.setHomepage(homepage);
			stream.setName(name);
			stream.setgenreId(genreId);
			stream.setImage(image);

			sess.save(stream);
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
	}

	/*
	 * ! @brief Diese Methode fügt ein Kommentar in die DB ein. Verwendet wird
	 * sie wenn ein Benutzer ein Kommentar hinzufügt.
	 */
	public void addComment(int userId, int streamId, String text, int blocked) {
		System.out.println("\n\nInit add!\n");

		Date date = new Date();

		System.out.println("Comment in DB speichern!!!!!\n");
		Session sess = null;
		Transaction trx = null;

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			Comments comment = new Comments();
			// comment.setId(id); wird automatisch inkrementiert
			comment.setText(text);
			System.out.println(text + "\n");
			comment.setBlocked(blocked);
			System.out.println(blocked + "\n");
			comment.setDate(date);
			System.out.println(date + "\n");
			comment.setUser(getUser(userId));
			comment.setStream(getStream(streamId));

			sess.save(comment);
			System.out.println("Now added!\n\n");
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
	}

	/*
	 * ! @brief Die Methode gibt ein Userobjekt nach ID zurück.
	 * 
	 * Wird von addComment() verwendet @param userId ID des Benutzers @return
	 * User-Objekt
	 */
	public User getUser(int userId) {
		Session sess = null;
		Transaction trx = null;

		User user = new User();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			System.out.println("\nUser zu Id:" + userId);

			String hql = new String(
					"select user from User as user where user.id = :userId");
			Query query = sess.createQuery(hql);
			query.setInteger("userId", userId);
			Iterator itr = query.iterate();
			while (itr.hasNext()) {
				user = (User) itr.next();
			}
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		return user;
	}

	/*
	 * ! @brief Die Methode gibt ein Streamobjekt nach ID zurück. Wird von
	 * addComment() verwendet @param streamId ID des Streams @return
	 * Stream-Objekt
	 */
	public Stream getStream(int streamId) {
		Session sess = null;
		Transaction trx = null;
		Stream stream = new Stream();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			System.out.println("\nStream zu Id:" + streamId);

			String hql = new String(
					"select stream from Stream as stream where stream.id = :streamId");
			Query query = sess.createQuery(hql);
			query.setInteger("streamId", streamId);
			Iterator itr = query.iterate();
			while (itr.hasNext()) {
				stream = (Stream) itr.next();
			}
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		return stream;
	}

	/*
	 * ! @brief Diese Methode gibt die verfügbaren Kommentare aus.
	 * 
	 * Wahlweise nach User oder Stream @param id Angabe des Streams oder des
	 * Users @param searchUser Angabe ob nach Kommentaren eines Users(true) oder
	 * nach Kommentaren zu einem Stream gesucht wird @return Gibt das Ergebniss
	 * als XML-String zurück
	 */
	public String showComments(int id, boolean searchUser) {
		Session sess = null;
		Transaction trx = null;
		StringBuffer output = new StringBuffer();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			String hql = new String();
			if (!searchUser) {
				hql = "select comment from Comments as comment where comment.stream = :id order by date asc";
			} else {
				hql = "select comment from Comments as comment where comment.user = :id";
			}

			Query query = sess.createQuery(hql);
			query.setInteger("id", id);
			Iterator itr = query.iterate();

			System.out.println("\n\nComments als XML ausgeben!!!!!\n\n");

			output.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			output.append("<comments>");

			while (itr.hasNext()) {
				Comments comment = (Comments) itr.next();
				output.append("<comment>");
				output.append("\t<text>" + comment.getText() + "</text>");
				output.append("\t<user>" + comment.getUser().getUsername()
						+ "</user>");
				output.append("\t<date>" + comment.getDate().toString()
						+ "</date>");
				output.append("</comment>");
			}

			output.append("</comments>");
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		String string = new String(output);

		// HttpServletResponse response;
		// response.setContentType("text/html");

		return string;
	}

	// ! @brief Diese Methode gibt alle Kommentare als String zurück
	public String showComments() {
		Session sess = null;
		Transaction trx = null;

		StringBuffer output = new StringBuffer();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			System.out.println("\nAlle Kommentare:");
			List comments = sess.createQuery("from Comments").list();
			for (int i = 0; i < comments.size(); i++) {
				Comments comment = (Comments) comments.get(i);

				output.append("Commment: " + comment.getText());
				System.out.println("Commment: " + comment.getText()
						+ "\nvon User: " + comment.getUser().getUsername()
						+ "\nStreamname:" + comment.getStream().getName());

			}
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		String string = new String(output);
		return string;
	}

	// ! @brief Diese Methode gibt alle Streams als String zurück
	public String showStreams() {
		Session sess = null;
		Transaction trx = null;

		StringBuffer output = new StringBuffer();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			System.out.println("\nStreams:");
			List streams = sess.createQuery("from Stream").list();
			for (int i = 0; i < streams.size(); i++) {
				Stream stream = (Stream) streams.get(i);

				output.append("Streams: " + stream.getName());
				System.out.println("GenreId:  " + stream.getgenreId()
						+ "\nName: " + stream.getName() + "\nHomepage: "
						+ stream.getHomepage() + "\nUrl: " + stream.getUrl()
						+ "\nImage: " + stream.getImage());
			}

			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		String string = new String(output);
		return string;
	}

	// ! @brief Diese Methode gibt alle User als String zurück
	public String showUsers() {
		Session sess = null;
		Transaction trx = null;

		StringBuffer output = new StringBuffer();

		try {
			sess = sessionFactory.openSession();
			trx = sess.beginTransaction();
			List users = sess.createQuery("from User").list();
			for (int i = 0; i < users.size(); i++) {
				User user = (User) users.get(i);

				output.append("User : " + user.getName());
				System.out.println("Name:  " + user.getName() + " Username: "
						+ user.getUsername() + " Alter: " + user.getBirthday());

			}
			trx.commit();
		} catch (HibernateException ex) {
			if (trx != null)
				try {
					trx.rollback();
				} catch (HibernateException exRb) {
				}
			throw new RuntimeException(ex.getMessage());
		} finally {
			try {
				if (sess != null)
					sess.close();
			} catch (Exception exCl) {
			}
		}
		String string = new String(output);
		return string;
	}
	
	
}
&#91;/sourcecode&#93;
Okay ersmal eine Menge Source. Wie versprochen sind alle Methoden die wir brauchen aufgeführt. Es sind aber noch zwei weitere hinzugekommen. Nämlich <em>getUser()</em> und <em>getStream()</em>, die für die Funktion <em>addComment()</em>, die dann den jeweiligen User und Stream zurückgeben. Wie ihr seht geben unsere Funktionen bis auf <em>showStreams()</em> immer XML-Strings zurück. Das hat den Grund das wir in <em>radioSiTY</em> diese Strings in ein DataGrid schreiben werden ;-)

Im Source findet ihr übrigens noch die Funktion <em>updateUser()</em>. Dort seht ihr wie man mit Hibernate eine Tabelle updatet.

Erwähnenswert wäre noch die main(). Dort seht ihr die auskommentierten Funktionsaufrufe. Das waren unsere Testmethoden bevor wir überhaupt an das Frontend gegangen sind. Man kann nämlich diese Anwendung auch als Konsolenanwendung starten. Dazu müsst ich nur wieder in die einzelnen Funktionen die Ausgabe über <em>System.out.println()</em> gestalten ansatt einen String zurückzugeben (siehe <em>showStreams()</em>). Ich errinner mich gerade das ich ja nochmal auf den SQL-Output aus der Hibernate-Konfigurationsdatei zu sprechen kommen wollte. Dadurch das wir den Wert auf <em>true</em> gesetzt hatten, sehen wir jetzt die SQL Ausgabe in der Konsole. Das betrifft dann auch die Ausgaben auf der Konsole von BlazeDS/FDS-Tomcat. Sehr nützlich ;-)

Im Übrigen seht ihr, dass alle Querys in <em>HQL </em>geschrieben sind. Das ist die <em>Hibernate Query Language</em> und muss benutzt werden. Es ist also kein normales <em>SQL</em> möglich. Es ist übrigens sehr wichtig das ihr alle <em>Hibernate *.jar's</em> zu dem <em>Java Build Path</em> hinzufügt, da sich ansonsten das Projekt nicht kompilieren lässt.

Jetzt legt ihr unter dem Ordner <em>\WEB-INF\classes\flex</em> das Verzeichniss radioSiTY an. Dort hinein kopiert ihr die kompilierten <em>DAO-Klassen</em> und den <em>HibernateHandler</em>.
<h4>Implementierung Frontend (mxml-Applikation)</h4>
So wir kommen langsam aber sicher zum Ende. Nun ist es nicht mehr viel. Wir schreiben eine ganz kleine Anwendung du die unsere Funktionen nutzt. Nur die <em>addStream()</em> nicht, da diese analog zu <em>addComment()</em> läuft. Aber das seht ihr dann eh selbst im Source.

<a href="http://www.florian-oeser.de/wordpress/wp-content/2008/06/hibernate_mxml.jpg"><img class="aligncenter size-medium wp-image-47" title="hibernate_mxml" src="http://www.florian-oeser.de/wordpress/wp-content/2008/06/hibernate_mxml-300x128.jpg" alt="hibernate_mxml" width="300" height="128" /></a>

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<!&#91;CDATA&#91;

private function checkComments():void {

remoteObject.showComments(78,false);
}

private function addComments() :void {

remoteObject.addComment(userId.text,streamId.text,text.text,blocked.text);
remoteObject.showComments(streamId.text,false);

}

&#93;&#93;>
</mx:Script>
<mx:RemoteObject id="remoteObject" destination="product"/>

<mx:Button x="115" y="58" label="Get Comments" id="getComments" click="checkComments()"/>
<mx:Button x="587" y="58" label="Get Streams" id="getStreams" click="remoteObject.showStreams()"/>
<mx:TextInput x="70" y="345" width="37" id="streamId"/>
<mx:TextInput x="169" y="345" width="31" id="userId"/>
<mx:TextInput x="282" y="345" width="246" id="text"/>
<mx:TextInput x="599" y="345" width="59" id="blocked"/>
<mx:Button x="684" y="345" label="add Comment" id="addComment" click="addComments()"/>
<mx:TextArea x="483" y="109" width="330" height="196" id="txt_getStreams" text="{remoteObject.showStreams.lastResult}" />
<mx:TextArea x="52" y="109" id="txt_getComments" text="{remoteObject.showComments.lastResult}" width="311" height="196"/>
<mx:Label x="10" y="347" text="streamId:"/>
<mx:Label x="115" y="347" text="userId:"/>
<mx:Label x="239" y="347" text="text:"/>
<mx:Label x="537" y="347" text="blocked?"/>

</mx:Application>

Ich denke der Source ist leicht verständlich. Wichtig ist das RemoteObject. Denn das gibt an aus welcher Klasse überhaupt die Methoden aufgerufen werden sollen. Diese Destination muss ähnlich wie bei bei der web.xml in einer Konfigurationsdatei bekannt gegeben werden. Aber dazu komm ich gleich. Weiterhin hätte man die XML-Ausgabe bei den Kommentaren auch in ein DataGrid lesen können.

Da das RemoteObject teil der LCDS ist muss man bei der Projekterstellung noch ein paar Sachen beachten. So müsst ihr unter Server technology die J2EE angeben. Der Haken bei Use remote object access service muss natürlich gesetzt sein. Im nächsten Fenster sind auch noch ein paar Einstellungen zu treffen. Hier muss ein Server ausgewählt werden, auf dem die Applikation kompiliert und ausgeführt werden soll. Wichtig dabei ist das der Server vor dem validieren gestartet ist. Ich zeige euch die Einstellungen für den BlazeDS und für den FDS-Tomcat:

hibernate_fds_conf

hibernate_blaze_conf

Es ist übrigens sehr wichtig auf dem Server zu kompilieren, da es ansonsten zu Fehlern kommt das die RemoteObject-Klasse nicht gefunden wird(in userem Fall der HibernateHandler). Das äussert sich dann in Can’t find Destination Fehlern.

Kommen wir zum letzten Schritt. Wir müssen die Destination noch angeben. Wenn man mit RemoteObject arbeitet findet diese Angabe in der remoting-config.xml unter \WEB-INF\flex statt. Analog zu dem Beispieleintrag fügen also folgendes ein:

<destination id="product">
<properties>
<source>flex.radioSiTY.HibernateHandler</source>
</properties>
</destination>

Jetzt nur noch in Flex das Projekt starten und staunen 😀

Conclusion

Dieses HowTo war recht umfangreich aber ich denke es hat sich gelohnt. Habt ihr alles umgesetzt habt ihr eine gute Basis um eigene Projekt mit Flex und Hibernate zu realisieren. Ich werde bei Gelegenheit die eine oder andere Sache vielleicht noch ein wenig ausführlicher beschreiben. Auch hier noch einmal der Link zum Tutorial von Thorsten Horn, da er wie gesagt die Sachen rund um persistente Datenhaltung, Hibernate und Debugging sehr viel genauer beschreibt.

Ich hoffe ich habe nix vergessen oder übersehen. Bei Fragen oder Anregungen könnt ihr mir wie immer mailen oder ein Kommentar hinterlassen. In dem Sinne viel Erfolg 😀

]]>
http://www.florian-oeser.de/2008/06/07/howto-blazedsfds-tomcat-flex-hibernate-mysql/feed/ 0
HowTo: BlazeDS/FDS-Tomcat Installation auf Linux http://www.florian-oeser.de/2008/06/05/howto-blazedsfds-tomcat-installation-auf-linux/ http://www.florian-oeser.de/2008/06/05/howto-blazedsfds-tomcat-installation-auf-linux/#comments Thu, 05 Jun 2008 15:46:22 +0000 http://www.florian-oeser.de/?p=15 Einleitung

In diesem HowTo möchte ich euch zeigen wie man den BlazeDS/FDS-Tomcat auf einem Linuxsystem installiert. Für unser Projekt radioSiTY haben diese Server auf einem SuSE Linux 9.3 aufgesetzt, jedoch dürften fast alle Schritte auch auf einer anderen Distribution laufen.

Was brauchen wir:

Installation und Systemanpassungen

Ich habe mich bei dem JDK für das *.rpm.bin Package entschieden. Um das JDK zu installieren sind zwei Schritte notwendig. Zuerst mit ’sh‘ das Skript ausführen.

sh jdk1.6.0_06-rpm.bin

Den Lizenztext mit ‚yes‘ bestätigen.

Danach einfach mit dem Programm ‚rpm‘ das Packgage installieren.

rpm -i jdk1.6.0_06-linux.rpm

Das JDK wurde nun in /usr/java/j2sdk1.4.0/ installiert.

Beide Server brauchen noch die Angabe wo das JDK und die JRE liegt(Classpath). Diese Angaben macht man mit ‚export‘. Da standartmäßig diese Variablen nach einem Neustart wieder auf default zurückgesetzt werden, ist es ganz praktisch diese in der ‚profile.local‘ anzulegen. Diese Datei wird beim Anmelden am System aufgerufen. Weiterhin ist zu erwähnen das die beiden Server nicht parallel mit einem anderen Webserver laufen. Da bei vielen Servern der Apache vorinstalliert ist muss dieser natürlich auch vorher noch gestoppt werden. Dies erledigen wir ebenfalls gleich mit in der ‚profile.local‘. Die Datei könnte dann so aussehen:

### CHANGES TO RUN FDS-TOMCAT/BLAZEDS  ###
echo
echo Changes made to run fds-tomcat. view conf under /etc/profile.local

export JRE_HOME=/usr/java/jdk1.6.0_06/jre
echo JRE_HOME now @ $JRE_HOME
export JAVA_HOME=/usr/java/jdk1.6.0_06/
echo JAVA_HOME now @ $JAVA_HOME
echo
echo must shutdown the apache otherwise fds-tomcat/blazeds would not run!
rcapache2 stop
echo

Hier muss ich sagen das der Befehl ‚rcapache2 stop‘ und alle anderen rc’s nur unter SuSE funktionieren. Unter anderen Distributionen kann man dies aber wie folgt tun:

/etc/init.d/apache2 stop

Alternativ hat man natürlich eh die Möglichkeit Apache beim Systemstart gar nicht erst zu starten indem man die runlevel-confs bearbeitet. Dort könnte man auch einstellen das der BlazeDS/FDS-Tomcat beim Booten gestartet wird.

Installation und Konfiguration der Server

Nun den BlazeDS/FDS-Tomcat in ein beliebiges Verzeichnis mit ‚unzip‘ entpacken. Zum Beispiel so:

unzip fds-tomcat.zip /home/fds

Jetzt sind wir schon fast am Ziel. Was noch fehlt ist, das wir die Skripte die die Server starten/stoppen, mit ‚chmod‘ ausführbar machen. Bei BlazeDS muss ausserdem das Skript was die Datenbank startet ausführbar gemacht werden.

BlazeDS:

chmod + x blazeds/sampledb/startdb.sh

chmod + x blazeds/tomcat/bin/catalina.sh

chmod + x blazeds/tomcat/bin/shutdown.sh

FDS-Tomcat:

chmod + x fds-tomcat/bin/catalina.sh

chmod + x fds-tomcat/tomcat/bin/shutdown.sh

Jetzt kommt der große Moment 😀

Zum Starten des FDS-Tomcat’s ruft ihr folgenden Befehl auf:

sh catalina.sh run

Das Starten des BlazeDS erfolgt analog dazu, jedoch muss man erst die Datenbank starten:

sh startdb.sh

Ob alles geklappt seht ihr am Ende der Ausgabe wenn angezeigt wird, wie lange der Server zum booten gebraucht hat. Ausserdem könnt ihr jetzt im Browser die jeweilige Startseite betrachten. http://localhost:8600/ für den FDS-Tomcat und http://localhost:8400/ für den BlazeDS.

fds_start

Für die Arbeit mit den beiden Server haben wir noch auf das praktische ’screen‘ Tool zurückgegriffen. Das ermöglicht zum Einen das der Prozess in den Hindergrund geschoben wird. Somit kann man die Putty-Sitzung beenden und der Server bleibt gestarten. Zum Anderen kann man mit [STRG+A]+C einen neuen Screen erzeugen indem man dann andere Sachen machen kann. Mit STRG+A kann man dann zwischen den einzelnen Fenstern wechseln.

Conclusion

Da beim Starten der Output relativ schnell von statten geht und die Server zu meist auch starten wenn es zu einer Exception kam, ist es ganz sinnvoll bei Problemen sich die Logfiles anzuschauen. Diese findet man im Ordner /logs. Entscheident ist dort die catalina.*.log und die localhost.*.log Datei.

Falls ihr beim Starten einen Fehler bekommt das die VM nicht genug Speicher allozieren konnte, habt ihr ein mehr oder minder großes Problem :-/. Dieses Problem hatten wir ebenfalls, da wir lediglich einen vServer mit 512MB FlexRam hatten. Das Problem mit dem Starten ansich lässt sich leicht umgehen. Dazu müsste ihr einfach in der ‚catalina.sh‘ relativ am Anfang den Wert von 512MB auf zum Beispiel 128MB setzten. Das eigentliche Problem ist aber damit nicht gelöst, da zum einem der Server langsamer läuft und auch keiner Dauerbelastung stand hält. Wir können da ein Lied von singen 😀

Das heißt als einzige Alternative würde nur ein vServer/RootServer mit mindestens 1GB Ram in Frage kommen. Zumindest haben wir keine andere Lösung gefunden.

Bei Fragen, Kritik und Anregungen könnt ihr mir gerne eine Mail oder ein Kommentar schreiben. Auch an distributionsspezifischen Änderungen wäre ich interessiert 😉

]]>
http://www.florian-oeser.de/2008/06/05/howto-blazedsfds-tomcat-installation-auf-linux/feed/ 2