<?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>Christoph Bünte &nbsp;&raquo; Software Entwicklung Berlin</title>
	<atom:link href="http://www.christophbuente.de/kategorie/software-entwicklung/web-entwicklung/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.christophbuente.de</link>
	<description>Software Entwicklung</description>
	<lastBuildDate>Tue, 07 Dec 2010 11:30:07 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.5</generator>
		<item>
		<title>Opensocial Gadgets &#8211; Apps für StudiVZ selbst entwickeln</title>
		<link>http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/</link>
		<comments>http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/#comments</comments>
		<pubDate>Thu, 29 Oct 2009 19:39:51 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[backend]]></category>
		<category><![CDATA[beispiel]]></category>
		<category><![CDATA[example]]></category>
		<category><![CDATA[gadgets]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[meinvz]]></category>
		<category><![CDATA[opensocial]]></category>
		<category><![CDATA[ruby]]></category>
		<category><![CDATA[Ruby on Rails]]></category>
		<category><![CDATA[schuelervz]]></category>
		<category><![CDATA[studivz]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[widgets]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/?p=399</guid>
		<description><![CDATA[Wie StudiVZ bereits angekündigt hat, werden die ersten opensocial Anwendungen für ihre Plattform bald live gehen. Entwickler können sich schon jetzt registrieren und ihre so genannten Gadgets in einer Sandbox ausprobieren. Ich habe das Vergnügen für meinen aktuellen Kunden DaWanda ein Gadget für StudiVZ zu entwickeln. Ein Gadget besteht aus einer XML Datei, die definiert, [...]]]></description>
			<content:encoded><![CDATA[<p>Wie <a href="http://developer.studivz.net/2009/09/24/opensocial-is-now-available-in-our-vz-sandbox/">StudiVZ</a> bereits angekündigt hat, werden die ersten <a href="http://code.google.com/intl/de-DE/apis/opensocial/">opensocial</a> Anwendungen für ihre Plattform bald live gehen. Entwickler können sich schon jetzt registrieren und ihre so genannten Gadgets in einer Sandbox ausprobieren.</p>
<p>Ich habe das Vergnügen für meinen aktuellen Kunden <a href="http://www.dawanda.com">DaWanda</a> ein Gadget für StudiVZ zu entwickeln. Ein Gadget besteht aus einer XML Datei, die definiert, um welche Anwendung es sich handelt, wer der Author ist usw. Dazu gehören wahlweise mehrere CSS und Javascript Dateien. Nachdem man alles in eine Zip Datei, die den gleichen namen wie die XML Datei trägt, gepackt hat, kann man die Anwendung in die Sandbox hochladen und ausprobieren. Eine Beispiel Datei könnte so aussehen:</p>
<pre escaped="true" lang="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Module&gt;
  &lt;ModulePrefs
    title="Example StudiVZ Gadget"
    title_url="http://www.christophbuente.de"
    author="Christoph Bünte"
    author_email="info@christophbuente.de"
    description="Example description"
    screenshot="http://www.christophbuente.de/images/screenshot.jpg"
    thumbnail="http://www.christophbuente.de/images/thumbnail.jpg"&gt;
    &lt;Require feature="opensocial-0.8" /&gt;
    &lt;Require feature="views" /&gt;
  &lt;/ModulePrefs&gt;
  &lt;Content type="html" &gt;
    &lt;![CDATA[
      &lt;script&gt;
        function init() {
          // Put initialization code here
        }
        gadgets.util.registerOnLoadHandler(init);
      &lt;/script&gt;
    &lt;div id='container'&gt;&lt;/div&gt;
    ]]&gt;
  &lt;/Content&gt;
&lt;/Module&gt;</pre>
<p><span id="more-399"></span><br />
Jede Interaktion des Benutzers mit der Anwendung wird mit Hilfe weiterer Javascript Funktionen behandelt. Dazu stehen eine Reihe eingebauter Funktionen aus dem Opensocial Container bereit. Darüber hinaus haben die Entwickler von StudiVZ eine <a href="http://developer.studivz.net/wiki/index.php/Gadgets_API">API Beschreibung</a> der zusätzlichen Funktionen bereit gestellt. Was aber, wenn man nun nicht mit Javascript entwickeln möchte bzw. kann? Es besteht die Möglichkeit Flash Content einzubinden. Man kann aber auch ein beliebiges Backend einbinden. Dieses Backend muss nur 2 Bedingungen erfüllen:</p>
<ul>
<li>Es muss auf <acronym title="Hypertext Transfer Protocol">HTTP</acronym> Requests reagieren können und beliebiges <acronym title="Hypertext Markup Language">HTML</acronym>,<acronym title="Cascading Style Sheet">CSS</acronym> und JavaScript zurück liefern</li>
<li>Es muss im Namen des Applikationsbenutzers einen OAuth signierten Request an die StudiVZ <acronym title="Representational State Transfer">REST</acronym> <acronym title="Application Programming Interface">API</acronym> schicken können.</li>
</ul>
<p><a href="http://www.christophbuente.de/wp-content/uploads/Bild-7.png"><img class="size-full wp-image-419 alignleft" title="Request Opensocial Backend" src="http://www.christophbuente.de/wp-content/uploads/Bild-7.png" alt="Schema: Request Opensocial Backend" width="348" height="376" /></a><br />
Wie im Schema zu sehen, sendet der User einen Request, um die Applikation zu benutzen. Der Container liest darauf hin den Inhalt der XML Datei aus. Dort ist neben einem DIV Container lediglich ein externes Javascript referenziert. In dem Javascript sind einfache Methoden zum Navigieren und Interagieren definiert. Das rendern des HTML übernimmt ein Ruby Backend. Das braucht zum Render unter Umständen noch zusätzliche Informationen wie zum Beispiel Name und Foto des Benuter usw. Diese Informationen holt es sich mit einem OAuth signierten Request von der abgebildeten REST API. Das vom Ruby Backend erzeugte HTML rendert der Opensocial Container in einem IFrame im Browser des Benutzers. Für eine bessere Übersicht definieren wir im XML einzelne Views, die jeweils einen eigenen Endpunkt im Backend haben. Eine einfache gadget.xml mit 2 definierten Views für Profil und Vollansicht mit Backend Anbindung:</p>
<pre escaped="true" lang="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;Module&gt;
  &lt;ModulePrefs
    title="Spitzenmässiges StudiVZ Gadget mit Backendanbindung"
    title_url="http://www.christophbuente.de"
    author="Christoph Bünte"
    author_email="info@christophbuente.de"&gt;
    &lt;Require feature="opensocial-0.8" /&gt;
    &lt;Require feature="views" /&gt;
  &lt;/ModulePrefs&gt;
  &lt;Content type="html" view="profile" &gt;
    &lt;![CDATA[
      &lt;script type="text/javascript" src="http://www.yourbackend.com/profile.js" &gt;&lt;/script&gt;
      &lt;div id='profile'&gt;&lt;/div&gt;
    ]]&gt;
  &lt;/Content&gt;
  &lt;Content type="html" view="canvas" &gt;
    &lt;![CDATA[
      &lt;script type="text/javascript" src="http://www.yourbackend.com/canvas.js" &gt;&lt;/script&gt;
      &lt;div id='canvas'&gt;&lt;/div&gt;
    ]]&gt;
  &lt;/Content&gt;
&lt;/Module&gt;</pre>
<p>Das referenzierte Javascript ist ebenfalls sehr schlank. Darin wird lediglich eine Backend URL aufgerufen und deren Response Body in das profile bzw. canvas div geschrieben.</p>
<pre>function init{
  var url = 'http://www.yourbackend.com/profile/'
  var params = {};
  params[gadgets.io.RequestParameters.AUTHORIZATION]=gadgets.io.AuthorizationType.SIGNED;
  gadgets.io.makeRequest(url, requestCallback, params);

}

function requestCallback(obj) {
	document.getElementById('profile').innerHTML = obj.data;
}</pre>
<p>Damit die REST API benutzt werden kann, muss man sich an ihr Authentifizieren. Da das Backend aber dumm ist, hat es keine Userdaten, um sich dort anzumelden. Glücklicherweise ist der Request aus dem Javascript mit dem verschlüsselten Authentifierungsdaten des angemeldeten Anwendungsbenutzers signiert. Mit deren Hilfe können wir einen neuen, signierten Request erzeugen, den die API akzeptieren wird. Doch zuerst muss die Glültigkeit der Signatur überprüft werden:</p>
<p><code>sudo gem install oauth --source http://gemcutter.org</code></p>
<pre lang="ruby">
require 'oauth'
require 'oauth/consumer'
require 'oauth/request_proxy/action_controller_request'
require 'oauth/signature/rsa/sha1'
require 'oauth/signature/hmac/sha1'

class ApplicationController < ActionController::Base

  before_filter <img src='http://www.christophbuente.de/wp-includes/images/smilies/icon_surprised.gif' alt=':o' class='wp-smiley' /> auth_required

  def oauth_required
    cert_file = File.join(RAILS_ROOT, 'public', 'public.cer')
    cert_str = File.read(cert_file)
    consumer = OAuth::Consumer.new(ConsumerToken, cert_str)

    begin
      pass = OAuth::Signature.build(request, :consumer => consumer).verify
      logger.info "Signature verification returned: #{pass}"
    rescue OAuth::Signature::UnknownSignatureMethod => e
      logger.error "ERROR "+ e.message
    end

    render :text => "OAuth access denied", :status => :unauthorized  unless pass
  end
</pre>
<p>Die URL für das öffentliche Zertifikat wird mit jedem Request mit geschickt. Einmal runterladen sollte reichen. Ich denke, man muss es nicht bei jedem Request erneut vom StudiVZ Server laden. Den Consumer Token findet ihr in der OAuth Section eurer Gadget Sandbox (http://www.meinvz.net/GadgetDeveloper/GadgetOAuth/xxx). xxx ist deine Gadget ID.</p>
<p>Sollte der before_filter nicht abbrechen war die Signatur korrekt und der Request kann bearbeitet werden. Aus einem mir nicht bekannten Grund ist der Request des Containers an das Backend mit der RSA-SHA1 Methode signiert. Dafür wird das Zertifikat benötigt. Für den Request an die API muss die HMAC-SHA1 Methode verwendet werden. Dafür wird ein neuer Consumer benötigt:</p>
<pre lang="ruby">def get_owner
  consumer = OAuth::Consumer.new(ConsumerToken, ConsumerSecret,
      :signature_method => 'HMAC-SHA1',
      :site => "http://sandbox.gadgets.apivz.net",
      :scheme => :query_string,
      :http_method => :get)
  requestor_param = {:xoauth_requestor_id => params[:opensocial_owner_id]}.to_param
  access_token = OAuth::AccessToken.new(consumer)
  response = access_token.get("/social/rest/people/#{params[:opensocial_owner_id]}/@self?#{requestor_param}")
  owner_attributes = JSON.parse(response.body)['entry']
end</pre>
<p>Feedback, Fragen und Kritik sind immer gerne willkommen.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/" rel="bookmark" class="crp_title">Paypal subscriptions &#8211; Abonnements in Java integrieren</a></li><li><a href="http://www.christophbuente.de/2010-07-20-rails-hosting-bei-rocket-rentals-auch-mit-staging-umgebung/" rel="bookmark" class="crp_title">Rails Hosting bei Rocket Rentals &#8211; auch mit Staging Umgebung</a></li><li><a href="http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/" rel="bookmark" class="crp_title">captcha tutorial &#8211; Bots aussperren</a></li><li><a href="http://www.christophbuente.de/2007-12-06-voiceglue-installation-interactive-voice-response-leicht-gemacht/" rel="bookmark" class="crp_title">VoiceGlue Installation &#8211; Interactive Voice Response leicht gemacht</a></li><li><a href="http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">canoo webtest &#8211; Web-Anwendungen automatisch testen</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>widgets bei myspace &#8211; externe Programme benutzen</title>
		<link>http://www.christophbuente.de/2008-04-01-widgets-bei-myspace-externe-programme-bei/</link>
		<comments>http://www.christophbuente.de/2008-04-01-widgets-bei-myspace-externe-programme-bei/#comments</comments>
		<pubDate>Tue, 01 Apr 2008 20:25:43 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[blockbuster]]></category>
		<category><![CDATA[gadgets]]></category>
		<category><![CDATA[myspace]]></category>
		<category><![CDATA[myspace.com]]></category>
		<category><![CDATA[opensocial]]></category>
		<category><![CDATA[spass]]></category>
		<category><![CDATA[spass macht]]></category>
		<category><![CDATA[trailers]]></category>
		<category><![CDATA[widgets]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2008-04-01-widgets-bei-myspace-externe-programme-bei/</guid>
		<description><![CDATA[Fast jeder ist schon einmal auf myspace.com gewesen und hat nach Freunden gesucht, oder sich die ein oder andere auffällig bunt blinkende Seite angesehen. Als neues Highlight kann man sich zu seiner myspace Seite nun auch externe Programme, sogenannte Widgets hinzufügen. Dafür hat myspace.com seine Plattform geöffnet und eine API geschaffen. Derzeit ist das noch [...]]]></description>
			<content:encoded><![CDATA[<div id="attachment_40" class="wp-caption alignleft" style="width: 310px"><a href="http://www.christophbuente.de/wp-content/trailerquiz.jpg"><img class="size-medium wp-image-40" title="trailerquiz" src="http://www.christophbuente.de/wp-content/trailerquiz.jpg" alt="A screenshot of moviepilots trailerquizz on myspace.com" width="300" height="142" /></a><p class="wp-caption-text">A screenshot of moviepilots trailerquizz on myspace.com</p></div>
<p>Fast jeder ist schon einmal auf <a href="http://www.myspace.com">myspace.com</a> gewesen und hat nach Freunden gesucht, oder sich die ein oder andere auffällig bunt blinkende Seite angesehen. Als neues Highlight kann man sich zu seiner myspace Seite nun auch externe Programme, sogenannte <a href="http://de.wikipedia.org/wiki/Widget">Widgets</a> hinzufügen. Dafür hat <a href="http://www.myspace.com">myspace.com</a> seine Plattform geöffnet und eine API geschaffen. Derzeit ist das noch im Beta Stadium, jedoch gibt es schon einige nette Programme.</p>
<p><span id="more-39"></span></p>
<p>Als eines der ersten deutschen <a href="http://de.wikipedia.org/wiki/Widget">Widgets</a> gibt es das <a href="http://www.myspace.com/trailerquiz">Trailerquiz</a> von <a href="http://www.moviepilot.de">moviepilot</a>. Erstaunlich viel Spaß macht das Erraten eines Filmes anhand eines max. 10 Sekunden langen Trailers. Wählt man den richtigen der vier vorgegebenen Filmtitel, erhält man dafür Punkte. Hilfe erhält man, indem nach und nach falsche Filmtitel verschwinden.  Je mehr Zeit für die Antwort benötigt wird, desto weniger Punkte gibt es. Als zusätzlicher Anreiz werden die Punkte mit einem Bonusfaktor multipliziert, je häufter richtige Antworten in Folge gegeben werden. Allerdings ist das Spiel vorbei, wenn man fünf mal falsch geraten hat.</p>
<p>Wer meint, das wäre kinderleicht, der kann es gerne mal <a href="http://www.myspace.com/trailerquiz">ausprobieren</a>. Die kurzweilige Auswahl der Filme reicht vom aktuellen <a href="http://de.wikipedia.org/wiki/Blockbuster">Blockbuster</a> bis hin zum alltime favourite. Und nun viel Spaß beim raten, es gilt den Highscore von poweruser &#8220;<a href="http://profile.myspace.com/index.cfm?fuseaction=user.viewprofile&amp;friendid=362985276">zorro</a>&#8221; mit 2800 Punkte zu schlagen.</p>
<p><strong>Update:</strong> Jetzt ist der <a href="http://www.moviepilot.de/quizzes/trailerquiz">Trailerquiz</a> endlich auch ohne ein Myspace Konto benutzbar. Also, gleich mal testen!</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2008-08-19-lastfm-fur-das-iphone-gute-musik-fur-unterwegs/" rel="bookmark" class="crp_title">last.fm für das iPhone &#8211; gute Musik für unterwegs</a></li><li><a href="http://www.christophbuente.de/2008-08-04-bilder-versehentlich-geloscht-urlaubserinnerungen-ganz-leicht-zuruckholen/" rel="bookmark" class="crp_title">Bilder versehentlich gelöscht &#8211; Urlaubserinnerungen ganz leicht zurückholen</a></li><li><a href="http://www.christophbuente.de/2008-07-01-mysql-myisam-index-oder-nicht/" rel="bookmark" class="crp_title">MySQL MyISAM &#8211; Index oder nicht?</a></li><li><a href="http://www.christophbuente.de/2009-09-18-kalenderwoche-eine-einfuehrung/" rel="bookmark" class="crp_title">Kalenderwoche &#8211; eine Einführung</a></li><li><a href="http://www.christophbuente.de/2008-09-25-gratis-klingeltoene-fuers-iphone-herunterladen-oder-einfach-selbst-erstellen/" rel="bookmark" class="crp_title">Gratis Klingeltöne fürs iPhone herunterladen &#8211; oder einfach selbst erstellen!</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2008-04-01-widgets-bei-myspace-externe-programme-bei/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Stripes Framework &#8211; Tests für ActionBeans im Wizard Modus</title>
		<link>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/</link>
		<comments>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/#comments</comments>
		<pubDate>Thu, 24 Jan 2008 22:44:17 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Java JEE]]></category>
		<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[actionBean]]></category>
		<category><![CDATA[anleitung]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JEE]]></category>
		<category><![CDATA[junit]]></category>
		<category><![CDATA[mock]]></category>
		<category><![CDATA[MockRoundTrip]]></category>
		<category><![CDATA[MockServletContext]]></category>
		<category><![CDATA[stripes]]></category>
		<category><![CDATA[stripes framework]]></category>
		<category><![CDATA[Testing]]></category>
		<category><![CDATA[tests]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[unit tests]]></category>
		<category><![CDATA[wizard mode]]></category>
		<category><![CDATA[wizard modus]]></category>
		<category><![CDATA[__fp]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/</guid>
		<description><![CDATA[Bei meinem aktuellen Kunden wurde das Stripes Framework verwendet, um eine Java basierte Webanwendung zu erstellen. Für mehrseitige Formulare bietet Stripes ActionBeans mit einen Wizard Modus an. Dieser zeichnet sich dadurch aus, dass alle bereits ausgefüllten Formularfelder als versteckte Felder in alle folgenden Formulare eingefügt werden. Dieser Modus erspart bei mehrseitigen Formularen viel Arbeit, aber [...]]]></description>
			<content:encoded><![CDATA[<p>Bei meinem aktuellen Kunden wurde das <a href="http://www.stripesframework.org/">Stripes Framework</a> verwendet, um eine Java basierte Webanwendung zu erstellen. Für mehrseitige Formulare bietet Stripes ActionBeans mit einen <a href="http://www.stripesframework.org/display/stripes/Wizard+Forms">Wizard Modus</a> an. Dieser zeichnet sich dadurch aus, dass alle bereits ausgefüllten Formularfelder als versteckte Felder in alle folgenden Formulare eingefügt werden. Dieser Modus erspart bei mehrseitigen Formularen viel Arbeit, aber wie lässt sich dieser Wizard Modus am besten Testen? In diesem Artikel wird beschrieben, wie man mit Hilfe dem Stripes eigenen MockServletContexts sinnvolle Unit tests schreiben kann.</p>
<p><span id="more-31"></span></p>
<p>Die Dokumentation für <a href="http://www.stripesframework.org/display/stripes/Unit+Testing#UnitTesting-Approach2%3AMockContainerUsage">Stripes Unit tests</a> beschreibt sehr ausführlich, wie man mit Hilfe des MockServletContext eine Testinfrastruktur aufbaut, die es ermöglicht die ActionBeans ausserhalb des <acronym title="Java Enterprise Edition" lang="en">JEE</acronym>-Containers zu testen. Dies funktioniert für klassische ActionBeans ganz wunderbar. Doch beim schreiben von Tests für ActionBeans im Wizard Mode fiel auf, dass die Simulation eines Submits nicht funktionierte. Im Wizard Modus erzeugt das <a href="http://stripes.sourceforge.net/docs/current/javadoc/net/sourceforge/stripes/tag/FormTag.html">Stripes FormTag</a> ein zusätzliches verstecktes Formularfeld mit dem Namen &#8220;__fp&#8221;, welches alle Feldnamen des aktuellen Formulars in verschlüsselter Form enthält. Ohne dieses Feld verweigert der Stripes Dispatcher die Arbeit! Nimmt man nun einen beliebigen Wert und legt ihn in diesem Feld ab, so erzeugt das ebenfalls einen Fehler:<small>(Der Rechtschreibfehler ist tatsächlich aus dem Framework.)</small></p>
<p><code>Stripes attmpted and failed to decrypt the non-null value in the 'fields present' field.</code></p>
<p>Auch ist es nicht sinnvoll, einen beliebigen Text mit Hilfe des <code>CryptoUtil</code> zu verschlüsseln. Der Submit kann dann zwar erfolgreich simuliert werden, jedoch werden sämtliche Validierungsregel ignoriert. Mit folgenden Zeilen Code, kann ein korrekter Wert für das &#8220;__fp&#8221; Feld erzeugt werden:</p>
<pre lang="java">// Alle Feldnamen des aktuellen Formulars in eine Liste aufnehmen
   List fields = new ArrayList();
   fields.add("username");
   fields.add("password");
// Alle Feldnamen mit Trennzeichen zusammenfassen
   String hiddenFieldValue = HtmlUtil.combineValues(fields);
// servlet engine configurieren
   MockServletContext ctx = TestFixture.getServletContext();
   MockRoundtrip trip = new MockRoundtrip(this.ctx, YourCustomActionBean.class);
   HttpServletRequest request = (HttpServletRequest) trip.getRequest();
   hiddenFieldValue = CryptoUtil.encrypt(hiddenFieldValue, request);
   trip.setParameter(StripesConstants.URL_KEY_FIELDS_PRESENT, hiddenFieldValue);</pre>
<p>Für das Testen der Feldvalidierung ist es wichtig, dass die Feldnamen mit dem Wert des <code>&lt;input name="" /&gt;</code> Name Attributes in dem jeweiligen Formular übereinstimmen. Denn es werden tatsächlich nur die Felder validiert, die aus dem decodiertem Inhalt des &#8220;__fp&#8221; Feldes ausgelesen werden können.</p>
<p>Angenommen der oben abgebildete Code wird in der <code>setup()</code> Methode eines Unit Tests ausgeführt und speichert das <code>MockRoundtrip</code> Object in einem privaten Member, könnte ein Beispieltest könnte dann so aussehen:</p>
<pre lang="java">@Test
public void testLogin() throws Exception {
	trip.setParameter("username", "christophbuente");
	trip.setParameter("password", "secret");
	trip.execute("login");
	YourCustomActionBean bean = trip.getActionBean(YourCustomActionBean.class);
	Assert.assertTrue("Destination", "/account", trip.getDestination());
}</pre>
<p>Ich hoffe, dass diese Informationen ausreichen, um sinnvolle Unit Tests für ActionBeans im Wizard Modus zu schreiben. Bei Fragen helfe ich gerne weiter. Und wie immer sind Kommentare sehr willkommen.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/" rel="bookmark" class="crp_title">captcha tutorial &#8211; Bots aussperren</a></li><li><a href="http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">canoo webtest &#8211; Web-Anwendungen automatisch testen</a></li><li><a href="http://www.christophbuente.de/2009-05-16-remarkable-rails-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">Remarkable &#8211; Rails Anwendungen automatisch testen</a></li><li><a href="http://www.christophbuente.de/2007-11-24-advancing-rails-ein-workshop-mit-david-a-black/" rel="bookmark" class="crp_title">Advancing Rails &#8211; Ein Workshop mit David A. Black</a></li><li><a href="http://www.christophbuente.de/2007-08-05-objekte-und-beziehungen/" rel="bookmark" class="crp_title">Objekte und Beziehungen</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Paypal subscriptions &#8211; Abonnements in Java integrieren</title>
		<link>http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/</link>
		<comments>http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/#comments</comments>
		<pubDate>Sat, 10 Nov 2007 14:48:10 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[abonnement]]></category>
		<category><![CDATA[anleitung]]></category>
		<category><![CDATA[buttonfactory]]></category>
		<category><![CDATA[exclude]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[J2EE]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[JEE]]></category>
		<category><![CDATA[paypal]]></category>
		<category><![CDATA[paypal subschription]]></category>
		<category><![CDATA[sandbox]]></category>
		<category><![CDATA[Software Entwicklung]]></category>
		<category><![CDATA[Tutorial]]></category>
		<category><![CDATA[zahlungsabwicklung]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/</guid>
		<description><![CDATA[Für einen Kunden habe ich in der letzten Woche die Zahlungsabwicklung von Abonnements über Paypal integriert. Der Web Nutzer kann auf diesem einfachen Wege die Vorzüge vom Premiumdiensten nutzen. Der Vorteil hierbei ist: Der potentielle Käufer muss seine Kreditkarten- bzw. Kontodaten nicht an eine &#8220;nicht vertrauenswürdige&#8221; Seite weitergeben, sondern nutzt die bereits bei Paypal hinterlegten [...]]]></description>
			<content:encoded><![CDATA[<p>Für einen Kunden habe ich in der letzten Woche die Zahlungsabwicklung von Abonnements über <a href="http://www.paypal.de">Paypal</a> integriert. Der Web Nutzer kann auf diesem einfachen Wege die Vorzüge vom Premiumdiensten nutzen. Der Vorteil hierbei ist: Der potentielle Käufer muss seine Kreditkarten- bzw. Kontodaten nicht an eine &#8220;nicht vertrauenswürdige&#8221; Seite weitergeben, sondern nutzt die bereits bei <a href="http://www.paypal.de">Paypal</a> hinterlegten Daten. Ist der Käufer noch keine <a href="http://www.paypal.de">Paypal</a> Kunde, ist eine vorige Anmeldung allerdings notwendig.</p>
<p><span id="more-18"></span></p>
<p>Der grundlegende Mechanismus für Subscriptions ist denkbar einfach. An einer beliebigen Stelle in unserer Webapplikation wird der Benutzer per link oder submit button an die Paypal Seite geleitet. Alle relevanten Daten für die Zahlung werden entweder als GET Parameter (Link) oder POST Parameter (Button) übertragen. Dazu zählen vor allem folgende Angaben:</p>
<ul>
<li>Abonnementinterval (1 Tag, 4 Wochen oder 12 Monate)</li>
<li>Betrag pro Abonnementinterval</li>
<li>Währung</li>
<li>Paypal Id des Verkäufers</li>
<li>Rechnungsnummer</li>
<li>Automatische Verlängerung (ja/nein)</li>
<li>Beliebige zusätzliche Informationen</li>
</ul>
<p>Sollte der Benutzer noch kein Paypal Kunde sein, muss er sich bei Paypal erst registrieren. Um dies so einfach wie möglich zu gestalten können wir zusätzlich Benutzerinformationen mitsenden, die Paypal in das Registrierungsformular einfügt.</p>
<ul>
<li>Name, Nachname</li>
<li>Adress(Strasse, <abbr title="Postleitzahl">PLZ</abbr>, Stadt, Land)</li>
<li>Email Adresse</li>
</ul>
<p>Damit wir zum Testen unserer Applikation kein Vermögen hinblättern müssen, stellt Paypal eine sogenannte Sandbox zur Verfügung. Eine eigene <a href="https://developer.paypal.com">Developer Sandbox</a> kann man sich hier einrichten. Diese <a href="https://sandbox.paypal.com">Sandbox</a> stellt die kompletten Funktionen von Paypal zur Verfügung. Mit zwei Unterschieden: Es fließt kein echtes Geld und es gibt nur die Benutzer, die wir selbst angelegt haben. Um also einen Zahlungsfluss zu simulieren müssen in der Sandbox mindestens zwei Benutzer angelegt sein (Verkäufer und Käufer). Zum Erzeugen eines einfachen Bezahlknopfes  eignet sich die Paypal Button Factory, welche unter <a href="https://www.sandbox.paypal.com/us/cgi-bin/webscr?cmd=_merchant&amp;nav=3">Merchant Services</a> im Paypal Menü des Verkäufers erreichbar ist. Doch es gibt auch Gründe gegen einen statischen Bezahlknopf auf der Webseite. Angenommen wir wollen mehrere Abonnements verkaufen, die sich je nach Laufzeit im Preis unterscheiden. Wäre es da nicht besser ein eigenes Formular zu erstellen, welches dem Benutzer die komplette Auswahl unserer Produkte anzeigt? Nachdem sich der Benutzer für eines unserer Abonnements entschieden hat, überträgt er seine Auswahl per druck auf den Bezahlknopf. Unsere Webapplikation wertet diese Informationen aus und leitet den Benutzer per redirect auf die Paypal Seite um. Dazu muss die <acronym title="Uniform Resource Locator" lang="en">URL</acronym> mit sämtlichen Zahlungsinformationen dynamisch erzeugt werden. Hier eine Beispielimplementierung:</p>
<pre lang="java">public String getPurchaseUrl() throws EncoderException {
	StringBuffer urlBuffer = new StringBuffer("https://www.sandbox.paypal.com/cgi-bin/webscr");
	urlBuffer.append("?no_note=1"); // Abonnements unterstützen keine Notizen
	urlBuffer.append("&amp;a3=9.99"); // Der Preis des Abonnements
	urlBuffer.append("&amp;t3=M"); // Intervall Einheit = Monate
	urlBuffer.append("&amp;p3=1"); // Intervall Länge = 1 (Monat)
	urlBuffer.append("&amp;business=verkaeufer@deine.url.de"); // Die email adresse für den verkäufer account bei paypal
	urlBuffer.append("&amp;cmd=_xclick-subscriptions"); // Wir wollen eine abonnement
	urlBuffer.append("&amp;src=1"); // Abonnement verlängert sich automatisch
	urlBuffer.append("&amp;currency_code=EUR"); // Wir nehmen Zahlungen in Euro entgegen
	urlBuffer.append("&amp;invoice=Rechnungsnummer"); // Hier kann eine Rechnungsnummer vergeben werden
	urlBuffer.append("&amp;lc=DE"); // Die Zielseite soll in deutscher Sprache angezeigt werden
	urlBuffer.append("&amp;no_shipping=1"); // Wir brauchen keine Versandadresse, denn wir verkaufen virtuelle Güter oder Dienstleistungen
	URLCodec encoder = new URLCodec();
	return encoder.encode(urlBuffer.toString());
}</pre>
<p>Für das URL Encoding, welches alle Sonderzeichen in übertragbare Zeichen umwandelt, verwende ich die <a href="http://commons.apache.org/codec/">commons-codec Bibliothek</a> von <a href="http://commons.apache.org/">Apache Commons</a>.</p>
<p><!--adsense#vert-banner--></p>
<p>Die geladene Paypal Seite enthält nun alle Zahlungsinformationen. Der Potentielle Käufer muss sich lediglich mit seinem Paypal Benutzernamen anmelden und die Zahlung bestätigen. Ist die Automatische Rückleitung aktiviert, landet der Benutzer danach wieder auf unserer Seite. Die Aktivierung kann im Paypal Menu des Verkäufers unter &#8220;Profile -&gt;Website Payment Preferences&#8221; eingestellt werden. Dazu wählt man den Radiobutton &#8220;Auto Return ON&#8221; und trägt unter &#8220;Return URL&#8221; die entsprechende Url ein, an die der Benutzer weitergeleitet werden soll. Mit dieser URL überträgt Paypal eine Transaktions Id,  wenn in den Einstellungen zusätzlich noch &#8220;Payment Data Transfer&#8221; auf &#8220;On&#8221; gesetzt wurde. Die Dokumentation für <acronym title="Payment Data Transfer" lang="en">PDT</acronym> finden man auf der <a href="https://www.paypal.com/IntegrationCenter/ic_pdt.html">Paypal Developer Seite</a>. Paypal erwartet darauf hin, dass unsere Webapplikation folgende Informationen zurück sendet:</p>
<ul>
<li>TransaktionsId</li>
<li>Identitätstoken</li>
<li> Kommando</li>
</ul>
<p>Die TransaktionsId ist die eben empfangene Id. Der Identitätstoken kann an der Stelle eingesehen werden, wo &#8220;Payment Data Transfer&#8221; angeschaltet wurde. Dies stellt sicher, dass  Zahlungsinformationen nicht an unberechtigte Personen gesendet werden. Das Kommando muss den den Wert &#8220;_notify-synch&#8221; enthalten. Hier ein Beispiel, wie die Implementierung aussehen könnte. Für den Post Request zurück an Paypal habe ich die <a href="http://jakarta.apache.org/httpcomponents/httpclient-3.x/">commons-httpclient Bibliothek</a> von <a href="http://commons.apache.org/">Apache Commons</a> verwendet.</p>
<pre lang="java">/**
 * Verabeitet Paypals Payment Data Transfer
 *
 * @param txId The transactionId
 * @return
 */
public void handleCallback(long txId) {
	Map&lt;String,Object&gt; params = new HashMap&lt;String, Object&gt;();
	params.put("cmd","_notify_synch");
	params.put("tx", txId);
	params.put("at", "IDENTITY_TOKEN");
	try {
		PostMethod method = doPostRequest("https://www.sandbox.paypal.com/cgi-bin/webscr", params);
		String responseBody = method.getResponseBodyAsString();
		if(responseBody != null) {
			if(responseBody.startsWith("SUCCESS")) {
				// Super, alles hat geklappt
				// Erfolgsnachricht anzeigen
			} else if(responseBody.startsWith("FAIL")) {
				// Die Zahlung war nicht korrekt durchgeführt
				// Fehlernachricht anzeigen
			} else {
				// Irgendwas ging richtig in die Hose
				// Fehlerbehandlung
			}
		} else {
 			// Das war auch nicht korrekt
	 		// Fehlerbehandlung
 		}
	} catch (HttpException e) {
		// Error sending PDT response
	} catch (IOException e) {
		// Error sending PDT response
	}
}
/**
 * Does a POST request to the given url and the given parameter map
 *
 * @param url
 * @param params
 * @return The method containing the response
 * @throws HttpException
 * @throws IOException
 */
public PostMethod doPostRequest(String url, Map&lt;String, Object &gt; params)
 	throws HttpException, IOException {
		HttpClient client = new HttpClient();
		HttpConnectionManager conManager = client.getHttpConnectionManager();
		HttpClientParams clientParams = client.getParams();
		clientParams.setConnectionManagerTimeout(2000);
		HttpConnectionManagerParams managerParams = conManager.getParams();
		managerParams.setConnectionTimeout(5000);
		managerParams.setSoTimeout(5000);
		PostMethod method = new PostMethod();
		method.setURI(new URI(url, false));
		HttpMethodParams paramsM = method.getParams(); // Wiederholungen unterdrücken
		paramsM.setParameter(HttpMethodParams.RETRY_HANDLER,
	 		new DefaultHttpMethodRetryHandler(0, false));
	Set&lt;String&gt; keys = params.keySet();
	for (String key : keys) {
		String value = (String) params.get(key);
		method.addParameter(key, value); // Setze Url Parameter
	}
	client.executeMethod(method); // Http Post ausführen
	return method;
}</pre>
<p>Der Benutzer merkt vom Austausch der Zahlungsinformationen nichts, Es wird lediglich eine einfache Bestätigungsseite angezeigt. Auf dieser Seite wird dem Benutzer mitgeteilt wird, dass die Zahlung erfolgreich oder nicht erfolgreich war, und dass er von Paypal per Mail darüber benachrichtigt werden wird.</p>
<p>Paypal bietet zusätzlich die Möglichkeit, unsere Webapplikation asynchron über eingehende Zahlungen und/oder Kündigungen zu informieren. Die Mechanismus nennt sich <acronym title="Instant Payment Notification" lang="en">IPN</acronym>, was soviel bedeutet wie &#8220;sofortige Zahlungsavisierung&#8221;. Dieser Mechanismus ist deutlich robuster, als &#8220;Payment Data Transfer&#8221;. Doch dazu in später mehr.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2007-10-23-bots-aussperren-mit-captcha/" rel="bookmark" class="crp_title">captcha tutorial &#8211; Bots aussperren</a></li><li><a href="http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/" rel="bookmark" class="crp_title">Opensocial Gadgets &#8211; Apps für StudiVZ selbst entwickeln</a></li><li><a href="http://www.christophbuente.de/2008-01-24-stripes-framework-tests-fur-actionbeans-im-wizard-modus/" rel="bookmark" class="crp_title">Stripes Framework &#8211; Tests für ActionBeans im Wizard Modus</a></li><li><a href="http://www.christophbuente.de/2007-11-17-canoo-webtest-web-anwendungen-automatisch-testen/" rel="bookmark" class="crp_title">canoo webtest &#8211; Web-Anwendungen automatisch testen</a></li><li><a href="http://www.christophbuente.de/2009-09-09-500-internal-server-error-apache-config-vs-wp-super-cache/" rel="bookmark" class="crp_title">500 Internal Server Error &#8211; Apache config vs. WP-Super-Cache</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2007-11-10-paypal-subscriptions-abonnements-in-java-integrieren/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Crossbrowser Webentwicklung</title>
		<link>http://www.christophbuente.de/2007-07-28-crossbrowser-webentwicklung/</link>
		<comments>http://www.christophbuente.de/2007-07-28-crossbrowser-webentwicklung/#comments</comments>
		<pubDate>Sat, 28 Jul 2007 10:00:05 +0000</pubDate>
		<dc:creator>Christoph Bünte</dc:creator>
				<category><![CDATA[Web Entwicklung]]></category>
		<category><![CDATA[css stylesheet]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[ie 6.0]]></category>
		<category><![CDATA[internet explorer]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mac user]]></category>
		<category><![CDATA[microsoft windows]]></category>
		<category><![CDATA[parallels desktop]]></category>
		<category><![CDATA[seo]]></category>
		<category><![CDATA[standard browser]]></category>
		<category><![CDATA[validation]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.christophbuente.de/2007/07/28/crossbrowser-webentwicklung/</guid>
		<description><![CDATA[Nun hat das Schicksal auch mich ereilt! Obwohl ich es bisher vermieden habe, entwickle ich für meinen aktuellen Kunden ein Webfrontend. Dabei ist der Anspruch neben guter Benutzbarkeit: valides XHTML 1.0 strict Optimierung des Inhalts für die Suchmaschinenindizierung Trennung von Inhalt und Aussehen mit Hilfe von CSS Trennung von Inhalt und Verhalten mit Hilfe von [...]]]></description>
			<content:encoded><![CDATA[<p>Nun hat das Schicksal auch mich ereilt! Obwohl ich es bisher vermieden habe, entwickle ich für meinen aktuellen Kunden ein Webfrontend. Dabei ist der Anspruch neben guter Benutzbarkeit:</p>
<ul>
<li>valides <a href="http://www.w3.org/TR/xhtml1/" title="XHTML 1.0 strict">XHTML 1.0 strict</a></li>
<li>Optimierung des Inhalts für  die <a href="http://de.wikipedia.org/wiki/Suchmaschinenoptimierung" title="Suchmaschinenoptimierung">Suchmaschinenindizierung</a></li>
<li>Trennung von Inhalt und Aussehen mit Hilfe von <a href="http://www.css4you.de/" title="Cascading Style Sheet">CSS</a></li>
<li>Trennung von Inhalt und Verhalten mit Hilfe von <a href="http://script.aculo.us/" title="Scriptaculous">Javascript Bibliotheken</a></li>
<li>Und selbstverständlich: identisches Aussehen und Verhalten in allen Browsern</li>
</ul>
<p><span id="more-10"></span></p>
<p>[ad#vert-banner]</p>
<p>Soviel zur Theorie, denn jeder Webentwickler wird die Schmerzen bereits kennen, die ich im Moment empfinde. Die freie Auslegung der verabschiedeten Webstandards in den verschiedenen Webbrowsern macht die Entwicklung zu einer Tortour. Lässt sich in Browser f das Aussehen eines bestimmten Elementes definieren, ignoriert Browser e diese Anweisung beflissentlich. Definiert man die Position im externen <acronym lang="en" title="Cascading Style Sheet">CSS</acronym> Stylesheet für ein Element relativ, so muss es für &#8220;nicht-Standard-Browser&#8221; absolut positioniert werden, um das gleiche Ergebnis zu erzielen. So setzt sich die Quälerei unendlich fort. Und immer öfter zwängt sich mir die Frage auf, wie ein ominöser Mensch namens Bill G. in der Softwarewelt mit konsequent nicht-standardkonformer Software so groß werden konnte. Man sehe mir die Polemik nach, aber es sind wirklich Schmerzen, die ich demütig erdulde.</p>
<p>Für mich als Mac User stellt sich neben dem Unverständnis für die microsoftsche Auslegung von Standards  ein weiteres Problem: Wie testet man das entwickelte Produkt im berühmten Internet Exploder? Ich habe mich für die, in meinen Augen einzige, Möglichkeit entschieden, Microsoft Windows XP in einen digitalen Käfig zu sperren. Mit Hilfe von <a href="http://www.parallels.com/" title="Parallels Desktop">Parallels Desktop</a> läuft das weit verbreitete Betriebssystem als Gast in meiner nativem Umgebung. Wann immer ein neues Feature oder eine weitere Komponente vermeintlich fertig gestellt wurde, werfe ich mein Parallels an. Der schmermütige Atemzug beim Betrachten im IE ist obligatorisch. Doch kleine Tools wie die <a href="http://www.microsoft.com/downloads/details.aspx?familyid=e59c3964-672d-4511-bb3e-2d5e1db91038&amp;displaylang=en" title="Internet Explorer Developer Toolbar">Internet Explorer Developer Toolbar</a> verhelfen der Weboberfläche mehr oder weniger schnell zum gewünschten Aussehen bzw. Verhalten.</p>
<p>Natürlich ist Parallels nicht auf Microsoft Windows beschränkt, sondern gewährt auch anderen Vertretern der Gattung Betriebssystem temporären Unterschlupf. Ich kann jedem diese Software nur wärmstens ans Herz legen.</p>
<div id="crp_related"><h3>Ähnliche Beiträge:</h3><ul><li><a href="http://www.christophbuente.de/2007-12-06-voiceglue-installation-interactive-voice-response-leicht-gemacht/" rel="bookmark" class="crp_title">VoiceGlue Installation &#8211; Interactive Voice Response leicht gemacht</a></li><li><a href="http://www.christophbuente.de/2007-12-21-voiceglue-konfiguration-server-fur-sprachanwendungen-einrichten/" rel="bookmark" class="crp_title">VoiceGlue Konfiguration &#8211; Server für Sprachanwendungen einrichten</a></li><li><a href="http://www.christophbuente.de/2008-08-04-bilder-versehentlich-geloscht-urlaubserinnerungen-ganz-leicht-zuruckholen/" rel="bookmark" class="crp_title">Bilder versehentlich gelöscht &#8211; Urlaubserinnerungen ganz leicht zurückholen</a></li><li><a href="http://www.christophbuente.de/2009-10-29-opensocial-gadgets-apps-fur-studivz-selbst-entwickeln/" rel="bookmark" class="crp_title">Opensocial Gadgets &#8211; Apps für StudiVZ selbst entwickeln</a></li><li><a href="http://www.christophbuente.de/2007-11-24-advancing-rails-ein-workshop-mit-david-a-black/" rel="bookmark" class="crp_title">Advancing Rails &#8211; Ein Workshop mit David A. Black</a></li></ul></div>]]></content:encoded>
			<wfw:commentRss>http://www.christophbuente.de/2007-07-28-crossbrowser-webentwicklung/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Minified using memcached (Feed is rejected)
Page Caching using memcached
Database Caching 13/66 queries in 0.063 seconds using memcached
Object Caching 1363/1501 objects using memcached

Served from: www.christophbuente.de @ 2012-02-08 10:32:31 -->
