<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Zustandsforschung &#187; programmierung</title>
	<atom:link href="http://www.zustandsforschung.de/index.php/kategorien/programmierung/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.zustandsforschung.de</link>
	<description>Mit offenen Augen durch die Welt</description>
	<lastBuildDate>Wed, 21 Jul 2010 09:37:54 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.6</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Versionsverwaltung mit Mercurial</title>
		<link>http://www.zustandsforschung.de/index.php/versionsverwaltung-mit-mercurial/</link>
		<comments>http://www.zustandsforschung.de/index.php/versionsverwaltung-mit-mercurial/#comments</comments>
		<pubDate>Wed, 14 Jul 2010 10:49:50 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[KnowHow]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[mercurial]]></category>
		<category><![CDATA[scm]]></category>
		<category><![CDATA[versionierung]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=1549</guid>
		<description><![CDATA[Vor ein paar Monaten bin ich über das verteilte Source-Control-Management-Tool Mercurial gestoßen und habe angefangen mich damit zu beschäftigen. Mittlerweile setze ich es im privaten wie im professionellen Bereich ein und weil ich es so toll und praktisch finde, möchte es euch hier ein bisschen näher bringen.

Unterschied zum zentralisierten Modell
Der wichtigste Unterschied zwischen Mercurial und [...]]]></description>
			<content:encoded><![CDATA[<p>Vor ein paar Monaten bin ich über das verteilte Source-Control-Management-Tool <a href="http://mercurial.selenic.com/">Mercurial</a> gestoßen und habe angefangen mich damit zu beschäftigen. Mittlerweile setze ich es im privaten wie im professionellen Bereich ein und weil ich es so toll und praktisch finde, möchte es euch hier ein bisschen näher bringen.</p>
<p><span id="more-1549"></span></p>
<h2>Unterschied zum zentralisierten Modell</h2>
<p>Der wichtigste Unterschied zwischen Mercurial und traditionellen Versionierungssystemen wie CVS oder SVN ist das verteilte Modell. Das bedeutet, es gibt nicht mehr nur ein zentrales Repository, sondern jeder Entwickler hat quasi eine Kopie des zentralen Repositories bei sich lokal liegen.</p>
<p>Der Entwickler checkt nun zunächst in sein lokales Repository ein und kann damit arbeiten wie z.B. mit einem SVN, über das er die alleinige Kontrolle hat. Zu einem Zeitpunkt, an dem der Entwickler mit seiner Arbeit so weit zufrieden ist, dass er sie den anderen an dem Projekt beteiligten Entwicklern zur Verfügung stellen möchte, schiebt er alles in das übergeordnete Repository.</p>
<p>Bei diesem Vorgang wird nicht nur der aktuelle Stand hochgeschoben, sondern auch alle Zwischenstände, die der Entwickler an seinem lokalen Repository eingecheckt hat. Außerdem kann sich das übergeordnete Repository selbst von einem weiteren Repository bedienen, so dass nicht nur zwei Ebenen sondern eine ganze Repository-Hierarchie möglich ist.</p>
<p>Für mich selbst war zunächst vor allem die Tatsache ungewohnt, dass man bei sich lokal eincheckt &#8211; das klingt ein bisschen so als wäre der Code in größerer Gefahr, weil er ja nur auf dem eigenen Rechner liegt. In der Praxis erweist sich das aber als großer Vorteil, denn man kann auf die Art viel freier ausprobieren und auch mal Zwischenstände einchecken, die nicht so hundertprozentig stabil sind und die man normalerweise seinen Kollegen nicht als Checkin zumuten würde.</p>
<h2>Mercurial</h2>
<p>Mercurial selbst ist Open-Source-Software und benötigt Python ab Version 2.4 zum Funktionieren. Mitte 2005 gab Mat Mackall bekann, daran zu arbeiten. Mittlerweile hat Mercurial die Versionsnumer 1.6 erreicht und ist recht verbreitet &#8211; so werden unter anderem die Sourcen von FireFox, OpenOffice und Python damit verwaltet. Außerdem kann man bei den bei <a href="http://code.google.com">Google Code</a> gehosteten Projekten außer SVN auch Mercurial einsetzen.</p>
<p>Es gibt ein gutes Eclipse-Plugin, das sich <a href="http://www.javaforge.com/project/HGE">MercurialEclipse</a> nennt und eine Shell-Extension für Windows ähnlich dem bekannten TortoiseSVN, die sich <a href="http://tortoisehg.bitbucket.org/">TortoiseHG</a> nennt. </p>
<p>Das folgende Mini-Tutorial zeigt allerdings den Standardfall: Mercurial über die Kommandozeile.</p>
<h2>Mini-Tutorial</h2>
<p>Der Befehl, um Mercurial aufzurufen ist hg (das chemische Zeichen für Quecksilber, englisch eben &#8220;mercurial&#8221; &#8211; soll wohl so &#8216;ne Art Wortspiel sein). Mercurial bring eine gute Dokumentation gleich mit. Eine Kurzversion wird angezeigt, wenn man nur <code>hg</code> aufruft &#8211; eine ausführlichere Variante wird bei <code>hg help</code> angezeigt.</p>
<p>Um ein neues Repository anzulegen, muss man nur in das Verzeichnis seiner Wahl wechseln (wo evtl. bereits nicht-versionierte Dateien liegen) und den Befehl <code>hg init</code> ausführen. Damit erstellt Mercurial ein neues Verzeichnis <code>.hg</code>, in das das komplette Repository abgelegt wird.</p>
<p>Nun kann man mit <code>hg add</code> und einem anschließenden <code>hg commit -m "Adding files."</code> seine Dateien in das Repository einchecken. Nun kann man den Source-Code weiterbearbeiten, irgendwann dann mit einem <code>hg status</code> nachschauen, was man so alles geändert hat und dann wieder mit <code>hg commit</code> einchecken.</p>
<p>Interessant wird es dann, wenn mehrere Repositories ins Spiel kommen. Möchte man jetzt eine Kopie des Repositories erstellen, wechselt man beispielsweise ins übergeordnete Verzeichnis und führt <code>hg clone <em>Original-Verzeichnis</em> <em>Clone-Verzeichnis</em></code> aus. Dann kann man im Clone-Verzeichnis weiterarbeiten und auch hier einchecken. </p>
<p>Das Original-Verzeichnis ist so lange davon nicht betroffen, wie man ein <code>hg push</code> ausführt, das dann die Änderungen ins Original-Verzeichnis hochschieben würde. Idealerweise macht man vorher erst ein <code>hg pull</code>, um das Clone-Repository auf den neuesten Stand zu bringen und dann ein <code>hg update</code>, um die Dateien im Clone-Verzeichnis zu aktualisieren. Falls es im übergeordneten Repository schon Änderungen gegeben hat, muss vor dem Push mit <code>hg merge</code> gemergt werden.</p>
<p>Weitere Detail zur Nutzung von Mercurial kann man dem sehr guten <a href="http://mercurial.selenic.com/guide/">Tutorial auf der offiziellen Mercurial-Seite</a> entnehmen oder dem Tutorial von Joel Spolsky auf <a href="http://hginit.com" >hginit.com</a>.</p>
<h2>Mercurial zusammen mit SVN</h2>
<p>Man kann mit Mercurial auch dann nutzbringend einsetzen, wenn für das aktuelle Projekt ein anderes SCM wie zum Beispiel Subversion im Einsatz ist. Das kann dann folgendermaßen funktionieren:</p>
<p>Zunächst checkt man den Code wie gewohnt aus dem Subversion-Repository aus. Dann führt man im obersten Verzeichnis <code>hg init</code> aus. Dann legt man dort ein zusätzliches File <code>.hgignore</code> mit folgendem Inhalt an:<br />
<code><br />
syntax: glob<br />
.svn<br />
</code><br />
Das verhindert, dass man die SVN-spezifischen Files mit eincheckt. Umgekehrt sollte man das Einchecken des <code>.hg</code>-Ordners ins SVN verhindern, indem man das <code>svn:ignore</code>-Property entsprechend setzt. Danach kann man mit <code>hg add</code> und <code>hg commit</code> das lokale Mercurial-Repository mit Inhalt füllen.</p>
<p>Nun arbeitet man immer zunächst gegen das Mercurial-Repository und checkt dort ein. Wenn man seinen Code der Allgemeinheit zur Verfügung stellen möchte, macht man schließlich ein <code>svn commit</code>. Man muss allerdings darauf achten, wenn man ein Update aus dem SVN macht, dieses Update auch in das lokale Mercurial-Repository einzuchecken.</p>
<h2>Ein Wort zu git</h2>
<p>Wenn es um verteilte Versionskontrollsysteme geht, fällt einem meistens vermutlich <a href="http://git-scm.com/">git</a> ein. Wahrscheinlich hat man schon davon gehört, wie toll git ist und was git alles kann. Es gibt sogar eine Internetseite, die erklärt <a href="http://whygitisbetterthanx.com/">warum git besser ist als alles andere</a>. Warum also Mercurial ausprobieren?</p>
<p>Für meine Begriffe liegt die Popularität von git &#8211; abgesehen davon, dass es sicher ein geniales SCM ist &#8211; hauptsächlich daran, dass die git-Benutzer lauter schreien als die Mercurial-Benutzer <img src='http://www.zustandsforschung.de/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' />  Ich bin immer davon abgeschreckt, wenn irgendetwas zu sehr gehypt und zu vollmundig angepriesen wird. Deshalb habe ich mich dazu entschieden, Mercurial auszuprobieren. Mercurial-Benutzer müssen nicht rumlaufen und Leute missionieren &#8211; sie benutzen einfach ihr SCM und sind glücklich damit dass es so toll funktioniert.</p>
<p>Nein. Scherz. Ich denke die Entscheidung sollte im ersten Schritt nicht zwischen Mercurial oder git getroffen werden, sondern <em>für</em> ein verteiltes Versionskontrollsystem. Denn das ist das zentrale Merkmal, das beide Systeme auszeichnet. Der Rest ist dann Geschmackssache. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/versionsverwaltung-mit-mercurial/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Die Vor- und Nachteile von ClearCase</title>
		<link>http://www.zustandsforschung.de/index.php/die-vor-und-nachteile-von-clearcase/</link>
		<comments>http://www.zustandsforschung.de/index.php/die-vor-und-nachteile-von-clearcase/#comments</comments>
		<pubDate>Tue, 22 Jun 2010 15:03:15 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[programmierung]]></category>
		<category><![CDATA[clearcase]]></category>
		<category><![CDATA[scm]]></category>
		<category><![CDATA[versionierung]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=1503</guid>
		<description><![CDATA[Was ich selbst von ClearCase halte, habe ich ja bereits deutlich gemacht &#8211; aber ich habe mal sehen wollen, was denn andere Entwickler so davon halten. Daher hatte ich vor ungefähr einem Jahr auf Stackoverflow die Frage nach den Vor- und Nachteilen von ClearCase gestellt.
Mittlerweile wurde der Artikel mehr als 3000 mal geklickt, hat 27 [...]]]></description>
			<content:encoded><![CDATA[<p>Was ich selbst von ClearCase halte, habe ich ja <a href="http://www.zustandsforschung.de/index.php/arbeitsverhinderungstool-clearcase/">bereits deutlich gemacht</a> &#8211; aber ich habe mal sehen wollen, was denn andere Entwickler so davon halten. Daher hatte ich vor ungefähr einem Jahr auf Stackoverflow die <a href="http://stackoverflow.com/questions/1074580/clearcase-advantages-disadvantages">Frage nach den Vor- und Nachteilen</a> von ClearCase gestellt.</p>
<p>Mittlerweile wurde der Artikel mehr als 3000 mal geklickt, hat 27 Antworten und eine intensive Diskussion produziert. Obwohl sich tatsächlich auch einige Leute gefunden haben, die ein paar Vorteile von ClearCase aufzeigen, ist doch die Mehrheit der Antworten damit beschäftigt Nachteile aufzuzeigen. Für mich kommt das natürlich nicht wirklich überraschend, aber es ist doch interessant zu sehen, dass es nicht nur daran liegt, dass ich <a href="http://www.zustandsforschung.de/index.php/arbeitsverhinderungstool-clearcase/#comment-7441">keine Ahnung von ClearCase</a> habe.</p>
<p>Was mich seit ich ClearCase kenne immer gewundert hat, ist dass es mindestens eine Person braucht, die sich um nichts anderes kümmert und andere Leute sehen das ebenfalls so:</p>
<blockquote><p>The very fact that it&#8217;s complicated enough to require a full-time nanny is also worrying.</p></blockquote>
<p>Auch immer noch ein Rätsel ist mir der Zweck der Dynamischen Views in ClearCase &#8211; seit wir wieder darauf umgestellt hatten, haben wir gemerkt, dass das Resultat davon, dass man sofort alle Änderungen sieht, ist dass man sofort alle Fehler hat, die jemand anders gemacht hat. Auch das war Thema der Diskussion:</p>
<blockquote><p>Dynamic views are terrible unless you are in a very small team. And if that&#8217;s the case, why do you even have ClearCase? I have seen countless people&#8217;s views getting hosed because someone checked in files that broke the views of everyone else. You should always update and merge any conflicts on your own view. That way, the changes only affect you. With a dynamic view, you cannot merge down before pushing back up; you just commit and hope.</p></blockquote>
<p>Dann habe ich noch einen nicht ganz ernst gemeinten Ratschlag bekommen, der zwar für uns leider nichts gebracht hat, weil ja ClearCase schon gesetzt war, den ich aber wegen seinem Unterhaltungswert hier wiedergeben möchte:</p>
<blockquote><p>If you need an argument to convince people not to use &#8216;clearcase&#8217;, you can say that all the good developers hate it. I know, I wouldn&#8217;t work at any place who used clearcase as their source control. Heck, I would gladly suffer the pain of VSS to avoid clearcase.</p></blockquote>
<p>Wer möchte kann sich einfach mal <a href="http://stackoverflow.com/questions/1074580/clearcase-advantages-disadvantages">die ganze Diskussion auf Stackoverflow</a> durchsehen &#8211; es sind echt ein paar interessante Kommentare dabei. </p>
<p>Als Schlusswort möchte ich noch diesen Kommentar zitieren, den ich so voll und ganz unterschreiben könnte:</p>
<blockquote><p>If you&#8217;re considering integrating ClearCase into your development environment, I can offer only one piece of advice: don&#8217;t.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/die-vor-und-nachteile-von-clearcase/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Wir sind zu 90% fertig</title>
		<link>http://www.zustandsforschung.de/index.php/wir-sind-zu-90-prozent-fertig/</link>
		<comments>http://www.zustandsforschung.de/index.php/wir-sind-zu-90-prozent-fertig/#comments</comments>
		<pubDate>Tue, 04 May 2010 07:30:45 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[Spiele]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[ps3]]></category>
		<category><![CDATA[videospiele]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=1367</guid>
		<description><![CDATA[Der für die Entwicklung des lange erwarteten PS3-Titels &#8220;Gran Turismo 5&#8243; verantwortliche Kazunori Yamauchi gab in einem Interview bekannt, man sei aktuell bei einem Fertigstellungsgrad von 90%. Das Spiel ist seit 5 Jahren in der Entwicklung und der Release-Termin wurde mittlerweile mehrfach verschoben.
Für einen Softwareentwickler klingt der Spruch &#8220;wir sind zu 90% fertig&#8221; allerdings gar [...]]]></description>
			<content:encoded><![CDATA[<p>Der für die Entwicklung des lange erwarteten PS3-Titels &#8220;Gran Turismo 5&#8243; verantwortliche Kazunori Yamauchi gab in einem Interview bekannt, man sei <a href="http://blogs.insideline.com/straightline/2010/04/kazunori-yamauchi-gran-turismo-5-about-90-finished-gt-academy-might-come-to-us.html">aktuell bei einem Fertigstellungsgrad von 90%</a>. Das Spiel ist seit 5 Jahren in der Entwicklung und der Release-Termin wurde mittlerweile mehrfach verschoben.</p>
<p>Für einen Softwareentwickler klingt der Spruch &#8220;<em>wir sind zu 90% fertig</em>&#8221; allerdings gar nicht gut. Denn in der Softwareentwicklung kennt man die sogenannte 90-90-Regel, die besagt, dass die ersten 90 Prozent der Entwicklung 90 Prozent der Zeit brauchen und die restlichen 10 Prozent die anderen 90 Prozent der Zeit. Die letzten 10 Prozent bis zur Fertigstellung einer Entwicklung dauern immer am längsten und sind auch immer die schwierigsten.</p>
<p>Wenn der Typ nun also sagt, &#8220;<em>es sind doch nur noch 10 Prozent</em>&#8220;, dann soll das eigentlich heißen &#8220;<em>wir sind bald fertig</em>&#8221; aber tatsächlich heißt es eher &#8220;<em>es gibt noch eine ganze Menge zu tun</em>&#8220;. Yamauchi liefert quasi selbst den Beweis dafür ab, denn in einem Interview Anfang dieses Jahres <a href="http://www.edge-online.com/news/gran-turismo-5-is-90-per-cent-complete">waren&#8217;s auch schon 90 Prozent</a> <img src='http://www.zustandsforschung.de/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
<p>Mehr Infos zum Thema <a href="http://en.wikipedia.org/wiki/Ninety-ninety_rule">ninety-ninety rule gibt&#8217;s bei der Wikipedia</a> oder im Artikel <a href="http://www.processimpact.com/articles/essays.shtml#ninety">Ninety Percent Done</a>, der auch ein paar Ansätze beschreibt, was man dagegen tun kann.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/wir-sind-zu-90-prozent-fertig/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Der Weg in den Java-Dschungel</title>
		<link>http://www.zustandsforschung.de/index.php/der-weg-in-den-java-dschungel/</link>
		<comments>http://www.zustandsforschung.de/index.php/der-weg-in-den-java-dschungel/#comments</comments>
		<pubDate>Wed, 17 Mar 2010 15:13:54 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[programmierung]]></category>
		<category><![CDATA[java]]></category>
		<category><![CDATA[tutorial]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=1220</guid>
		<description><![CDATA[Der Artikel &#8220;New to Java programming&#8221; aus der IBM-developerWorks-Reihe bietet außer einer kurzen Einführung was Java eigentlich ist, auch für den nicht mehr ganz so neuen Java-Programmierer eine schöne Übersicht über die wichtigsten Aspekte, Themen und Technologien der Java-Welt, wie z.B.

How does Java technology relate to Web application development?
How does Java technology relate to SOA/Web [...]]]></description>
			<content:encoded><![CDATA[<p>Der Artikel &#8220;<a href="http://www.ibm.com/developerworks/java/newto/index.html?ca=drs-">New to Java programming</a>&#8221; aus der IBM-developerWorks-Reihe bietet außer einer kurzen Einführung was Java eigentlich ist, auch für den nicht mehr ganz so neuen Java-Programmierer eine schöne Übersicht über die wichtigsten Aspekte, Themen und Technologien der Java-Welt, wie z.B.</p>
<ul>
<li><a href="http://www.ibm.com/developerworks/java/newto/index.html?ca=drs-#4">How does Java technology relate to Web application development?</a></li>
<li><a href="http://www.ibm.com/developerworks/java/newto/index.html?ca=drs-#5">How does Java technology relate to SOA/Web services?</a></li>
<li><a href="http://www.ibm.com/developerworks/java/newto/index.html?ca=drs-#6">How does Java technology relate to dynamic languages and functional programming?</a></li>
<li><a href="http://www.ibm.com/developerworks/java/newto/index.html?ca=drs-#7">How does Java technology relate to open source software development?</a></li>
</ul>
<p>Jeder Abschnitt enthält weiterführende Links zu developerWorks- oder auch anderen Tutorials, wo man seine Kenntnisse dann auffrischen oder vertiefen kann.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/der-weg-in-den-java-dschungel/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$</title>
		<link>http://www.zustandsforschung.de/index.php/a-z0-9-_-a-z0-9-a-z2/</link>
		<comments>http://www.zustandsforschung.de/index.php/a-z0-9-_-a-z0-9-a-z2/#comments</comments>
		<pubDate>Thu, 10 Dec 2009 10:35:28 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[KnowHow]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[regex]]></category>
		<category><![CDATA[validierung]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=1009</guid>
		<description><![CDATA[Das immer wieder gerne angeführte Standardbeispiel für die Anwendung von Regular Expressions ist die E-Mail-Validierung. Mittels eines mehr oder weniger komplizierten Ausdrucks soll dabei geprüft werden, ob die von einem Benutzer in ein Webformular eingegebene E-Mail-Adresse den Regeln für eine gültige Adresse genügt. Dabei reichen die Varianten von eher simplen wie z.B. 
^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$
über komplizierte wie [...]]]></description>
			<content:encoded><![CDATA[<p>Das immer wieder gerne angeführte Standardbeispiel für die Anwendung von Regular Expressions ist die E-Mail-Validierung. Mittels eines mehr oder weniger komplizierten Ausdrucks soll dabei geprüft werden, ob die von einem Benutzer in ein Webformular eingegebene E-Mail-Adresse den Regeln für eine gültige Adresse genügt. Dabei reichen die Varianten von eher simplen wie z.B. </p>
<p><code>^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$</code></p>
<p>über komplizierte wie </p>
<p><code>[a-z0-9!#$%&#038;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&#038;'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?</code></p>
<p>bis hin zu solchen, die man schon eher als <em>völlig irre</em> bezeichnen könnte:</p>
<p><code>(?:[a-z0-9!#$%&#038;'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&#038;'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])</code></p>
<p>die allesamt darauf abzielen eine mehr oder weniger genaue Abdeckung der in <a href="http://tools.ietf.org/html/rfc2822#section-3.4.1">RFC 2822 &#8211; Internet Message Format</a> definierten Vorgabe zu erfüllen.</p>
<p>Die Frage ist doch aber &#8211; warum tut man so etwas? Klar, man möchte dass der Benutzer eine gültige E-Mail-Adresse (im besten Fall seine eigene) in ein Formular einträgt. Aber dient eine Validierung der Gültigkeit mittels Regular Expressions wirklich diesem Zweck?</p>
<p>Mit fallen zwei Szenarien ein, wo ein Benutzer eine ungültige E-Mail-Adresse eingibt: Entweder er macht es unabsichtlich, wenn er sich vertippt oder so. Oder er macht es absichtlich, um irgendeine Zwangsregistrierung zu umgehen. In beiden Fällen greift die Validierung über Regular Expression nicht wirklich.</p>
<p>Erster Fall &#8211; Versehen: Wenn ich mich beim Eingeben der Mailadresse vertippe, dann dreht sich das doch meist um einzelne Buchstaben &#8211; dass ich das &#8220;@&#8221; vergesse kommt eher selten vor. D.h. ich kann mich leicht so vertippen, dass ich zwar eine formal gültige Adresse eingebe, die aber leider trotzdem falsch ist (z.B. Buchstabendreher im Vornamen).</p>
<p>Zweiter Fall &#8211; Absicht: Ich kann mir leicht eine lustige Mailadresse ausdenken, die bei der Validierung zwar durchgeht, aber völlig frei erfunden ist. Die Mailadresse hab.ich@erfunden.de erfüllt zwar die Validierung &#8211; ist aber deshalb noch lange keine gültige E-Mail-Adresse.</p>
<p>Eine mögliche Lösung könnte daher sein, die Mailadresse über Double Opt-in zu validieren, also dass man dem Benutzer eine Mail an diese Adresse schickt, die einen Link enthält, den der Benutzer klicken muss, um seine E-Mail-Adresse zu bestätigen. Aber auch diese Variante schützt nicht gegen mutwillige Falscheingabe &#8211; es gibt immer noch <a href="http://www.spamgourmet.com/">Spamgourmet</a>, <a href="http://www.mailinator.com/">Mailinator</a> und ähnliche Sites, die Wegwerf-E-Mail-Adressen bereitstellen.</p>
<p>Fazit: Mit Regular Expressions kann man zwar den Benutzer bei der Eingabe etwas unterstützen, aber der Weisheit letzter Schluss sind sie für die E-Mail-Validierung deshalb noch lange nicht. Und gerade deshalb braucht man sich auch nicht verkünsteln, um zu beweisen, wie toll man Regular Expressions (kopieren) kann.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/a-z0-9-_-a-z0-9-a-z2/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Programmieren mit der Google App Engine</title>
		<link>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/</link>
		<comments>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/#comments</comments>
		<pubDate>Wed, 19 Aug 2009 17:15:19 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[programmierung]]></category>
		<category><![CDATA[appengine]]></category>
		<category><![CDATA[google]]></category>
		<category><![CDATA[python]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=810</guid>
		<description><![CDATA[Was ist die Google App Engine?
Die Google App Engine ist die Cloud-Lösung von Google. Das Prinzip ist, dass man sich in der Implementierung seiner Applikation an die Vorgaben von Google hält und im Anschluss daran seine Applikation auf die Server von Google hochlädt und dort deren Ressourcen bis zu einem gewissen Grad kostenlos nutzen kann. [...]]]></description>
			<content:encoded><![CDATA[<h2>Was ist die Google App Engine?</h2>
<p>Die Google App Engine ist die Cloud-Lösung von Google. Das Prinzip ist, dass man sich in der Implementierung seiner Applikation an die Vorgaben von Google hält und im Anschluss daran seine Applikation auf die Server von Google hochlädt und dort deren Ressourcen bis zu einem gewissen Grad kostenlos nutzen kann. Darüberhinaus hat man die Möglichkeit gegen Bezahlung weitere Ressourcen zu benutzen. Die Vorgaben sind entweder Python oder Java als Programmiersprache zu verwenden und gewisse Einschränkungen bei der Implementierung zu beachten, wie zum Beispiel keine direkten Filesystemzugriffe zu machen.</p>
<p>Im Gegenzug bekommt man eine API, mit der man auf die von Google zu Verfügung gestellte Infrastruktur zugreifen kann. Daten werden beispielsweise nicht in einer Datenbank gespeichert, sondern im Bigtable-Datastore. Das fühlt sich im ersten Moment an wie eine Datenbank, hat allerdings z.B. die Einschränkung keine Joins zu unterstützen, wodurch man ab und zu dazu gezwungen ist, seine Daten zwecks besserer Performance zu denormalisieren (siehe Artikel <a href="http://highscalability.com/how-i-learned-stop-worrying-and-love-using-lot-disk-space-scale">How I Learned to Stop Worrying and Love Using a Lot of Disk Space to Scale</a>).</p>
<h2>Das Projekt</h2>
<p>Eins gleich vorweg: Ich zeige euch hier nicht, wie ihr mit der Google App Engine ein Blog implementiert. Ich glaube es gibt tausende Tutorials, die das tun. In diesem Artikel werde ich mit der Python-Version der Google App Engine eine kleine Applikation implementieren, die von Twitter die neuesten Tweets zieht, die mit dem Hashtag &#8220;#confession&#8221; getaggt sind. Wie die fertige Applikation einmal aussehen soll, könnt ihr euch hier ansehen: <a href="http://confessiontweets.appspot.com">http://confessiontweets.appspot.com</a>.</p>
<p>Ich werde die wichtigsten Features der Google App Engine hier jeweils kurz anreißen &#8211; weitere Details findet ihr in der sehr guten, aber leider nicht auf Deutsch verfügbaren Anleitung unter <a href="http://code.google.com/appengine/">http://code.google.com/appengine/</a>. Ich bitte meine manchmal vielleicht ein bisschen unbeholfene Art, Python zu implementieren zu entschuldigen &#8211; eigentlich komme ich aus der Java-Welt und habe die Google App Engine auch dazu benutzt, ein bisschen Python zu lernen.<br />
<span id="more-810"></span><br />
Der vollständige Source-Code kann hier heruntergeladen werden: <a href='http://www.zustandsforschung.de/wp-content/uploads/2009/07/ConfessionTweets.zip'>ConfessionTweets.zip</a></p>
<h2>Die Applikation</h2>
<p>Die Applikation selbst basiert auf dem mit der App Engine mitgelieferten <a href="http://code.google.com/intl/de-DE/appengine/docs/python/tools/webapp/">webapp Framework</a>. Der Einstiegspunkt in die Applikation ist der folgende Code in der Datei &#8220;<em>controller.py</em>&#8220;:</p>
<pre>
application = webapp.WSGIApplication([('/', ConfessionListPage),
                                      ('/c/.*', SingleConfessionPage),
                                      ('/ajax', AjaxConfessionService)])

def main():
  run_wsgi_app(application)

if __name__ == "__main__":
  main()
</pre>
<p>Hier wird im Wesentlichen definiert, dass Zugriffe auf &#8220;<em>/</em>&#8221; auf die Klasse &#8220;<em>ConfessionListPage</em>&#8221; gemappt werden sollen, Zugriffe auf &#8220;<em>/c/.*</em>&#8221; &#8211; also auf alles was mit &#8220;<em>/c/</em>&#8221; anfängt auf die Klasse &#8220;<em>SingleConfessionPage</em>&#8221; und Zugriffe auf &#8220;<em>/ajax</em>&#8221; auf die Klasse &#8220;<em>AjaxConfessionService</em>&#8220;.<br />
Für die beiden Klassen, die einzelne Seiten darstellen, habe ich eine gemeinsame Superklasse &#8220;<em>Page</em>&#8221; geschrieben, die bereits einige Variablen bereitstellt, die von beiden Klassen benötigt werden. Diese Klasse erbt selbst von &#8220;<em>webapp.RequestHandler</em>&#8220;, und hat damit deren Funktion als Einsprungpunkt in die Applikation.</p>
<pre>
class Page(webapp.RequestHandler):
  collection = None
  template_values = {}
  template_url = ''

  def get(self):

    header_path = os.path.join(os.path.dirname(__file__), '../templates/header.html')
    footer_path = os.path.join(os.path.dirname(__file__), '../templates/footer.html')

    live = 'localhost' not in self.request.host    

    self.template_values.update({ 'header_path' : header_path,
                                 'footer_path' : footer_path,
                                 'live' : live })

    self.page_title = 'ConfessionTweets'

    self.service()

    self.template_values.update({ 'page_title' : self.page_title })

    path = os.path.join(os.path.dirname(__file__), self.template_url)
    try:
        self.response.out.write(template.render(path, self.template_values))
    except TemplateDoesNotExist:
        logging.error('Template %s not found!' % self.template_url)
</pre>
<p>Die Methode &#8220;<em>get</em>&#8221; wird automatisch vom webapp Framework aufgerufen, wenn eine Seite (per GET-Request) angefragt wurde. Ebenso gibt es Framework-Methoden für POST- oder HEAD-Requests. Dann werden hier einige Werte initialisiert wie beispielsweise der Pfad zu Header und Footer oder der Seitentitel. Dann folgt ein Aufruf der Methode &#8220;<em>service</em>&#8220;, die wie wir gleich sehen werden von den erbenden Klassen implementiert wird. Abschließend wird die Methode &#8220;<em>template.render</em>&#8221; aufgerufen, die als Parameter den Pfad zum Template, das die Seite darstellt und ein Dictionary mit den auf dem Template verwendeten Werten bekommt. Innerhalb dieser Methode werden die Werte in das Template geschrieben und das Resultat in die Response ausgegeben.</p>
<h2>Die ConfessionListPage</h2>
<p>Von dieser Klasse wird nun die Klasse &#8220;<em>ConfessionListPage</em>&#8221; abgeleitet. Diese Klasse ist für die Listendarstellung der einzelnen Tweets verantwortlich. </p>
<pre>
class ConfessionListPage(Page):

    def service(self):
        self.template_url = '../templates/confession_list.html'

        twitter_request = TwitterRequest()
        tweets = twitter_request.get_latest_tweets()

        self.template_values.update({ 'tweets' : tweets })
</pre>
<p>Da alles andere schon in der &#8220;<em>Page</em>&#8220;-Klasse passiert, muss hier nur noch die &#8220;<em>service</em>&#8220;-Methode implementiert werden, die von der Page-Klasse aufgerufen wird. Als erstes wird der Pfad zum Template, also der View für diese Seite definiert. Dann wird mit Hilfe der Klasse &#8220;<em>TwitterRequest</em>&#8220;, die wir gleich implementieren werden, eine Liste von &#8220;<em>Tweets</em>&#8221; erzeugt, die dann mit der Anweisung &#8220;<em>self.template_values.update</em>&#8221; an das Template übergeben werden.</p>
<p>Im Template selbst findet sich der folgende Code:</p>
<pre>
{% include header_path %}
{% for tweet in tweets %}
	&lt;script type="text/javascript" &gt;
		confessions["{{ tweet.key.name }}"] = "{{ tweet.key.name }}";
	&lt;/script&gt;
	{% if forloop.first %}
		&lt;div id="{{ tweet.key.name }}" class="first_confession"&gt;
			&lt;h3&gt;&lt;a href="/c/{{ tweet.key.name }}"&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
			&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href="{{ tweet.author_url }}"&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
		&lt;/div&gt;
	{% else %}
		&lt;div id="{{ tweet.key.name }}" class="confession"&gt;
			&lt;h3&gt;&lt;a href="/c/{{ tweet.key.name }}"&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
			&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href="{{ tweet.author_url }}"&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
		&lt;/div&gt;
	{% endif %}
{% endfor %}
	&lt;div id="new_confessions" &gt;&lt;/div&gt;
	&amp;darr; &lt;a id="more_button" href="javascript:void(0)" onclick="getConfessions(confessions)" &gt;more&lt;/a&gt; &amp;darr;
{% include footer_path %}
</pre>
<p>Die Templates für die Goolge App Engine werden mit Hilfe des Django-Frameworks erzeugt. Eine Liste von verwendbaren Template-Tags kann man daher der Django-Dokumentation selbst entnehmen: <a href="http://docs.djangoproject.com/en/dev/ref/templates/builtins/">Built-in template tags and filters</a>. Die erste und letzte Zeile binden den Header und Footer entsprechend den in der Klasse &#8220;<em>Page</em>&#8221; definierten Pfaden ein. Dann wird über die Liste der Tweets mittels &#8220;<em>for tweet in tweets</em>&#8221; iteriert. Innerhalb dieser Schleife stehen die einzelnen Listenelemente dann in der Variable &#8220;<em>tweet</em>&#8221; zur Verfügung. Da wir das erste Element dieser Liste speziell formatieren wollen, machen wir von der innerhalb von &#8220;<em>for</em>&#8220;-Schleifen vorhandenen Variable &#8220;<em>forloop.first</em>&#8221; gebrauch: Wenn wir das erste Element haben, dann schreiben wir einen anderen Div um die Ausgabe drumherum als für alle anderen Schleifenelemente. Zur Optimierung könnte man sicherlich nur das &#8220;<em>class</em>&#8220;-Attribut in den if-else-Block setzen, ich habe das hier aber nicht gemacht, um zu verdeutlichen, was mit diesem Template-Tag so alles möglich ist.</p>
<h2>Die SingleConfessionPage</h2>
<p>Da ich auch in der Lage sein möchte, jeden Tweet unter seiner eigenen URL darstellen zu können, habe ich eine Tweet-Klasse implementiert, die alle Daten aus dem Feed im Datastore speichert:</p>
<pre>
class Tweet(db.Model):
    published = db.DateTimeProperty(required=True)
    status_url = db.StringProperty(required=True)
    title = db.StringProperty(required=True)
    content = db.StringProperty(required=True)
    profile_image = db.StringProperty()
    author = db.StringProperty(required=True)
    author_url = db.StringProperty(required=True)
</pre>
<p>Diese Klasse erbt von der vom App-Engine-Framework zur Verfügung gestellten Basisklasse db.Model und hat damit schon eine Menge Funktionen, wie z.B. die get_or_insert-Methode, die innerhalb der __get_tweet_from_xml__-Methode der TwitterRequest-Klasse aufgerufen wird, und die alle übergebenen Daten auch gleich im Datastore speichert, falls es den gleichen Tweet nicht schon gibt:</p>
<pre>
        tweet = Tweet.get_or_insert(key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url)
</pre>
<p>Man muss diese statische Methode nicht benutzen, um ein Objekt im Datastore zu erzeugen, man kann auch einfach den Konstruktor der Klasse verwenden und dann mit der &#8220;<em>put</em>&#8220;-Methode die Daten persistieren:</p>
<pre>
	tweet = Tweet(key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url)
	tweet.put()
</pre>
<p>Ich identifiziere dabei die einzelnen Tweets anhand der base-64-codierten Status-URL, die mir von Twitter geliefert wird und die pro Tweet eindeutig ist. Diesen Wert verwende ich nicht nur hier als key_name, sondern auch in der URL für die einzelnen Tweets, die dann etwa  &#8220;<em>/c/eG94T3BhbEFtYW5kYS9zdGF0dXNlcy8yMjgzMTI0MDg5</em>&#8221; lautet. Eine solche URL wird dann vom Controller auf die get-Methode der Klasse SingleConfessionPage gemappt: </p>
<pre>
class SingleConfessionPage(Page):

    def service(self):
        self.template_url = '../templates/confession.html'

        match = re.match("/c/(.*)", self.request.path)
        if match is not None:
            key_name = urllib.unquote(match.group(1))

        tweet = Tweet.get_by_key_name(key_name)

        if tweet is None:
            self.redirect('/')
            return

        self.page_title += ' - A confession by ' + tweet.author + ' made ' + timesince.timesince(tweet.published) + ' ago ' 

        self.template_values.update({ 'tweet' : tweet })
</pre>
<p>Dort lese ich den Key wieder aus der URL aus, und hole dann den entsprechenden Tweet per &#8220;<em>Tweet.get_by_key_name(key_name)</em>&#8221; aus dem Datastore und übergebe ihn ans Template, das dann eine vereinfachte Variante von dem für die ConfessionListPage ist:</p>
<pre>
	{% include header_path %}
			&lt;div id="{{ tweet.key.name }}" class="first_confession"&gt;
				&lt;h3&gt;&lt;a href="{{ tweet.status_url }}"&gt;{{ tweet.title }}&lt;/a&gt;&lt;/h3&gt;
				&lt;p&gt;Confessed {{ tweet.published|timesince }} ago by &lt;a href="{{ tweet.author_url }}"&gt;{{ tweet.author }}&lt;/a&gt;&lt;/p&gt;
			&lt;/div&gt;
	{% include footer_path %}	
</pre>
<h2>Der TwitterRequest-Service</h2>
<p>Diese Klasse führt in der Methode &#8220;<em>get_latest_tweets</em>&#8221; den eigentlichen Request an Twitter aus, mit dem die Applikation ihre Daten erhält. Dabei wird in der Methode &#8220;<em>__get_xml__</em>&#8221; mittels der urlfetch-Methode des Frameworks von der Twitter-Suche ein XML geholt und in eine DOM-Datenstruktur überführt:</p>
<pre>
class TwitterRequest:

    def __init__(self):
        self.request_url = 'http://search.twitter.com/search.atom?q=%23confession&#038;rpp=5'

    def get_latest_tweets(self, page='1'):
        tweets = []

        self.request_url += '&#038;page=' + page;
        logging.debug('Requesting %s' % self.request_url)

        twitter_response = self.__get_xml__(self.request_url)                

        if twitter_response is None:
            logging.warning('Trying again...')
            twitter_response = self.__get_xml__(self.request_url)

        if twitter_response is None:
            return None 

        for entry in twitter_response.getElementsByTagName('entry'):
            tweets.append(self.__get_tweet_from_xml__(entry))

        return tweets

    def __get_xml__(self, request_url):
        try:
            result = urlfetch.fetch(request_url)
        except DownloadError:
            logging.error('Could not contact webservice.')
            return None
        if result.status_code == 200:
            return parseString(result.content)

    def __get_tweet_from_xml__(self, entry):
        published_string = entry.getElementsByTagName('published')[0].firstChild.data
        published = datetime.datetime.strptime(published_string, "%Y-%m-%dT%H:%M:%SZ")        

        status_url = entry.getElementsByTagName('link')[0].getAttribute('href')
        title = entry.getElementsByTagName('title')[0].firstChild.data
        content = entry.getElementsByTagName('content')[0].firstChild.data
        author = entry.getElementsByTagName('author')[0].getElementsByTagName('name')[0].firstChild.data
        author_url = entry.getElementsByTagName('author')[0].getElementsByTagName('uri')[0].firstChild.data
        profile_image = entry.getElementsByTagName('link')[1].getAttribute('href')

        key_name = base64.standard_b64encode(status_url[len('http://twitter.com/'):len(status_url)])
        tweet = Tweet.get_or_insert(key_name=key_name, published=published, status_url=status_url, title=title, content=content, profile_image=profile_image, author=author, author_url=author_url)
        return tweet
</pre>
<p>Dann rufe ich für jeden &#8220;<em>entry</em>&#8220;-Knoten in diesem XML die &#8220;<em>__get_tweet_from_xml__</em>&#8220;-Methode auf, die die relevanten Daten aus dem Knoten ausliest und letztendlich mit der &#8220;<em>get_or_insert</em>&#8220;-Methode von db.Model die Daten im Datastore abspeichert. Die &#8220;<em>get_latest_tweets</em>&#8220;-Methode gibt schließlich eine Liste mit den einzelnen Tweet-Objekten, die aus dem XML des Webservice abgerufen wurden zurück.</p>
<h2>Der AjaxConfessionService</h2>
<p>Ich möchte auf der Startseite auch die Möglichkeit bieten, durch die Liste der Tweets blättern zu können. Zu diesem Zweck benutze ich eine AJAX-Schnittstelle, um beim Klick auf den more-Link weitere Tweets von Twitter abzuholen und einzublenden. Das JavaScript schickt den Request an den Pfad &#8220;<em>/ajax</em>&#8220;, der auf die Klasse &#8220;<em>AjaxConfessionService</em>&#8221; gemappt wird. Auch diese Klasse erbt wieder von &#8220;<em>webapp.RequestHandler</em>&#8221; und implementiert die &#8220;<em>get</em>&#8220;-Methode:</p>
<pre>
class AjaxConfessionService(webapp.RequestHandler):

    def get(self):
        page = self.request.get('page', '');

        twitter_request = TwitterRequest()
        tweets = twitter_request.get_latest_tweets(page)
        self.response.out.write('{ "confessions": {')
        for tweet in tweets:
            self.response.out.write('"' + tweet.key().name() + '" : { ')
            self.response.out.write('"title" : "' + tweet.title.replace('"', '\\"') + '", ')
            self.response.out.write('"author" : "' + tweet.author.replace('"', '\\"') + '", ')
            self.response.out.write('"author_url" : "' + tweet.author_url.replace('"', '\\"') + '", ')
            self.response.out.write('"published" : "' + timesince.timesince(tweet.published) + '"')
            self.response.out.write('}')
            if tweet is not tweets[-1]:
                self.response.out.write(', ')
        self.response.out.write('}}')
</pre>
<p>Hier werden auch wieder die aktuellsten Tweets der entsprechenden Ergebnisseite bei Twitter abgeholt. Hier habe ich kein Template verwendet, sondern wandle die resultierenden Tweet-Objekte direkt in einen JSON-String um, den ich in die Response schreibe. Auf der Clientseite liest jetzt mein JavaScript wiederum das JSON aus und konstruiert HTML, das in die Seite eingefügt wird.</p>
<h2>Konfiguration</h2>
<p>Damit die Applikation überhaupt in der Google-App-Engine lauffähig ist, braucht sie noch eine Konfiguration. Diese wird in der Markup-Language YAML geschrieben und landet in einer Datei &#8220;<em>app.yaml</em>&#8221; im Wurzelverzeichnis der Applikation: </p>
<pre>
application: confessiontweets
version: 1
runtime: python
api_version: 1

handlers:
- url: /robots.txt
  static_files: robots.txt
  upload: robots.txt

- url: /css
  static_dir: css
  expiration: 1h

- url: /images
  static_dir: images
  expiration: 1d

- url: /js
  static_dir: js
  expiration: 60s

- url: /.*
  script: confession/controller.py
</pre>
<p>Zuoberst muss man das Label seiner Applikation deklarieren, so wie man es später auch in der Verwaltung der App-Engine angibt. Dann kommt die Version der Applikation und die Runtime &#8211; in unserem Fall Python. Die API-Version ist aktuell noch immer 1.</p>
<p>Unterhalb dieser Sektion definiert man nun einzelne Handler für verschiedene Files. Man kann keine der hochgeladenen Files direkt aufrufen, daher definiere ich hier zunächst die Position der robots.txt und der Verzeichnisse für CSS, Bilder und JavaScript, jeweils mit einer unterschiedlichen Cache-Expiry-Zeit.</p>
<p>Als letztes definiere ich noch, dass alles, was bisher nicht gemappt werden konnte an das Skript &#8220;<em>/confession/controller.py</em>&#8221; delegiert werden soll.</p>
<h2>Und jetzt ins Internet damit&#8230;</h2>
<p>Um das ganze wirklich ins Netz zu bringen, muss man sich mit einem Google-Account auf der Seite anmelden und bekommt dann einen Freischaltcode per SMS zugeschickt. Damit kann man dann bis zu 10 Google-App-Engine-Applikationen hosten lassen. Im Web-Interface muss ich jetzt zunächst eine Applikation mit dem gleichen Label wie in der Konfiguration unter &#8220;application&#8221; angegeben anlegen:</p>
<p><img src="http://www.zustandsforschung.de/wp-content/uploads/2009/08/create_application_app_engine.jpg" alt="Applikation in der Google App Engine anlegen" title="Applikation in der Google App Engine anlegen" width="500" height="287" class="alignnone size-full wp-image-959" /></p>
<p>Da diese Applikations-Ids über die gesamte Google-App-Engine eindeutig sind, muss man unter Umständen ein bisschen rumprobieren, bis man einen Namen gefunden hat, der noch verfügbar ist.</p>
<p>Der Upload der Applikation erfolgt dann über ein mitgeliefertes Python-Skript:</p>
<pre>
	python appcfg.py update &lt;pfad-zur-applikation&gt;
</pre>
<p>Man wird noch nach Mailadresse und Passwort gefragt und kann anschließend sein Werk im Internet bewundern.</p>
<p>Wenn man jetzt noch ein bisschen Styling und eine nette Hintergrundgrafik hinzufügt, erhält man bereits ein nettes kleines Mashup. Ich habe der ganzen Sache noch ein paar nette Animationseffekte spendiert und im Endergebnis sieht das ganze dann folgendermaßen aus:</p>
<p><a href="http://confessiontweets.appspot.com/"><img src="http://www.zustandsforschung.de/wp-content/uploads/2009/06/confession-tweets.jpg" alt="ConfessionTweets" title="ConfessionTweets" width="500" height="607" class="alignnone size-full wp-image-820" /></a></p>
<h2>Fazit</h2>
<p>Natürlich hat man bei Verwendung der Google-App-Engine einen gewissen Vendor-Lock-In-Effekt, vor allem durch die Verwendung des Datastore statt einer Datenbank. Auf der anderen Seite bietet die GAE eine interessante Möglichkeit, mit einer neuen Idee schnell und günstig &#8211; weil kostenlos &#8211; Live zu gehen und erst wenn die Applikation ein gewisses Maß an Usern erreicht hat muss man sich über Rentabilität überhaupt Gedanken machen.</p>
<p>Außer dem hier beschriebenen Mini-Mashup habe ich noch meine Video-Verwaltungsapplikation &#8220;<a href="http://www.veedemus.com">veedemus &#8211; movie collection</a>&#8221; mit der Google App Engine implementiert. Dort kann man schon eher sehen, dass nicht nur Spielereien, sondern auch komplexe Applikationen mit der GAE umsetzbar sind.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/programmieren-mit-der-google-app-engine/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Arbeitsverhinderungstool ClearCase</title>
		<link>http://www.zustandsforschung.de/index.php/arbeitsverhinderungstool-clearcase/</link>
		<comments>http://www.zustandsforschung.de/index.php/arbeitsverhinderungstool-clearcase/#comments</comments>
		<pubDate>Thu, 09 Jul 2009 09:48:22 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[Kurioses]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[clearcase]]></category>
		<category><![CDATA[scm]]></category>
		<category><![CDATA[versionierung]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=849</guid>
		<description><![CDATA[In meinem neuen Projekt ist die Maßgabe mit IBM Rational ClearCase zu arbeiten. Eigentlich ist ClearCase ein Tool zur Versionsverwaltung ähnlich wie CVS oder Subversion &#8211; allerdings kein gutes. ClearCase hält einen verdienten Platz 4 bei dreckstool.de für Entwicklungswerkzeuge. 
Abgesehen davon dass das Team ein Produkt verwenden soll, von dem es keine Ahnung hat, ist [...]]]></description>
			<content:encoded><![CDATA[<p>In meinem neuen Projekt ist die Maßgabe mit <a href="www.ibm.com/software/awdtools/clearcase">IBM Rational ClearCase</a> zu arbeiten. Eigentlich ist ClearCase ein Tool zur <a href="http://de.wikipedia.org/wiki/Versionsverwaltung">Versionsverwaltung</a> ähnlich wie CVS oder Subversion &#8211; allerdings kein gutes. ClearCase hält einen verdienten Platz 4 bei <a href="http://dreckstool.de">dreckstool.de</a> für Entwicklungswerkzeuge. </p>
<p>Abgesehen davon dass das Team ein Produkt verwenden soll, von dem es keine Ahnung hat, ist ClearCase selbst ziemlicher Schrott. </p>
<p>Ein kleines Beispiel: Um eine Datei zu löschen, reicht es nicht das File zu löschen und einzuchecken. Man muss zunächst zusätzlich zur Datei den übergeordneten Ordner auschecken, dann kann man die Datei erst löschen und dann am Ende alles zusammen wieder einchecken.</p>
<p>Ein weiteres interessantes Thema: Die dynamischen Views, für die ClearCase so berühmt ist. Schade nur, dass wir feststellen mussten, dass ein Build auf der dynamischen View bei uns so zwischen 15 und 30 Minuten dauert &#8211; hat man die Files lokal, dann ist das eine Sache von ungefähr einer Minute. Daher pfeifen wir jetzt auf dynamische Views und arbeiten mit Snapshot-Views.</p>
<p>Auch lustig: Es gibt keine Möglichkeit, Dateien von der Versionskontrolle auszuschließen. Also sowas wie die ignore-Funktionalität bei CVS oder SVN. Eine absolute Notwendigkeit, um vernünftig arbeiten zu können. Geht bei ClearCase einfach nicht. Resultat: Wir haben bereits nach 2 Wochen ein lustiges Durcheinander an Dateien im Repository, die dort nie hätten rein sollen.</p>
<p>Noch mehr über die interessanten Features von ClearCase kann man in den beiden Artikeln <a href="http://www.digitaltorque.ca/2006/12/18/clearcase-making-easy-things-hard/">ClearCase, making easy things hard</a> und <a href="http://www.aldana-online.de/2009/03/19/reasons-why-you-should-stay-away-from-clearcase/">Reasons NOT to use ClearCase</a> lesen.</p>
<p>Insgesamt bekommt man bei der Bedienung so ein bisschen den Eindruck, dass sich jemand wirklich viele Gedanken gemacht hat, wie er den Entwicklern die Arbeit mit dem Tool besonders schwer machen kann. Ich glaube ich habe in den letzten zwei Wochen die Hälfte der Zeit damit verbracht, mit ClearCase zu kämpfen und mit den Support zu telefonieren.</p>
<p>Ich bin schon sehr gespannt wieviel Zeit wir noch mit diesem Tool verlieren, das uns eigentlich die Arbeit hätte erleichtern sollen.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/arbeitsverhinderungstool-clearcase/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Lightbox-Clones</title>
		<link>http://www.zustandsforschung.de/index.php/lightbox-clones/</link>
		<comments>http://www.zustandsforschung.de/index.php/lightbox-clones/#comments</comments>
		<pubDate>Tue, 12 Aug 2008 12:56:04 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[programmierung]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[lightbox]]></category>
		<category><![CDATA[programming]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=626</guid>
		<description><![CDATA[Lightbox &#8211; das sind diese hübschen Layer, die man immer öfters mal auf den Internet-Seiten sieht &#8211; zu sehen zum Beispiel hier, wenn man auf eines der Thumbnails klickt.
Von Lightbox-ähnlichen JavaScript-Bibliotheken gibt&#8217;s Unmengen, so dass es wahrscheinlich für jeden Anwendungszweck eine Variante gibt, wenn man sie nur finden würde.
Eine tolle Übersicht bietet &#8220;The Lightbox Clones [...]]]></description>
			<content:encoded><![CDATA[<p>Lightbox &#8211; das sind diese hübschen Layer, die man immer öfters mal auf den Internet-Seiten sieht &#8211; zu sehen <a href="http://blackandwhite.zustandsforschung.de/">zum Beispiel hier</a>, wenn man auf eines der Thumbnails klickt.</p>
<p>Von Lightbox-ähnlichen JavaScript-Bibliotheken gibt&#8217;s Unmengen, so dass es wahrscheinlich für jeden Anwendungszweck eine Variante gibt, wenn man sie nur finden würde.</p>
<p>Eine tolle Übersicht bietet &#8220;<a href="http://planetozh.com/projects/lightbox-clones/">The Lightbox Clones Matrix</a>&#8220;, wo mittlerweile mehr als 40 Lightbox-Clones gelistet werden, die sich auch nach Kategorien wie &#8220;zugrundeliegendes JavaScript-Framework&#8221; oder deren Features (kann Bilder/IFrames/Flash anzeigen) filtern lassen.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/lightbox-clones/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Kann man schon was sehen?</title>
		<link>http://www.zustandsforschung.de/index.php/kann-man-schon-was-sehen/</link>
		<comments>http://www.zustandsforschung.de/index.php/kann-man-schon-was-sehen/#comments</comments>
		<pubDate>Tue, 05 Aug 2008 10:01:01 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[Ganz kurz]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[fun]]></category>
		<category><![CDATA[humor]]></category>
		<category><![CDATA[work]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=627</guid>
		<description><![CDATA[Bei der allseits beliebten und grundsätzlich immer viel zu früh gestellten Frage von Kunden, Projektleitern oder ähnlichem
&#8220;Kann man denn schon was sehen?&#8221; 
empfehle ich eine Antwort in Form von folgender URL zu geben:
http://www.kannmanschonwassehen.de
]]></description>
			<content:encoded><![CDATA[<p>Bei der allseits beliebten und grundsätzlich immer viel zu früh gestellten Frage von Kunden, Projektleitern oder ähnlichem</p>
<p>&#8220;Kann man denn schon was sehen?&#8221; </p>
<p>empfehle ich eine Antwort in Form von folgender URL zu geben:</p>
<p><a href="http://www.kannmanschonwassehen.de/">http://www.kannmanschonwassehen.de</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/kann-man-schon-was-sehen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Universal Widget API</title>
		<link>http://www.zustandsforschung.de/index.php/universal-widget-api/</link>
		<comments>http://www.zustandsforschung.de/index.php/universal-widget-api/#comments</comments>
		<pubDate>Mon, 21 Jul 2008 14:44:32 +0000</pubDate>
		<dc:creator>Benedikt</dc:creator>
				<category><![CDATA[KnowHow]]></category>
		<category><![CDATA[programmierung]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[netvibes]]></category>
		<category><![CDATA[uwa]]></category>
		<category><![CDATA[widget]]></category>

		<guid isPermaLink="false">http://www.zustandsforschung.de/?p=579</guid>
		<description><![CDATA[Im Rahmen eines Projektes auf der Arbeit hatte ich die Gelegenheit, mich mit der Universal Widget API zu beschäftigen. 
Da bisher jeder Hersteller von Widgets seine eigene Variante hatte, um so etwas zu bauen mussten Programmierer ihre Widgets für jede Plattform anpassen oder komplett umbauen. An dieser Stelle kommt die Universal Widget API von Netvibes [...]]]></description>
			<content:encoded><![CDATA[<p>Im Rahmen eines <a href="http://about.namics.com/2008/06/namicslab_frank.html">Projektes auf der Arbeit</a> hatte ich die Gelegenheit, mich mit der Universal Widget API zu beschäftigen. </p>
<p>Da bisher jeder Hersteller von Widgets seine eigene Variante hatte, um so etwas zu bauen mussten Programmierer ihre Widgets für jede Plattform anpassen oder komplett umbauen. An dieser Stelle kommt die <a href="http://dev.netvibes.com/">Universal Widget API von Netvibes</a> ins Spiel, die es sich zur Aufgabe gemacht hat, ein einheitliches Framework und eine API zu schaffen, mittel dessen Widgets auf vielen Verschiedenen Systemen lauffähig gemacht werden können. Aktuell werden unterstützt iGoogle, Windows Vista, Apple Dashboard, Live.com, iPhone, Opera, MySpace und die Integration in Blogs.</p>
<p>Als Beispiel habe ich ein kleines Widget gebastelt, was das jeweils aktuellste Bild aus dem RSS-Feed von <a href="http://ffffound.com/">FFFFOUND!</a> darstellt. </p>
<p><img src="http://www.zustandsforschung.de/wp-content/uploads/2008/07/universal_widget_api.jpg" alt="" title="FFFFOUND!-Widget bei iGoogle" width="469" height="392" class="alignnone size-medium wp-image-621" /></p>
<p>Im Prinzip besteht ein Widget für die UWA aus einer einzelnen XHTML-Datei, die sich aus 5 wesentlichen Teilen zusammensetzt:</p>
<p><strong>Der Header</strong><br />
Im Header werden Metainformationen wie der Titel des Widgets, der Autor, eine Beschreibung und ähnliche Dinge eingetragen.<br />
<code><br />
    &lt;meta name="author" content="Zustandsforschung" /&gt;<br />
    &lt;meta name="description" content="image bookmarking" /&gt;<br />
    &lt;meta name="apiVersion" content="1.0" /&gt;<br />
    &lt;meta name="autoRefresh" content="20" /&gt;<br />
    &lt;meta name="debugMode" content="true" /&gt;<br />
</code><code><br />
    &lt;link rel="stylesheet" type="text/css"<br />
      href="http://www.netvibes.com/themes/uwa/style.css" /&gt;<br />
    &lt;script type="text/javascript"<br />
      src="http://www.netvibes.com/js/UWA/load.js.php?env=Standalone"&gt;&lt;/script&gt;<br />
</code><code><br />
    &lt;title&gt;FFFFOUND!&lt;/title&gt;<br />
    &lt;link rel="icon" type="image/png"<br />
      href="http://ffffound.com/favicon.ico" /&gt;<br />
</code></p>
<p><strong>Die Konfiguration</strong><br />
Mittels eines speziellen XML-Formates wird dann die Konfiguration definiert, wobei man Checkboxen, Eingabefelder oder Dropdowns anlegen kann. Die vom Benutzer dort eingetragenen Werte können dann über eine API wieder ausgelesen und verwendet werden.</p>
<p>Bei meinem Beispiel hat&#8217;s keine Konfiguration &#8211; das geht auch und sieht dann so aus:<br />
<code><br />
   &lt;widget:preferences&gt;<br />
   &lt;/widget:preferences&gt;<br />
 </code></p>
<p><strong>Styles</strong><br />
Da es sich bei einem UWA-Widget letztendlich um HTML handelt, kann das Widget mit CSS gestylt werden:<br />
<code><br />
    &lt;style type="text/css"&gt;<br />
		.ffffound {<br />
			text-align:center;<br />
		}<br />
		.ffffound img {<br />
			max-width:250px;<br />
		}<br />
    &lt;/style&gt;<br />
</code></p>
<p><strong>JavaScript</strong><br />
Der Hauptteil des Widgets ist ein JavaScript: Hier muss man eine widget.onLoad-Funktion schreiben, die beim Start des Widgets aufgerufen wird. Ansonsten kann man hier so ziemlich alles machen, was man mit JavaScript so alles machen kann. Außerdem gibt&#8217;s noch eine API für den vereinfachten Zugriff auf AJAX-Funktionen und ein paar DOM-Erweiterungen.<br />
<code><br />
    &lt;script type="text/javascript"&gt;<br />
      var FFFFound = {};<br />
</code><code><br />
      FFFFound.feedUrl = "http://feeds.feedburner.com/ffffound/everyone";<br />
</code><code><br />
      FFFFound.display = function(feed) {<br />
        widget.log(feed);<br />
		widget.log(feed.items[0].content);<br />
		widget.setBody("&lt;div class=\"ffffound\"&gt;"<br />
                        +feed.items[0].content+"&lt;/div&gt;");<br />
      }<br />
</code><code><br />
      widget.onLoad = function() {<br />
        UWA.Data.getFeed(FFFFound.feedUrl, FFFFound.display);<br />
      }<br />
    &lt;/script&gt;<br />
</code></p>
<p><strong>Body</strong><br />
Im Body definiert man mit HTML die Grundstruktur des Widgets. Meistens wird man aber eher alles wichtige per JavaScript hinzugefügt und der Body sieht dann so aus:<br />
<code><br />
  &lt;body&gt;<br />
    &lt;p&gt;Loading... &lt;/p&gt;<br />
  &lt;/body&gt;<br />
</code></p>
<p>Das Endergebnis bzw. den kompletten Sourcecode für das Widget könnt ihr euch hier mal anschauen: <a href='http://www.zustandsforschung.de/wp-content/uploads/2008/07/ffffound.html'>ffffound-Widget</a>. Und es gibt natürlich auch hierfür ein tolles <a href="http://dev.netvibes.com/blog/2008/06/06/new-version-of-the-uwa-cheat-sheet/">Cheat-Sheet</a>, wo alles draufsteht, was man wissen muss, um ein UWA-Widget zu erstellen.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.zustandsforschung.de/index.php/universal-widget-api/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
