vrijdag 31 december 2010

Winter banden

Het is twaalf graden in oktober, de winter staat weer voor de deur. Op mijn bureau ligt al enige tijd een brief van de leasemaatschappij met het verzoek een afspraak te maken voor winter banden. Via de website probeer ik een afspraak te maken bij Profile Tyrecenter. Dat is niet eenvoudig. Mijn banden liggen blijkbaar in eindhoven. Ik woon in Tilburg. Via de site vul ik een verzoek in om de banden naar Tilburg te transporteren. Dit kan tot maximaal drie weken duren. Geen probleem. Het is nog niet echt koud. De website bericht dat er binnen drie weken contact met mij zal worden opgenomen voor een afspraak. Na iets meer dan drie weken slaat het weer volledig om. Ineens ligt er sneeuw. Ongeduldig bel ik op naar Profile hoe het met mijn winterbanden zit. Er staat niks in het systeem. De banden liggen nog steeds in Eindhoven. Telefonisch doe ik een nieuw verzoek de banden baar Tilburg te sturen. Dit kan maximaal drie weken duren. Fouten worden wel eens gemaakt. Ik rijd wel wat voorzichtiger door de sneeuw. na drie weken heb ik nog niks gehoord. Ik bel op. De jongens zitten te lunchen, ik zal zo worden terug gebeld. Twee uur later bel ik op. Ze hebben nog geen tijd voor gehad om uit te zoeken waar de banden zijn. Ik krijg de indruk dat mijn winterbanden gewoon kwijt zijn. Aan het einde van de volgende dag word ik eindelijk gebeld. Mijn banden staan klaar voor transport in Eindhoven. Inmiddels zijn er bijna zeven weken verstreken. Ik heb het gevoel dat mijn zaak de hoogte prioriteit heeft. Er ligt al bijna een maand sneeuw. Anyway, ik zou nog die week gebeld worden voor een afspraak. Dat gebeurde pas op woensdag de week erna. De man aan de telefoon wekte de indruk dat de banden al even in Tilburg lagen, maar bellen was er nog even niet van gekomen. De hoogste prioriteit. Een afspraak kon gelukkig diezelfde dag nog, om 14:30. Natuurlijk was het druk. Om 15:00 ging mijn auto eindelijk de lucht in en een half uur later reed ik weg me winter banden. Eindelijk weer veilig de weg op.

donderdag 15 juli 2010

Code highlighting in Blogger

Het heeft even geduurd maar eindelijk heb ik code highlighting in blogger voor elkaar. Wat heb ik gedaan? Ga in de instellingen naar het HTML sjabloon van je blog. Voeg voor de <l/head> tag de volgende code toe:
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shCore.css' rel='stylesheet' type='text/css'/> 
<link href='http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css' rel='stylesheet' type='text/css'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js' type='text/javascript'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shAutoloader.js' type='text/javascript'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js' type='text/javascript'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js' type='text/javascript'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushCss.js' type='text/javascript'/> 
<script src='http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJava.js' type='text/javascript'/> 

Merk op de er script geladen worden van iemand anders zijn pagina. De maker raadt aan om de scripts zelf te hosten.

Voeg vervolgens achter de </body> tag de volgende code toe:
<script>
  SyntaxHighlighter.config.bloggerMode = true; 
  SyntaxHighlighter.all();
</script>

Je kunt nu java code higlighten met:
<pre class="brush: java">code</pre>

woensdag 7 juli 2010

Minimale afhankelijkheden van Hibernate

Het is even geleden dat ik hier iets geschreven heb. Dat heeft twee redenen. Ten eerste heb ik de studiedag meerdere keren gebruikt om de drukte van het werk op te vangen. Daarnaast waren de dingen die ik gedaan heb, niet echt spectaculair: een Java boek uitlezen, spelen met HTML5, CSS3, Geolocation en JavaScript, Internationalisation van UIBInder in GWT en een chrome extensie bouwen. Dat werd allemaal zo goed uitgelegd dat ik weinig kon toevoegen aan de link waar het al perfect uitgelegd stond.

Vandaag was ik aan het spelen met Hibernate aan de hand van een tutorial. Hibernate is een OR-Mapper. Dat wil zeggen dat je objecten in je programma om kunt zetten naar tabel records in je database naar keuze. Als de mapping van de velden eenmaal bekend is, kun je de objecten eenvoudig ophalen zonder complexe queries te hoeven schrijven. Een nadeel is wel dat Hibernate een leercurve heeft vanwege de vele opties die de gebruiker heeft.

Wat mij opviel aan de tutorial was het grote aantal jar-files in het classpath van het voorbeeld. Een goede dertig zouden er nodig zijn voor een "Hello World" voorbeeld. Dat leek me wat overdreven. Zelfs de Google resultaten leken me nog wat uitgebreid dus ik ben met de trial and error methode gaan zoeken naar welke jar files je nu echt nodig hebt voor stap 1 van die tutorial (die het overigens zelfs inclusief alle jars bij mij niet deed zonder wat code aan te passen).

Hieronder de lijst met jars die je absoluut nodig hebt om het voorbeeld met hulp van MySQL te draaien:
  • hibernate3.jar (uiteraard)
  • mysql-connector-java-3.0.16-ga-bin.jar (alleen als je MySQL gebruikt)
  • log4j-1.2.9.jar (voor de logging, maar je kunt ook een andere logger gebruiken)
  • commons-collections-2.1.1.jar (wat collections spul dat niet standaard in Java zit)
  • commons-logging-1.0.4.jar (wat logging spul dat log4j nodig heeft)
  • dom4j-1.5.2.jar (om xml bestanden te interpreteren)
  • cglib-full-2.0.2.jar (doet iets voor hibernate maar het is me niet duidelijk wat)
  • ehcache-1.1.jar (geen idee wat het doet)
  • jta.jar (wordt gebruikt voor transacties)

De andere 20 jars heb je dus voor een simpel voorbeeld helemaal niet nodig. Hier kun je bovenstaande bestanden downloaden.

Zoals ik al zei heb ik het voorbeeld aan moeten passen om het te laten werken. Er is een transactie toegevoegd. Hieronder de code:
public class FirstExample {
 public static void main(String[] args) {
  SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
  Session session = sessionFactory.openSession();
  Transaction transaction = session.beginTransaction();
  
  try{
   Contact contact = new Contact();
   contact.setId(2);
   contact.setFirstName("Gerben");
   contact.setLastName("Kegel");
   contact.setEmail("gerben@bastarddomain.com");
   session.save(contact);
   transaction.commit();
  }catch(HibernateException e){
   transaction.rollback();
   System.out.println(e.getMessage());
  }finally{
   session.close();
  }
 }
}

zondag 25 april 2010

GAE & Tapestry

Applicaties die moeten draaien in de Google App Engine kun je ontwikkelen in Eclipse en de door Google ontwikkelde plugin. Het maken en deployen van een applicatie is daarmee nog simpeler dan het genereren van een war-file voor Tomcat. Met een druk op de deploy knop wordt je applicatie online gezet.

Als eerste GAE projectje wilde ik een Hello World uit het Tapestry (een framework van Apache) project in GAE deployen. De vraag hierachter is hoe eenvoudig het is om een bestaand framework in de cloud te draaien.

Zoiets begint natuurlijk met even Googlen en de eerste hit is een tutorial hoe je tapestry 5.1 in de App Engine kunt draaien. Helaas werkte het niet (meer) zo simpel omdat een van de XML parsers die Tapestry gebruikt niet (meer) is goedgekeurd door Google. Er waren volgens andere blogs enkele hacks nodig om het aan de praat te krijgen, maar helaas leek ook dat niet het verschil te maken. (Het kan zijn dat ik iets over het hoofd zag.)

Op weer andere blogs las ik dat de nieuwe Tapestry 5.2 wel in GAE kon draaien. En inderdaad, de foutmelding zag er nu ineens anders uit. Het duurde nog even voordat ik ondekt had dat GAE case-sensitive is en dat de template files dus exact dezelfde naam moeten hebben als de bijbehorende classes (wat lokaal onder Windows niet het geval is). Daarmee rekening houdend, heb je het allemaal zo aan de praat en kunnen we vast stellen dat het gebruiken van een bestaand framework in de App Engine geen probleem is mits alle dependencies goedgekeurd zijn door Google.

Ik moet wel toegeven dat het mij enkele uren kostte om de simpele Hello World aan de praat te krijgen, daarom nog een keer de twee pointers:
  • Gebruik de snapshot van Tapestry 5.2;
  • GAE is case sensitive, noem daarom je class geen Index en de template index.tml.




http://www.atentia.net/2010/04/tapestry-on-gaej-java-lang-verifyerror-stack-size-too-large-solved/

Google App Engine

GWT genereert JavaScript dat wordt uitgevoerd in een browser. Zoals eerder geschreven, kun je daarmee vanuit de browser direct methodes op de server aanroepen. GWT zorgt voor de (un)marshalling. Deze in Java geschreven methodes zijn onderdeel van een Java Servlet. Zo'n Java Servlet heeft een container nodig waarbinnen hij kan draaien. Voorbeelden hiervan zijn Tomcat, Jetty, JBoss en Glassfish. Al deze containers zijn programma's welke op een computer kunt installeren en die bovenop de Java Virtual Machine een set features bieden welke handig kunnen zijn bij het ontwikkelen van Web Applicaties.

Wanneer je web applicatie populair wordt, kom je al snel in de problemen. De webserver/Java container kan maar een bepaald aantal verzoeken per tijdseenheid aan. Wanneer het aantal verzoeken blijft oplopen zullen er gebruikers zijn die niet te zien krijgen wat de bedoeling was. De oplossing voor dit probleem is het inzetten van meerdere servers. Hoeveel je er nodig hebt blijft een gok, dus worden er vaak veel te veel servers ingezet. Google had dit probleem ook en hun oplossing hiervoor hebben ze publiek toegankelijk gemaakt in de vorm van hun Google App Engine (GAE).

GAE is onder andere een op Jetty gebaseerde Java container en is onderdeel van de cloud. Wanneer een web applicatie die in deze App Engine draait veel gebruikt wordt, besluit de App Engine om meerdere instanties van de applicatie naast elkaar te gaan draaien, en de verzoeken te verdelen over deze instanties.

De opschaling problemen zijn hiermee verholpen maar helaas zijn er ook nadelen. Zo kun je geen gebruik maken van een SQL server in de cloud. Je bent gebonden aan BigTable (de database van Google) voor het opslaan van data. Daarnaast kun je ook niet zomaar alle jar-files in je project stoppen omdat niet alle Java functionaliteit aanwezig is en GAE. Jar-files moeten daarom worden goed gekeurd door Google.

Als je denkt dat opschalen een van de problemen is waarmee je te maken krijgt in de toekomst, en Java je favoriete ontwikkel taal is, is de Google App Engine zeker het overwegen waard. Als je daarnaast ook nog eens GWT gebruikt, lijkt het helemaal ideaal te zijn.

woensdag 14 april 2010

GWT Widget Custom Look&Feel

In GWT kun je snel dingen maken die leuk ogen, maar al snel komt het Ikea gevoel om de hoek kijken. Iemand komt je huiskamer binnen, ziet een Ikea-meubel staan, en moet even opmerken dat hij het meubel uit de catalogus herkent. Er is niks mis met het meubel, maar mensen weten meteen hoeveel je betaald hebt en waar ze er ook zo een kunnen halen. Zo gaat het ook een beetje met GWT. Neem bijvoorbeeld een DialogBox die mooi geanimeerd verschijnt. De mooie lichtblauw randjes en geronde hoekjes verraden het al snel: "Ah, je hebt GWT gebruikt." Wat kunnen we hieraan doen?

Google heeft dit helaas niet heel makkelijk gemaakt, maar er zijn twee dingen die je kunt doen. De randjes worden gemaakt door een drietal plaatjes. Zo zitten alle hoekjes in hetzelfde plaatje. Wanneer je je project compileert staan deze p[laatjes in /gwt/standard/images. Je kunt van deze plaatjes de kleur, hoek of schaduw aanpassen en opnieuw opslaan. De randen van een DialogBox zullen er dan iets anders uitzien, maar je bent in ieder geval van die blauwe kleur af.


Download ze hier.

De andere manier is wat ingrijpender. Hiervoor moet je de CSS-style voor de DialogBox overriden. Als je alle classes die de DialogBox nodig heeft opnieuw definieert, en vervolgens de StylePrimaryName van het dialogBox object aanpast, zal je DialogBox er ook anders uitzien. Hieronder staan de classen die gedefinieerd moeten worden. Voor het gemak heb ik er de uitleg uit de documentatie bijgezet en de oorspronkelijke waarde laten staan. De primaire naam is al wel aangepast.

.gwt-CustomDialogBox { 
  /*the outside of the dialog */ 
}

.gwt-CustomDialogBox .Caption { 
  /*the caption */
  background: #e3e8f3 url(images/hborder.png) repeat-x 0px -2003px;
  padding: 4px 4px 4px 8px;
  cursor: default;
  border-bottom: 1px solid #bbbbbb;
  border-top: 5px solid #89130e;
}

.gwt-CustomDialogBox .dialogContent { 
  /*the wrapepr around the content */
}

.gwt-CustomDialogBox .dialogTopLeft { 
  /*the top left cell */
  background: url(images/corner.png) no-repeat -13px 0px;
  -background: url(images/corner_ie6.png) no-repeat -13px 0px;
}

.gwt-CustomDialogBox .dialogTopLeftInner { 
  /*the inner element of the cell  */
  width: 5px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogTopCenter { 
  /*the top center cell, where the caption is located  */
}

.gwt-CustomDialogBox .dialogTopCenterInner { 
  /*the inner element of the cell */
}

.gwt-CustomDialogBox .dialogTopRight { 
  /*the top right cell  */
  background: url(images/corner.png) no-repeat -18px 0px;
  -background: url(images/corner_ie6.png) no-repeat -18px 0px;
}

.gwt-CustomDialogBox .dialogTopRightInner { 
  /*the inner element of the cell */
  width: 8px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogMiddleLeft { 
  /*the middle left cell */
  background: url(images/vborder.png) repeat-y;
}

.gwt-CustomDialogBox .dialogMiddleLeftInner { 
  /*the inner element of the cell */
}

.gwt-CustomDialogBox .dialogMiddleCenter { 
  /*the middle center cell, where the content is located  */
  padding: 3px;
  background: white;
}

.gwt-CustomDialogBox .dialogMiddleCenterInner { 
  /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogMiddleRight { 
  /*the middle right cell  */
  background: url(images/vborder.png) repeat-y -4px 0px;
  -background: url(images/vborder_ie6.png) repeat-y -4px 0px;
}

.gwt-CustomDialogBox .dialogMiddleRightInner { 
  /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogBottomLeft { 
  /*the bottom left cell */
  background: url(images/corner.png) no-repeat 0px -15px;
  -background: url(images/corner_ie6.png) no-repeat 0px -15px;
}

.gwt-CustomDialogBox .dialogBottomLeftInner { 
  /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogBottomCenter { 
  /*the bottom center cell */
  background: url(images/hborder.png) repeat-x 0px -4px;
  -background: url(images/hborder_ie6.png) repeat-x 0px -4px;
}

.gwt-CustomDialogBox .dialogBottomCenterInner { 
  /*the inner element of the cell  */
  width: 5px;
  height: 8px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogBottomRight { 
  /*the bottom right cell */
  background: url(images/corner.png) no-repeat -5px -15px;
  -background: url(images/corner_ie6.png) no-repeat -5px -15px;
}

.gwt-CustomDialogBox .dialogBottomRightInner { 
  /*the inner element of the cell  */
  width: 5px;
  height: 8px;
  zoom: 1;
}

In de CSS kun je de achtergronden van de hoekjes en randen van de DialogBox aanpassen. Het is het makkelijkst om per hoekje een apart plaatje te maken, maar je kunt ook zoveel mogelijk in één plaatje stoppen om het aantal downloads te beperken. Ik ben niet bepaald een Photoshop koning maar heb toch geprobeerd om een ander randje te maken. Zie hier het resultaat.


Hiervoor is de volgende CSS gebruikt:
.gwt-CustomDialogBox { 
 /*the outside of the dialog */ 
}

.gwt-CustomDialogBox .Caption { 
 /*the caption */
 background: url(img/t.png) repeat-x;
  padding: 4px 4px 4px 8px;
  cursor: default;
  
}

.gwt-CustomDialogBox .dialogContent { 
 /*the wrapepr around the content */
}

.gwt-CustomDialogBox .dialogTopLeft { 
 /*the top left cell */
 background: url(img/tl.png) no-repeat;
}

.gwt-CustomDialogBox .dialogTopLeftInner { 
 /*the inner element of the cell  */
 width: 12px;
 height: 12px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogTopCenter { 
 /*the top center cell, where the caption is located  */
}

.gwt-CustomDialogBox .dialogTopCenterInner { 
 /*the inner element of the cell */
 height: 12px;
}

.gwt-CustomDialogBox .dialogTopRight { 
 /*the top right cell  */
 background: url(img/tr.png) no-repeat;
}

.gwt-CustomDialogBox .dialogTopRightInner { 
 /*the inner element of the cell */
 width: 12px;
 height: 12px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogMiddleLeft { 
 /*the middle left cell */
 background: url(img/l.png) repeat-y;
}

.gwt-CustomDialogBox .dialogMiddleLeftInner { 
 /*the inner element of the cell */
}

.gwt-CustomDialogBox .dialogMiddleCenter { 
 /*the middle center cell, where the content is located  */
 padding: 1px;
  background: white;
}

.gwt-CustomDialogBox .dialogMiddleCenterInner { 
 /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogMiddleRight { 
 /*the middle right cell  */
 background: url(img/r.png) repeat-y;
}

.gwt-CustomDialogBox .dialogMiddleRightInner { 
 /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogBottomLeft { 
 /*the bottom left cell */
 background: url(img/bl.png) no-repeat;
}

.gwt-CustomDialogBox .dialogBottomLeftInner { 
 /*the inner element of the cell  */
}

.gwt-CustomDialogBox .dialogBottomCenter { 
 /*the bottom center cell */
 background: url(img/b.png) repeat-x;
}

.gwt-CustomDialogBox .dialogBottomCenterInner { 
 /*the inner element of the cell  */
 height: 12px;
  zoom: 1;
}

.gwt-CustomDialogBox .dialogBottomRight { 
 /*the bottom right cell */
 background: url(img/br.png) no-repeat;
}

.gwt-CustomDialogBox .dialogBottomRightInner { 
 /*the inner element of the cell  */
 width: 12px;
  height: 12px;
  zoom: 1;
}

Hier kun je de plaatjes downloaden.

GWT en Webservices

Een webservice is een service of programma welke beschikbaar is over een netwerk. Om gebruik te maken van de service, hoeft deze niet lokaal geinstalleerd te worden. De service kan over het netwerk aangeroepen worden. Het idee is vergelijkbaar met Remote Procedure Calls maar niet langer moet de service in dezelfde taal geschreven zijn als het programma dat gebruik wil maken van de service.

Een service wordt gedefinieerd in een op XML gebaseerde taal (WSDL). Dit WSDL bestand beschrijft wat de service doet, welke inputs nodig zijn, en welke outputs eruit komen. Aan de hand van deze WSDL kunnen de service- en clientstubs ontwikkeld worden. Veel software pakketten maken het mogelijk om een WSDL te genereren uit een reeds gebouwde service. Evengoed kan een clientstub gegenereerd worden uit deze WSDL-definitie. Voor het transport van de boodschappen worden ook XML berichten gebruikt (SOAP, XML-RPC of REST). Echter, nadat de service- en clientstub klaar zijn, heb je hier niks meer mee te maken. Je roept gewoon een methode aan in de clientstub en je krijgt, met een netwerk afhankelijke vertraging, een antwoord terug.

Het voordeel van webservices dat het platform onafhankelijk is. Betekent dat dat we een webservice ook kunnen aanroepen vanuit een website? Als de webservice draait op dezelfde server als waar de website draait, moet dat mogelijk zijn, maar wanneer deze op een andere server draait, komen we in aanraking met het "same origin policy". Het is in een browser om veiligheids redenen niet mogelijk om via JavaScript contact op te nemen met een vreemde server. Maar er is een omweg en GWT geeft ons wat we nodig hebben.

In GWT kunnen via RPC direct communiceren met Java code op de server. Voor onze server, geldt er geen "same origin policy", dus van daaruit kunnen gewoon gebruik maken van elke bereikbare webservice. Hieronder zal ik een voorbeeldje uitwerken. De webservice die ik zal gebruiken maakt het mogelijk om SQL achtige queries te doen op de databron achter de service. In die databron bevinden zich klantnamen. Wat we gaan maken is een textsuggestbox die suggesties levert die uit de webservice komen en afhankelijk zijn van de reeds ingetypte karakters.

De eerste stap bij het gebruiken van een webservice is het genereren van een clientstub. Er zijn een hoop verschillende tools die dit kunnen. Deze keer heb ik Axis (van Apache) gebruikt. Hierbij zit een scriptje genaamd WSDL2Java, wat precies doet wat je verwacht. Er wordt een class aangemaakt met daarin de methodes die de webservice levert. Ook worden er, indien nodig, allerlei complexe datatypen gemaakt.

De tweede stap is het aanmaken van een nieuw GWT project. Voeg hier de zojuist gegenereerde classes toe aan het classpath. Dit kan door alles in een jar-file in te pakken en deze in het buildpath van het project op te nemen, of door simpelweg alle genegereerde classes naar ons project te kopieren.

Definieer de volgende interface:
String[] getSuggestions(String start);
Maak hiervoor ook de asynchrome variant en implementeer de service op de webserver door gebruik te maken van de gegenereerde webservice clientstub. Ik ga ervanuit dat dat wel moet lukken.

Om onze suggestbox te voorzien van suggesties hebben we een zogenaamd Oracle nodig. Dit is een class, afgeleid van SuggestOracle, waarbij de requestSuggestions methode opnieuw gedefinieerd wordt. Dat ziet er als volgt uit:
private class XServerOracle extends SuggestOracle {

  @Override
  public void requestSuggestions(final Request request, final Callback callback) {
   testService.getSuggestions(request.getQuery(), new AsyncCallback() {
    @Override
    public void onSuccess(String[] result) {
     Collection suggestions = new ArrayList();
     for (final String row : result) {
      suggestions.add(new Suggestion() {
       @Override
       public String getDisplayString() {
        return row;
       }

       @Override
       public String getReplacementString() {
        return row;
       }
      });
     }
     Response resp = new Response(suggestions);

     callback.onSuggestionsReady(request, resp);
    }

    @Override
    public void onFailure(Throwable caught) {}
   });
  }
 }

Dit Oracle wordt vervolgens gebruikt bij het aanmaken van het SuggestBox object:
final SuggestBox box = new SuggestBox(new XServerOracle());

Klaar.

Een voorbeeld staat op:
http://waalwijk.yall.nl:8080/CustomerSearchWidget

woensdag 31 maart 2010

Windows Azure

Afgelopen donderdag was ik bezoek bij Ordina in Nieuwegein waar DotNed een sessie organiseerde over Windows Azure ofwel het Azure Service Platform. Ik was benieuwd hoe Azure zich verhoudt ten op zichte van Google App Engine, een service van Google die hetzelfde probleem op probeert te lossen.

Welk probleem is dat dan? Schaalbare, betrouwbare hosting. Wanneer je een website hebt kun je deze hosten op je eigen PC of bij een hosting provider. Mocht je website naar verloop van tijd wat populairder worden, dan heb je al snel een probleem als je tegen de capaciteiten van die webserver aan loopt. De oplossing hiervoor is opschalen, wat wederom een hoop problemen met zich mee neemt. Er zijn op dit moment tenminste drie providers die dit opschaal probleem aanpakken: Google, Amazon en Microsoft.

Google kwam in april 2008 met zijn App Engine. Hiermee haalden zij het hele idee van hosting overhoop. Niet langer had je een (virtuele) machine tot je beschikking, maar je kon je website draaien in de cloud van Google. Bij het online zetten van je website, had je vervolgens geen flauw idee waar je website gebleven was, maar hij bleef ten aller tijden bereikbaar op .appspot.com. Als je website populair was, dan maakt de App Engine meerdere instanties aan. Als je website minder populair wordt, worden deze instanties weer afgesbroken. Toegang tot een relationele database is er helaas niet, maar BigTable (het opslag systeem van Google) is wel aanwezig. Het mooie van BigTable is dat je tegen de tabel praat alsof het een enkele tabel is, maar de tabel kan verspreid zijn over meerdere servers in de wereld. Met andere woorden, het opschaal probleem is hiermee helemaal opgelost. Natuurlijk zijn er ook nadelen. Er worden op het moment van schrijven slechts 2 talen ondersteund: Python en Java. Daarnaast is er geen SQL ondersteuning zijn er vele restricties waarmee rekening gehouden moet worden.

Amazon loste dit op een iets andere wijze op. Zij gaan toch weer een beetje terug naar oorspronkelijke idee. Je huurt bij Amazon virtual machines. Die VM's draaien dan welleswaar iun de cloud, maar daar merk je niet zoveel van. Met die virtual machine kun je doen wat je wilt. Als je meer capaciteit nodig hebt, kun je dynamisch (of elastisch zoals ze het zelf noemen) instanties aanmaken. Je betaald daarbij per uur.

Microsoft kwam afgelopen januari met hun eigen oplossing: Azure. Daarmee gaan ze een beetje tussen Amazon en Google inzitten. Enerzijds maken ze ook gebruik van virtual machines/instanties die een website/webapp (Web Role) of achtergrond proces (Worker Role) kunnen draaien. Ook hier moeten de instanties handmatig opgehoogd worden wanneer de capaciteit bereikt wordt. Je kunt echter met zo'n instanties niet zomaar alles doen. Je kunt geen software installeren, afgezien van de speciale Azure software die je met Visual Studio 2010 hebt kunnen maken. Daarnaast ben je afhankelijk van de services die Microsoft levert. Naast integratie met diverse Windows Live services, hebben ze ook een aantal nieuwe services in het leven geroepen om Azure bij te staan waaronder SQL Azure (een SQL Server versie die in de cloud draait), Azure Tables (Microsofts variant van BigTable), Queue Service (om taken van de Web Role naar de Server Role te sturen), Blob (om grote data structuren op te slaan) en een Filesystem (om simpelweg bestanden weg te schrijven). Uiteraard kun je wel gewoon gebruik maken van alle services die je zelf kunt hosten en die over het internet beschikbaar zijn.

Eindelijk begeeft Microsoft zich ook op de cloud markt, maar mijns inziens zijn ze toch nog iets te vroeg ingestapt. Er ontbreken een aantal features waarbij ik met name het automatisch opschalen van het aantal servers mis. Maar de ontwikkelaars kunnen vast beginnen en Microsoft kennende, zullen ze in de komende maanden of jaren hun Azure Service Platform blijven verbeteren.

woensdag 24 maart 2010

GWT en Maps

Een locatie kan informatie verrijken en met de opkomst van Google Maps en concurrenten wordt daar op internet veel gebruik van gemaakt. Bij menig online service kun je een event, foto of bericht koppelen aan een locatie. Dat is op zich ook niet zo gek als je ziet hoe makkelijk Google het ons gemaakt heeft. Er is een uitgebreide en goed gedocumenteerde API om Google Maps ook op jouw website een plekje te geven.

Afgelopen jaar probeerde ik voor een project Google Maps te integreren in een Java programma. Dat viel nog niet mee. Dus de vraag waar ik nu mee zit is of Google Maps te integreren is binnen GWT.

Het antwoord zal je niet verrassen, natuurlijk kan dat en het is nog makkelijker dan ik verwacht had. Ik ga hieronder niet uitleggen hoe het werkt, maar verwijs naar een tutorial van Google waar het erg helder uit staat gewerkt.

http://code.google.com/p/gwt-google-apis/wiki/MapsGettingStarted

En mijn eigen probeersel:
http://waalwijk.yall.nl:8080/GoogleMaps/

woensdag 17 maart 2010

GWT en Comet

Lang geleden, toen het world wide web uitgedacht werd, is er weinig rekening gehouden met de behoefte die de gebruikers later zouden krijgen. Er is een protocol bedacht (HTTP) waarmee een client, meestal een browser, een bestand op kan vragen. Een webserver antwoord dan door het bestand terug te sturen. Een aanvraag kan eventueel voorzien worden van extra informatie in de vorm van key-value pairs. Later is er nog een kleine aanpassing aan dit HTTP protocol geweest om het wat efficienter te laten werken wanneer er meerdere bestanden binnen gehaald moeten worden. Verdere aanpassingen zijn uitgebleven.

Met de opkomst van JavaScript in de browser, en daarmee AJAX, werd het mogelijk om informatie ook op een andere manier naar de browser te sturen. Niet langer moest keer op keer een volledige pagina worden gedownload, maar alleen de nieuwe informatie kon opgehaald worden in een asynchrone request. Wachttijden zijn hierdoor drastisch verlaagd. Het principe is echter niet veranderd. Nog steeds wordt er een bestand opgevraagd en ontvangen. Enkel de verwerking van dit bestand in de browser is gewijzigd.

Maar wat nu als je web applicatie afhankelijk is van server updates? Het is dan erg onhandig om de server periodiek te vragen of er misschien al een update is. Ten eerste zit er dan bijna altijd een vertraging in en ten tweede zullen er veel lege berichten op en neer gestuurd worden op de momenten dat er geen updates zijn. Helaas hebben we hier te maken met restricties in het HTTP protocol.

Zoals het altijd gaat in de software wereld, is er toch altijd wel weer iemand met een briljante ingeving die een loophole ontdekt in het protocol. Zo ook in dit geval. Wanneer je een webrequest doet, heb je vaak in no time het antwoord binnen. Maar zo was het niet altijd. Toen het internet nog wat trager was, deden berichten er wat langer over. Een browser moest dan maar wachten tot er een keer een antwoord kwam. Het is moeilijk vast te stellen of er nog niet lang genoeg is gewacht, of dat er iets mis is gegaan. Daarom is afgesproken dat er na een instelbare hoeveelheid tijd, vanuit gegaan mag worden dat er iets mis is gegaan. Er kan dan een nieuw request verstuurd worden. Dit principe is uitgebuit in een techniek die later PTTH, Reverse AJAX , of Comet (een ander populair Amerikaans schoonmaak middel naast Ajax) genoemd is.

Het idee is het volgende. Een client doet een request, maar hij krijgt geen antwoord. De server houdt de lijn open. Er kunnen twee dingen gebeuren. Er verschijnt een update op de server, welke opgestuurd kan worden naar de client. De openstaande lijn kan hiervoor worden gebruikt. Het tweede dat kan gebeuren is dat er geen update beschikbaar is voordat de timeout op gaat treden. In dat geval wordt er een leeg antwoord gestuurd. De client doet vervolgens een nieuwe aanvraag waardoor het proces opnieuw begint. De twee nadelen die aanwezig waren bij het polling mechanisme, worden hier verkleind. Updates worden meteen naar de client gestuurd en het aantal lege berichten is minimaal. Comet is niet nieuw, maar bestaat al een aantal jaren, al is het niet bij iedereen even bekend.

Kunnen we deze techniek nu ook gebruiken binnen GWT? Natuurlijk. En het mooie is, het vervelende werk is al gedaan door andere mensen. Ik heb gezocht naar libraries en na enkele uren lezen en spelen, ben ik blijven steken bij een project genaamd GWTEventService. Van alles dat ik geprobeerd heb, werkte deze toch het lekkerst. Een client kan zich inschrijven op een event type, en de server kan een event van dit type publiceren. Er gaat dan bij de client, mits hij geabonneerd is op dit event-type, vrijwel meteen een event af.

Tijd voor een voorbeeld. Stel we hebben twee simpele chat Widgets (toch weer een chat voorbeeld) die volledig onafhankelijk van elkaar zijn. Beide kunnen ze een berichtje sturen naar de server en dit berichtje moet dan meteen doorgestuurd worden naar de andere chat Widgets.

Maak een nieuw project aan, download de jar-file van GWTEventService en voeg deze toe aan je build-path. Voeg ook de volgende regel toe in het .gwt.xml configuratiebestand.
<inherits name="de.novanic.eventservice.GWTEventService" />

De volgende stap is het implementeren van het event dat we willen versturen.
import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.client.event.domain.Domain;
import de.novanic.eventservice.client.event.domain.DomainFactory;

public class MyEvent implements Event
{
   /**
    * This distinguishes the event from others
    */
   public static final Domain NS = DomainFactory.getDomain("server_message_domain");

   private String message;

   private String sender;

   /**
    * Needed for serialization
    */
   public MyEvent() {}

   public MyEvent(String sender, String message) {
       this.sender = sender;
       this.message = message;
   }

   public String getMessage() {
       return message;
   }

   public String getSender() {
       return sender;
   }
}

De volgende stap is het schrijven van de standaard RPC interface zoals dat binnen GWT gebruikelijk is. We definieren een methode waarmee een chat client een berichtje kan sturen.

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;

@RemoteServiceRelativePath("MyInterface")
public interface MyInterface extends RemoteService {
 public void say(String who, String what);
}

En zoals gewoonlijk hebben we ook de asynchrone variant.
import com.google.gwt.user.client.rpc.AsyncCallback;

public interface MyInterfaceAsync {
 public void say(String who, String what, AsyncCallback anAsyncCallback);
}

Deze methodes moeten we implementeren aan de server kant.

import nl.yall.test.eventservice.client.MyEvent;
import nl.yall.test.eventservice.client.MyInterface;
import de.novanic.eventservice.service.RemoteEventServiceServlet;

public class MyInterfaceImpl extends RemoteEventServiceServlet implements MyInterface {
   public void say(String who, String what) {
       MyEvent event = new MyEvent(who, what);
       addEvent(MyEvent.NS, event);
   }
}

Vervolgens moeten we enkel de client nog implementeren. Om het een beetje overzichtelijk te houden, heb ik alleen de essentiele dingen achter gelaten.

import de.novanic.eventservice.client.event.Event;
import de.novanic.eventservice.client.event.RemoteEventService;
import de.novanic.eventservice.client.event.RemoteEventServiceFactory;
import de.novanic.eventservice.client.event.listener.RemoteEventListener;

public class EventService implements EntryPoint {
 private final MyInterfaceAsync service =
  GWT.create(MyInterface.class);

 private RemoteEventService remoteService =
  RemoteEventServiceFactory.getInstance().getRemoteEventService();

 public void onModuleLoad() {
  HorizontalPanel panel = new HorizontalPanel();
  panel.add(new MyPanel("Gerben"));
  panel.add(new MyPanel("David"));

  RootLayoutPanel.get().add(panel);
 }

 private class MyPanel extends FlowPanel {
  private final TextBox messageBox = new TextBox();
  private final ListBox listBox = new ListBox(true);
  private final String user;
  
  public MyPanel(final String user) {
   this.user = user;
   
   setSize("60%", "60%");
   
   remoteService.addListener(MyEvent.NS, new RemoteEventListener() {
    public void apply(Event e) {
     if(e instanceof MyEvent) {
      MyEvent me = (MyEvent) e;
      listBox.addItem(me.getSender() + ": " + me.getMessage());
     }
    }
   });

   messageBox.addKeyPressHandler(new KeyPressHandler() {
    @Override
    public void onKeyPress(KeyPressEvent event) {
     if (event.getCharCode() == '\r') {
      sendMessage();
      messageBox.setText("");
     }
    }
   });
   
   listBox.setSize("100%", "80%");
   listBox.addItem("Listening...");
   add(new Label(user));
   add(listBox);
   add(messageBox);
  }

  private void sendMessage() {
   service.say(user, messageBox.getText(), new AsyncCallback() {
    public void onFailure(Throwable aThrowable) {}
    public void onSuccess(Void aResult) {}
   });
  }
 }
}

Tenslotte moeten we de servlet nog toevoegen aan de configuratie file voor de applicatie server (Web.xml)

<servlet>
  <servlet-name>EventService</servlet-name>
  <servlet-class>de.novanic.eventservice.service.EventServiceImpl</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>EventService</servlet-name>
  <url-pattern>/eventservice/gwteventservice</url-pattern>
</servlet-mapping>

<servlet>
  <servlet-name>EventServlet</servlet-name>
  <servlet-class>nl.yall.test.eventservice.server.MyInterfaceImpl</servlet-class>
</servlet>

<servlet-mapping>
  <servlet-name>EventServlet</servlet-name>
  <url-pattern>/eventservice/MyInterface</url-pattern>
</servlet-mapping>

Het gebrek aan de mogelijkheid tot pushen van gegevens wordt vaak als een nadeel gezien van web applicaties, maar met de techniek die hierboven beschreven staat, hoeft dat dus niet zo te zijn.

Succes met spelen.

Gebruikte libraries:
http://code.google.com/p/gwteventservice/

Het voorbeeld:
http://waalwijk.yall.nl:8080/EventService/

woensdag 24 februari 2010

GWT Widget

Het grafische deel van een GWT applicatie bestaat uit Panels en Widgets. Een Panel is een container die andere Panels of Widgets kan bevatten. Verschillende panels rangschikken hun inhoud op verschillende manieren (links naar rechts, boven naar beneden, etc). Een Widget is een user-interface element. Standaard hebben we hier de TextBox, Button, Checkboxen etc. Daarnaast kunnen we ook zelf Widgets bouwen.

Stel nu dat we zo'n Widget hebben gebouwd en we willen die graag gebruiken op onze website, zonder alles om te bouwen naar GWT. Dit gaat als volgt. We voegen aan onze bestaande website een element toe:
<div id="mijnwidget"></div>
Dit is de placeholder waar onze widget gaat komen. Start nu een nieuw GWT project en implementeer de onModuleLoad() methode als volgt:
public void onModuleLoad() {
 Widget widget = new MijnWidget();
 RootPanel.get("mijnwidget").add(widget);
}
Compileer je project en zet de directory met daarin het resultaat naast je oorspronkelijke HTML bestand. Voeg tenslotte de volgende regel toe binnen de <head> en </head> tags:
<script type="text/javascript" language="javascript" src="mywidget/mywidget.nocache.js"></script>
waarin mywidget verwijst naar het door GWT gegenereerde JS bestand. Nu zou het moeten werken.

Op zich is dit nog niet zo spannend, dus we gaan een stap verder. In het volgende voorbeeld gebruiken we een uitgebreide versie van de RichTextArea, voorzien van een toolbar. Deze Widget willen we voortaan gebruiken als standaard textarea. We maken het volgende html bestand:
<html>
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <link type="text/css" rel="stylesheet" href="MyExample.css">
    <title>RichTextArea Demo</title>
    <script type="text/javascript" language="javascript" src="myexample/myexample.nocache.js"></script>
  </head>

  <body>
    <form action="myexample/print" method="post">
      <textarea name="mytext" class="rich"></textarea><br />
      <input type="submit" />
    </form>
  </body>
</html>
Nu moeten we nog een script maken dat onze Widget over de bovenstaande textarea heenplakt. Dit doen we als volgt. We gaan eerst op zoek naar de elementen waar onze Widget op geplakt moet worden. Deze elementen slaan we op. Vervolgens vervangen we deze elementen door placeholders. Vervolgens zetten we op de plaats van deze placeholder een kopie van onze Widget en een hidden input element waarin we de waarde van de textarea opslaan. Als de waarde in de textarea verandert, moet ook de waarde in onze hidden inputbox mee veranderen. En voila, onze eigen input widget.
public class MyExample implements EntryPoint {
 
 private Vector richTextAreas = new Vector();
 
 public void onModuleLoad() {
  Element body = RootPanel.getBodyElement();
  
  NodeList inputs = body.getElementsByTagName("textarea"); 
  
  for (int i = 0; i < inputs.getLength(); i++) {
   Element element = inputs.getItem(i);
   if (element instanceof TextAreaElement) {
    TextAreaElement textAreaElement = (TextAreaElement) element;
    if (textAreaElement.getClassName().startsWith("rich")) {
     richTextAreas.add(textAreaElement);
    }
   }
  }
   
  for (TextAreaElement element : richTextAreas) { 
   Element parent = element.getParentElement();
   String id = generateId(); 
   Element el = new Label().getElement();
   el.setId(id);
   parent.replaceChild(el, element);
   
   replaceTextArea(id, element.getName());
  }
  
  
 }
 
 private void replaceTextArea(String id, String name) {
  final Hidden hidden = new Hidden();
  hidden.setName(name);
  final RichTextArea textArea = 
   new RichTextArea();
  
  textArea.addRichTextHandler(new RichTextHandler() {

   @Override
   public void onChange(RichTextEvent event) {
    hidden.setValue(event.getHtml());
    
   }});
  RootPanel.get(id).clear();
  RootPanel.get(id).add(textArea);
  RootPanel.get(id).add(hidden);
 }
 
 private String generateId() {
  return UUID.uuid();
 }
}
An example can be found here.

woensdag 17 februari 2010

GWT 2.0

In het kader van persoonlijke ontwikkeling heb ik met mijn werkgever afgesproken om komend jaar één dag per week te besteden aan het leren van nieuwe (werk gerelateerde) vaardigheden. Tijdens het normale werk is er vaak geen tijd om ergens wat dieper in te duiken, of om een onbekende techniek, die wellicht heel handig zou kunnen zijn, te onderzoeken. Tijdens mijn nieuwe dag in de week neem ik de tijd om met deze onbekende technieken te experimenteren. In deze blog wil ik zo nu en dan kort vertellen wat ik geleerd of gemaakt heb.

Om die reden ben ik kort geleden weer eens aan de slag gegaan met Google Web Toolkit 2.0 (GWT). GWT is een Java-framework dat het mogelijk maakt om JavaScript-applicaties te ontwikkelen in een Java omgeving. Het maken van wat exotischere JavaScript-applicaties is vervelend omdat de verschillende webbrowsers eigen JavaScript engines (en HTML renderers hebben). De ene fabrikant volgt de JavaScript standaard stricter dan de andere, waardoor er kleine verschillen zijn ontstaan. Een JavaScript-applicatie kan zich daarom op verschillende manieren gedragen in verschillende browsers. De programmeur moet hier rekening mee houden door in sommige gevallen functionaliteit op verschillende manieren te programmeren. Daarnaast is het debuggen van een JavaScript applicatie een vervelende klus.

GWT geeft ons een omgeving waarin we op een hoger niveau een applicatie kunnen ontwikkelen. Gebruikmakend van libraries en tools van Google, kun je je webapplicatie helemaal in Java ontwikkelen, debuggen en testen. Wanneer je klaar bent kun je je code omzetten naar JavaScript-code. Tijdens deze stap wordt er rekening gehouden met de browser verschillen. De ontwikkelaar is daarom sneller klaar, of kan in dezelfde tijd een geliktere applicatie in elkaar zetten.

GWT had lang last van kinderziektes en onvolledigheden, maar inmiddels kunnen we bij versie 2.0 zeggen dat GWT volwassen is geworden. Ik kan daarom iedereen die bezig is met webapplicaties aanraden er eens naar te kijken.