Angry Birds



Az megvolt mindenkinek, hogy az új Chome-ra portolt (Canvas-os és WebGL-es) Angry Birds az GWT-ben íródott? Valahogy ez a "mellékes" infó a tech szájtokról lemaradt és most kerestem a GWT levlistában, de ott sem említik. Viszont meglestem a forrást is ÉSPEDIGDE.
Jó hír ez számunkra !@#$ DZSEGDISZ: http://chrome.angrybirds.com/

GWT layout #1 - HtmlPanel, RootPanel, FlowPanel, FormPanel, PopupPanel

Próbálok egy posztsorozatot készíteni az alap GWT-s panelekről, widgetekről, layoutról. Mivel munkám során hosszabban csak a SmartGWT-seket és Vaadin-osokat használtam (melyek semmilyen átfedésben nincsenek a natúr GWT-sekkel), ezért részletesen én is menet közben ismerem meg őket. Az egész posztsorozatban a következő html template-et használom, semmi extra nincs benne, a Google Eclipse Plugin által generálttól csak annyiban különbözik, hogy a RootPanel megismerésének érdekében feltettem egy mainPanel id-s div-et. Tehát a template html (GwtLayout.html nálam épp a neve):



<body>

<!-- OPTIONAL: include this if you want history support -->
<iframe src="javascript:''" id="__gwt_historyFrame" tabIndex='-1' style="position:absolute;width:0;height:0;border:0"></iframe>

<!-- RECOMMENDED if your web app will not function without JavaScript enabled -->
<noscript>
<div style="width: 22em; position: absolute; left: 50%; margin-left: -11em; color: red; background-color: white; border: 1px solid red; padding: 4px; font-family: sans-serif">
Your web browser must have JavaScript enabled
in order for this application to display correctly.
</div>
</noscript>
<div id="mainPanel"></div>
</body>





HtmlPanel


A HtmlPanelt fogom használni az összes többi panel/widget/layout bemutatásához, ezért most külön rá nem mutatok példát.
A felületre pakolt HtmlPanel egy div-et fog képezni a js által felépített domfában, és a div tartalma az, amit a HtmlPanel konstruktorában String-ként definiálunk.






RootPanel


A RootPanel kilóg a többi panel közül, nem generálódik hozzá semmilyen tag, hanem egyszerűen csak a statikus get() függvénnyel el tud kérni a template html-től id alapján tag-et vagy ha nem adunk meg id-t argumentumként, akkor a template html body tag-jét.


Első RootPanel példa:


Figyeljünk oda, hogy a "rootPanel" jelen esetben a body lesz, ezért a template-be bedefiniált "mainPanel" id-jú div MELLÉ kerül az aPanel és a bPanel kód.


Kód:

public void onModuleLoad() {
// gray background
RootPanel rootPanel = RootPanel.get();
rootPanel.setStyleName("rootPanel");

// blue background
HTMLPanel aPanel = new HTMLPanel("aPanel");
aPanel.setStyleName("aPanel");
rootPanel.add(aPanel);

// green background
HTMLPanel bPanel = new HTMLPanel("bPanel");
bPanel.setStyleName("bPanel");
rootPanel.add(bPanel);
}

Css:

.rootPanel {
background-color: gray;
}
.aPanel {
background-color: blue;
}
.bPanel {
background-color: green;
}
.aPanel, .bPanel {
color: white;
text-align: center;
}

Látvány:


A generált js kód alapján futásidőben felépített dom-fa:

<body class="rootPanel">
...
<div id="mainPanel"></div>
...
<div class="aPanel">aPanel</div>
<div class="bPanel">bPanel</div>
</body>



Második RootPanel példa:


Egyetlen különbség van az előzőhöz képest az általam írt kódban, mégpedig az, hogy a RootPanel.get("mainPanel")-t hívtam a RootPanel.get() helyett. Látszik, hogy ekkor a template html-be rakott "mainPanel" id-s div lesz a "rootPanel", ő kapja meg a rootPanel css style-t is (szürke háttér), de mivel ő nem teljes képernyős div, ezért a body háttere érvényesül, ami mindenfél felüldefiniálás nélküli, tehát fehér. Ezenkívül látszik, hogy most a "mainPanel" id-s div alá kerül be az "aPanel" és a "bPanel".


Kód:

public void onModuleLoad() {
// gray background
RootPanel rootPanel = RootPanel.get("mainPanel");
rootPanel.setStyleName("rootPanel");

// blue background
HTMLPanel aPanel = new HTMLPanel("aPanel");
aPanel.setStyleName("aPanel");
rootPanel.add(aPanel);

// green background
HTMLPanel bPanel = new HTMLPanel("bPanel");
bPanel.setStyleName("bPanel");
rootPanel.add(bPanel);
}

Css:

.rootPanel {
background-color: gray;
}
.aPanel {
background-color: blue;
}
.bPanel {
background-color: green;
}
.aPanel, .bPanel {
color: white;
text-align: center;
}

Látvány:


A generált js kód alapján futásidőben felépített dom-fa:

<body>
...
<div id="mainPanel" class="rootPanel">
<div class="aPanel">aPanel</div>
<div class="bPanel">bPanel</div>
</div>
...
</body>






FlowPanel


Sima div-et generál. Figyelem, visszatértem arra, hogy a rootPanel a body, valamint raktam a flowPanel-nek egy kis paddingot, hogy a pink-sége előbukkanjon a képen is.


Kód:

public void onModuleLoad() {
// gray background
RootPanel rootPanel = RootPanel.get();
rootPanel.setStyleName("rootPanel");

// pink background
FlowPanel flowPanel = new FlowPanel();
flowPanel.setStyleName("flowPanel");
rootPanel.add(flowPanel);

// blue background
HTMLPanel aPanel = new HTMLPanel("aPanel");
aPanel.setStyleName("aPanel");
flowPanel.add(aPanel);

// green background
HTMLPanel bPanel = new HTMLPanel("bPanel");
bPanel.setStyleName("bPanel");
flowPanel.add(bPanel);
}

Css:

.rootPanel {
background-color: gray;
}

.flowPanel {
background-color: pink;
padding: 10px;
}

.aPanel {
background-color: blue;
}

.bPanel {
background-color: green;
}

.aPanel, .bPanel, .flowPanel {
color: white;
text-align: center;
}

Látvány:


A generált js kód alapján futásidőben felépített dom-fa:

<body class="rootPanel">
...
<div id="mainPanel"></div>
<div class="flowPanel">
<div class="aPanel">aPanel</div>
<div class="bPanel">bPanel</div>
</div>
</body>






FormPanel


A FormPanel a SimplePanel leszármazottja, ez meg azt jelenti, hogy csak egy gyerekpanelt lehet hozzáadni, a második panel/widget hozzáadásakor IllegalStateException-t kapunk. A FormPanel-t nyilvánvalóan nem is arra találták ki, hogy még panel-eket pakoljunk hozzá, helyette inkább formelemeket, viszont ezeket be kell csomagolni egy layoutba/panelba és úgy rárakni a FormPanel-re. Itt a példa terjedelmesebb lesz, de megnézzük, hogy mivé fordulnak az egyes formelemek. Látható, hogy a RadioButton és a CheckBox span-be kerülnek, és kapnak label-t, míg a Simple verziójuk (SimpleRadioButton és SimpleCheckbox) simán csak az adott tag-gé fordul.


Kód:

public void onModuleLoad() {
// gray background
RootPanel rootPanel = RootPanel.get();
rootPanel.setStyleName("rootPanel");

// yellow background
FormPanel formPanel = new FormPanel();
formPanel.setStyleName("formPanel");
rootPanel.add(formPanel);

// pink background
FlowPanel flowPanel = new FlowPanel();
flowPanel.setStyleName("flowPanel");
formPanel.add(flowPanel);

TextBox textBox = new TextBox();
textBox.setText("textBox");
textBox.setStyleName("formElement");
flowPanel.add(textBox);

PasswordTextBox passwordTextBox = new PasswordTextBox();
passwordTextBox.setText("passwordTextBox");
passwordTextBox.setStyleName("formElement");
flowPanel.add(passwordTextBox);

RadioButton radioButton = new RadioButton("radioButton", "radioLabel");
radioButton.setStyleName("formElement");
flowPanel.add(radioButton);

SimpleRadioButton simpleRadioButton = new SimpleRadioButton("simpleRadioButton");
simpleRadioButton.setStyleName("formElement");
flowPanel.add(simpleRadioButton);

CheckBox checkBox = new CheckBox("checkBoxLabel");
checkBox.setStyleName("formElement");
flowPanel.add(checkBox);

SimpleCheckBox simpleCheckBox = new SimpleCheckBox();
simpleCheckBox.setStyleName("formElement");
flowPanel.add(simpleCheckBox);

TextArea textArea = new TextArea();
textArea.setText("textArea textArea textArea textArea textArea textArea");
textArea.setStyleName("formElement");
flowPanel.add(textArea);

ListBox listBox = new ListBox();
listBox.addItem("item1", "value1");
listBox.addItem("item2", "value2");
listBox.setStyleName("formElement");
flowPanel.add(listBox);

ListBox listBoxMultipleSelect = new ListBox(true);
listBoxMultipleSelect.addItem("item1", "value1");
listBoxMultipleSelect.addItem("item2", "value2");
listBoxMultipleSelect.setStyleName("formElement");
flowPanel.add(listBoxMultipleSelect);

FileUpload fileUpload = new FileUpload();
fileUpload.setStyleName("formElement");
flowPanel.add(fileUpload);

Hidden hidden = new Hidden();
hidden.setStyleName("formElement");
flowPanel.add(hidden);

DateBox dateBox = new DateBox();
dateBox.setStyleName("formElement");
dateBox.setValue(new Date());
flowPanel.add(dateBox);
}

Css (paddingot adok mind a flowPanel-nek, mind a formPanel-nek, hogy látszódjon belőlük valami, plusz a formelemeket display:block -kal látom el, hogy ne egy sorba kerüljenek):

.rootPanel {
background-color: gray;
}
.flowPanel {
background-color: pink;
padding: 10px;
}
.formPanel {
background-color: yellow;
padding: 10px;
display: block;
}
.formElement {
display:block;
}

Látvány:


A generált js kód alapján futásidőben felépített dom-fa:

<body class="rootPanel">
...
<form target="FormPanel_1" class="formPanel">
<div class="flowPanel">
<input type="text" tabindex="0" class="formElement">
<input type="password" tabindex="0" class="formElement">
<span class="formElement">
<input type="radio" name="radioButton" id="gwt-uid-1" tabindex="0">
<label for="gwt-uid-1">radioLabel</label>
</span>
<input type="radio" name="simpleRadioButton" tabindex="0" class="formElement">
<span class="formElement">
<input type="checkbox" id="gwt-uid-2" tabindex="0">
<label for="gwt-uid-2">checkBoxLabel</label>
</span>
<input type="checkbox" tabindex="0" class="formElement">
<textarea tabindex="0" class="formElement"></textarea>
<select tabindex="0" class="formElement">
<option value="value1">item1</option>
<option value="value2">item2</option>
</select>
<select multiple="" tabindex="0" class="formElement">
<option value="value1">item1</option>
<option value="value2">item2</option>
</select>
<input type="file" class="formElement">
<input type="hidden" class="formElement">
<input type="text" tabindex="0" class="formElement">
</div>
</form>
...
</body>






PopupPanel


PopupPanel-t nem kell hozzáadni semmilyen panelhez, a show metódusát hívva jelenik meg a többi panel/widget előtt. Ő maga a SimplePanel leszármazottja, ami (mint már tudjuk) azt jelenti, hogy csak egy widget-e / gyerekpanelja lehet. Tehát magyarul először rá kell tennünk pl. egy FlowPanel-t, és arra pakolni a gombokat, feliratokat. Amennyiben a popupPanel.setGlassEnabled(true)-t is meghívjuk, akkor felrak a popupPanel és a többi oldalelem közé még egy panelt, ami teljes brózer méretű, és egyrészt elszürkíti a hátteret (a default stílusbeállításokkal), valamint kiiktatja azok kattinthatóságát. A generált js kód által felépített dom-fában ez egy div-ként jelenik meg, mely a hierarchiában egy szintre kerül a popupPanel divjével.


Kód:

public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
rootPanel.setStyleName("rootPanel");

// black
final PopupPanel popupPanel = new PopupPanel();
popupPanel.setPopupPosition(100, 100);
popupPanel.setWidth("400px");
popupPanel.setHeight("200px");
popupPanel.setStyleName("popupPanel");
popupPanel.setGlassEnabled(true);

// yellow
FlowPanel flowPanel = new FlowPanel();
flowPanel.setStyleName("flowPanel");
popupPanel.add(flowPanel);

Label label = new Label("Some text!");
flowPanel.add(label);

Button closeButton = new Button();
closeButton.setText("Close");
closeButton.addClickHandler(new ClickHandler() {

@Override
public void onClick(ClickEvent event) {
popupPanel.hide();
}
});
flowPanel.add(closeButton);

Button button = new Button();
button.setText("Push me!");
button.addClickHandler(new ClickHandler() {

@Override
public void onClick(ClickEvent event) {
popupPanel.show();
}
});
rootPanel.add(button);
}

Css:

.rootPanel {
background-color: white;
}
.popupPanel {
background-color: black;
padding: 10px;
}
.flowPanel {
background-color: yellow;
padding: 10px;
}

Látvány a gomb megnyomása előtt:



Látvány a gomb megnyomása után:


A generált js kód alapján futásidőben felépített dom-fa a gomb megnyomása előtt:

<body class="rootPanel">
...
<button type="button" tabindex="0" class="gwt-Button">Push me!</button>
</body>

A gomb megnyomása után:

<body class="rootPanel">
...
<button type="button" tabindex="0" class="gwt-Button">Push me!</button>
<div class="gwt-PopupPanelGlass" style="position: absolute; left: 0px; top: 0px; width: 1680px; height: 957px; display: block; "></div>
<div style="position: absolute; left: 100px; top: 100px; overflow-x: visible; overflow-y: visible; " class="popupPanel">
<div class="popupContent">
<div class="flowPanel" style="height: 200px; width: 400px; ">
<div class="gwt-Label">Some text!</div>
<button type="button" tabindex="0" class="gwt-Button">Close</button>
</div>
</div>
</div>
</body>






Folytatás következik...

távoli parancsfuttatás linuxon

Adott a szitu, hogy több gépes szerverkörnyezetben szeretnénk egy gépről vezérelni a dolgokat, esetleg egy scripttel szeretnénk minden gépen minden komponenst elindítani/leállítani. Lehetne Java-san ügyeskedni, de minek túlbonyolítani a dolgot, a következő workflow működik (linuxon):

  1. Legyen ssh hozzáférés minden gépre (meglepő, mi ?!?)

  2. Generáljunk ssh kulcsot, és másoljuk szét a publikus párt a slave-ekre (nyomjuk bele az authorized_keys fájlba), a masteren (amin a távirányítószkript fog futni) meg legyen meg a privát.

  3. Ha sudo-zni kéne egy command futtatásához, akkor a sudoers fájlban / visudo-val kapcsoltathatjuk ki rá a password kérést

  4. Figyeljünk, hogy a sudoers fájlban ne legyen benne a távoli sudozás letiltása.

  5. Távoli parancsfuttatás:

    ssh user@hostname 'cmd'
    ssh user@hostname 'sudo cmd'



Mindegyik ponthoz rengeteg leírás áll rendelkezésre, ezért csak négy apró megjegyzés:

  • baromira figyeljünk a kulcsok, a .ssh könyvtár és az authorized_keys fájl jogosultságára; ha túl megengedő, akkor nem fog működni. Debugolásban segít az ssh '-v' kapcsolója.

  • sudoers fájlt sose szerkesszük közvetlenül, a visudo levalidálja mentés előtt, ezért használjuk inkább azt

  • Ha nem akarunk vi-jal szívni a visudo használatakor:

    export EDITOR=nano


  • A következő sor a sudoers fájlban éri el azt, hogy "ssh user@hostname 'sudo cmd'" parancs nem lesz engedélyezve, tehát a távoli parancsfuttatás.

    Defaults requiretty

    Ezt kikommentezve vagy kitörölve hívhatunk sudo-t távolról.

Adott a szitu, hogy pl. ffmpeg-et újrafordítás nélkül átvigyem egyik gépről a másikra (linuxosról linuxosra, és most épp Red Hat-esről Red Hat-esre). A binárist simán átkopizom, a gond csak az, hogy ha shared libraryt / shared object-et is használ, akkor azt nem fogja megtalálni, valami hasonló hibaüzenetet ad futtatáskor:

ffmpeg: error while loading shared libraries: libmp3lame.so: cannot open shared object file, or some other library.

Az üzenet egyértelmű, rá is találunk az eredeti gépen a /usr/local/lib alatt, hát fogjuk és áthozzuk ezeket is. De ennyitől még a hibaüzenet megmarad. Ugyanis a következő konfigurációra is szükség van az /etc/ld.so.conf fájlban:

jbuzi@gep:~$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
/usr/local/lib

Plusz, le kell futtatni az "ldconfig" parancsot is, hogy újrahúzza a configba beírt könyvtár/könyvtárak tartalmát.
Ezután már tudja a rendszerünk, hogy miket kell memóriába tölteni program indításkor.

gwt-maven-plugin: fordítás részletesen + futtatás dev mode-ban

Használjuk a következő pom.xml-t, figyelve főként a build->outputDirectory property-re, valamint a két gwt-maven-plugin-os konfigurációra:



<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>hu.jbuzi.sandbox</groupId>
<artifactId>gwt-maven-module-A</artifactId>
<packaging>war</packaging>
<version>1.0.0-SNAPSHOT</version>
<properties>
<gwt.version>2.0.4</gwt.version>
<maven.compiler.source>1.6</maven.compiler.source>
<maven.compiler.target>1.6</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-servlet</artifactId>
<version>${gwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.google.gwt</groupId>
<artifactId>gwt-user</artifactId>
<version>${gwt.version}</version>
<scope>provided</scope>
</dependency>

<!-- test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>hu.jbuzi.sandbox</groupId>
<artifactId>gwt-maven-module-B</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<!--
mikor eclipse .project es .classpath fajlokat keszitunk a 'mvn eclipse:eclipse' paranccsal, akkor az outputDirectory erteke hatarozza meg, hogy mi lesz a default target path az eclipse-ben forditaskor, tehat ezzel tudjuk elerni, hogy eclipse-ben futtatva a gwt alkalmazast, a modositasaink szerver- es browserrefresh nelkul eletbe lepjenek
-->
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>gwt-maven-plugin</artifactId>
<version>1.3-SNAPSHOT</version>
<configuration>
<!--
a gwt-maven-plugin runTarget property erteke hatarozza meg, hogy 'mvn gwt:run'-nal inditott, dev/hosted mode altal feldobott segedablak 'Launch default browser' gombja milyen url-re dobjon
-->
<runTarget>Gwt_module_A.html</runTarget>

<!--
a gwt-maven-plugin hostedWebapp property erteke hatarozza, hogy a 'mvn gwt:run'-nal inditott jetty melyik konyvtarat hasznalja, mint war konyvtar, tehat hol keresse a webapp-ot (default ertek a /war lenne)
-->
<hostedWebapp>
${project.build.directory}/${project.build.finalName}
</hostedWebapp>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.0.2</version>
</plugin>
</plugins>
</build>
</project>


Ekkor:


1, 'mvn compile' elkészíti a target/gwt-maven-module-A-1.0.0-SNAPSHOT/WEB-INF/classes könyvtárat, plusz a tartalmát, azaz a src/main/java alól lefordított .java fájlok .class kimenetét, plusz az src/main/resources alól odamásolja a fájlokat. Kommentbe is odaírtam, de mégegyszer: nem a maven miatt kell, hogy ide generálódjon, hanem azért, mert ha majd eclipse-es projektet készítünk ebből mvn eclipse:eclipse-szel, akkor ne kelljen kézzel beállítanunk a Default output folder-t.


2, 'mvn war:war' vagy 'mvn package' a target/gwt-maven-module-A-1.0.0-SNAPSHOT/ alá beteszi a nyitó html-t és css-t (nálam Gwt_module_A.html és Gwt_module_A.css a legalapabb Greetings alkalmazásból), plusz a target/gwt-maven-module-A-1.0.0-SNAPSHOT/WEB-INF/lib -et létrehozza és bemásolja a jar-okat, valamint a target/gwt-maven-module-A-1.0.0-SNAPSHOT/WEB-INF/web.xml -t is odamásolja.


3, 'mvn gwt:compile' elvégzi a lassú gwt-s kompájlt, majd a megszületett modulkönytárat javascriptesTÜL és hátéemelesTÜL odateszi a target/gwt-maven-module-A-1.0.0-SNAPSHOT/ alá (nálam ez a modulkönyvtár: gwt_module_a).


4, 'mvn gwt:run' dev mode-ban elindítja az alkalmazást a target/gwt-maven-module-A-1.0.0-SNAPSHOT alól.



Tegyük hozzá gyorsan, hogy nekem az sosem szempont, hogy maven-esen commandline indítható legyen dev mode-ban a gwt-s project, ez inkább csak valami plusz; dev mode-ban mindig Eclipse-ben futtattom az appot.
A lényeg , hogy maven-esen forduljon az alkalmazás (gwt-s javascript generálással együtt) a Hudson miatt, hogy eclipse-ben mindenféle debug varázslás (hot code replacement kliensoldalon és szerveroldalon) működjön dev mode-ban, valamint, hogy a Hudson által készített war rilíz tomcat alá deployolva automatikusan fusson. Ezzel a pom.xml -lel és a belőle 'mvn eclipse:eclipse'-szel készített eclipse projekttel ezek működnek.

gwt-maven-plugin: gwt-s újrafordításhoz szükséges dolgok a jar-ba

Tegyük fel, hogy gwt-s kliensoldali modulunkat abszolút újrafelhasználhatóvá akarjuk tenni, tehát egy különálló jar-t generálnánk belőle, amire később egy másik gwt-s kliensoldali projekt/modul dependálhat. GWT ahhoz, hogy optimális javascriptet generáljon, mindig teljes kliensoldali forráskódból fordít, tehát magyarul a jar-ba bele kell kerüljenek a .java fájlok is a .class-ok mellé, plusz legalább még a modulleíró gwt.xml fájl. Kétféleképp tudjuk ezt elérni a pom.xml-ben:



1, A fapadosabb megoldás, ha az alap maven resource-másolgatós fícsörjével tesszük ezt:




...



src/main/java

**/*.java



src/main/resources

**/*.gwt.xml




...



2, Közel ekvivalens megoldás, viszont tisztább, szárazabb, jobb, ha a gwt-maven-plugin -nal tesszük ezt:



...



org.codehaus.mojo
gwt-maven-plugin
1.2



resources






...



A 2,-es megoldásnak előnye többek közt, hogy nem kell beégetni, hogy src/main/java vagy src/main/resources alatt van a gwt.xml (ez azért fontos, mert a Google Eclipse Plugin-nek voltak/vannak problémái azzal, ha nem az src/main/java alatt van ez a leíró). Másik előny, hogy ha esetleg csak szerveroldali kódok is vannak a jar-ban, akkor azok mellé nem másol fölöslegesen .java forrásfájlt is, ezzel kisebb az eredmény jar mérete.

GWT (2.0) + maven + több projektes fejlesztés

Múltkor megnéztük, hogyan lehet m2eclipse segítségével gwt-zni maven-es környezettel. Egy probléma azonban volt az eredménnyel, mégpedig az, hogy abban az esetben, ha több projektre akartuk szétbontani az alkamazásunkat, együtt kellett élnünk két maven parancs futtatással, amelyeket minden egyes függőség projekt módosítás után futtatni kellett: mvn install (a függőség projekten) + mvn package (a gwt projekten). Ez sajnos fájdalmas nagy projekt esetén.


Közben viszont Józsa Kristóf rámutatott a maven eclipse plugin (hogy egyértelműbb legyen: az, amit mvn eclipse:eclipse -szel kell futtatni) egy számomra eddig ismeretlen fícsörére, nevezetesen, hogy nem csak egyszerűen a lokális maven repót címezi meg a függőség jar-ok lelőhelyeként, hanem a workspace-ünkben levő projektekre közvetlenül dependál. Szerintem ez a több projektes fejlesztés létminimuma, szóval meg kellett tudnom, hogyan is kell ezt. Lelövöm a poént, annyi a lényeg, hogy kell csinálni egy parent projektet, amiben a <modules> alatt fel van sorolva az összes projekt, ami a workspace-ünkbe is bekerül, majd ezen a parent-en futtatni az mvn eclipse:eclipse -et. (Hozzáteszem, hogy én a flat maven projekthierarchiát részesítem előnyben, tehát ahol a szülő a gyerekekkel egy szinten van).


Most akkor nulláról dobjunk össze egy többprojektes próbát (m2eclipse így most nem játékos, command line futtatjuk a maven-t, ezenkívül eclipse kell és google plugin). Verziók az előző bejegyzésem szerintiek.



  1. Csináljunk egy google webapp-ot, ezt alakítjuk át majd maven-esre. Így legalább kapunk egyből egy tesztkörnyezetet, azaz a Greetings app-ot.



  2. Alakítsuk át a projektstruktúrát maven-esre: csináljunk src/main/java forráskönyvtárt eclipse-ben az src helyett. Így néz ki az eredmény:



  3. Csináljuk meg azt a maven projektet, ami a függősége lesz a gwt projektnek: groupid: hu.jbuzi.sandbox; artifactid: gwt-maven-eclipse-dependency; version: 1.0.0-SNAPSHOT. pom.xml:


    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    4.0.0
    hu.jbuzi.sandbox
    gwt-maven-eclipse-dependency
    jar
    1.0.0-SNAPSHOT

    1.6
    1.6




    junit
    junit
    4.7
    test





    org.apache.maven.plugins
    maven-compiler-plugin
    2.1

    ${maven.compiler.source}
    ${maven.compiler.target}








  4. Csináljuk meg a parent projektet (csak pom-ot tartalmaz). Itt a pom.xml hozzá:

    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    4.0.0
    hu.jbuzi.sandbox
    gwt-maven-eclipse-parent
    1.0.0-SNAPSHOT
    pom
    gwt-maven-eclipse-parent

    1.6
    1.6


    ../gwt-maven-eclipse-dependency
    ../gwt-maven-eclipse




    org.apache.maven.plugins
    maven-compiler-plugin
    2.1

    ${maven.compiler.source}
    ${maven.compiler.target}



    org.apache.maven.plugins
    maven-eclipse-plugin
    2.8







  5. Másoljuk be ezt a pom.xml-t a gwt-s projekt könyvtárába (gwt-maven-eclipse):


    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    4.0.0
    hu.jbuzi.sandbox
    gwt-maven-eclipse
    war
    1.0.0-SNAPSHOT

    2.0.2
    1.6
    1.6



    com.google.gwt
    gwt-servlet
    ${gwt.version}
    runtime


    com.google.gwt
    gwt-user
    ${gwt.version}
    provided




    junit
    junit
    4.7
    test





    org.codehaus.mojo
    gwt-maven-plugin
    1.2


    org.apache.maven.plugins
    maven-war-plugin
    2.0.2







  6. gwt-maven-eclipse-parent könyvtárban állva, futtassunk egy mvn eclipse:eclipse -et.

  7. gwt projekten még kell kicsit reszelni (ezt sejteti, ha Refresh-t/F5-öt nyomunk eclipseben rajta, hibák jönnek), hogy teljesen maven-es legyen. Csináljuk src/main/webapp könyvtárat (nem kell, hogy source könyvtár legyen), mozgassuk bele a .css és .html fájlokat, csináljunk WEB-INF-et, abba meg mozgassuk bele a web.xml-t (végén mutatok screenshotot).

  8. Továbbra is a gwt projekten: jobb gomb -> Properties -> Google -> Web Application: This project has a WAR directory-nál legyen bent a pipa és src/main/webapp -ra írjuk át, és a Launch and deploy from this directory-ról vegyük le a pipát:




  9. gwt-s projektben commandline futtassunk egy mvn package-t, hogy létrehozza a megfelelő war könyvtárat, és így ebbe bele tudjuk irányítani a lefordított .class-okat. Jobb klatty -> Properties -> Build path -> Default output folder: gwt-maven-eclipse/target/gwt-maven-eclipse-1.0.0-SNAPSHOT/WEB-INF/classes:




  10. Csekkoljuk, hogy minden klappol-e. Fordítsuk le a google eclipse pluginnal a projektet. Jobb gomb -> Google -> GWT compile. A WAR folder kiválasztásnál a target/gwt-maven-eclipse-1.0.0-SNAPSHOT -ot adjuk meg, úgylesz jó.


  11. Futtassuk is, jobb klatty -> Run as... -> Web application. WAR könyvtár megadásnál ugyanazt adjuk meg, mint fordításnál: target/gwt-maven-eclipse-1.0.0-SNAPSHOT.


  12. Namost importáljuk be a függőség projektet eclipse-be (ami igazából még nincs bekötve függőségként). Jobb gomb -> Import... -> Existing projects into Workspace.


  13. src/main/java -ba hu.jbuzi.sandbox.gwtmaveneclipsedependency alá vegyük fel a GreetingsMsgCreator osztályt:

    package hu.jbuzi.sandbox.gwtmaveneclipsedependency;

    public class GreetingsMsgCreator {
    public String createMsg(){
    return "csipcsupcsodák";
    }
    }



  14. Tegyük be a gwt (gwt-maven-eclipse) projekt pom.xml -jébe függőségnek a gwt-maven-eclipse-dependency-t:


    hu.jbuzi.sandbox
    gwt-maven-eclipse-dependency
    1.0.0-SNAPSHOT




  15. Commandline a parent projektben futtassuk az mvn eclipse:eclipse -et megint, hogy végre láthassuk a csodát. Eclipse-ben ilyenkor utána mindig refresheljünk.


  16. Namost egy ilyen mvn eclipse:eclipse elront két dolgot az eddigi beállításainkon: az egyik, hogy visszaállítja a default output folder-t a target/classes-ra. Ezt könnyen orvosolhatjuk, írjuk a pom.xml <build> node alá: <outputDirectory>target/gwt-maven-eclipse-1.0.0-SNAPSHOT/WEB-INF/classes</outputDirectory>. A másik, hogy a GWT SDK library függőséget megszűnteti, erre nem tudok szép megoldást, minden mvn eclipse:eclipse után nyomjunk jobb gombot a projekten -> Google -> Web Toolkit -> és vegyük ki a pipát, majd egyből tegyük vissza, ezzel visszatér a világ rendje. Azt hiszem, ez kibírható áldozat.


  17. GreetingServiceImpl greetServer fv.-ének az utsó sorát írjuk át, hogy a mi jó kis üzenetkreálónkat is használja:

    return "Hello, " + input + "!

    I am running " + serverInfo
    + ".

    It looks like you are using:
    " + userAgent + "
    "+ new GreetingsMsgCreator().createMsg();



  18. futtassuk az alkalmazást debug módban(jobb klatty -> Debug as... -> Web Application).
    Tesztelhetjük, ha átírjuk a csipcsupcsodák szöveget. Nem csak, hogy a két maven parancsot megúsztuk, de az egész szerver restartot is.




Azaz szerver újraindítás nélkül tudunk szerveroldali módosításokat végezni, ezek egyből életbe lépnek (persze szokásos restrikciókkal: metódus szignatúrát nem érinthet a változás) + nem kell szenvednünk az m2eclipse-es bugoktól (kilőhetetlen végtelen build proecessek, stb.).


Végül egy kép a két projekt layoutjáról + a végső pom.xml a gwt-maven-eclipse projektben:







xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
4.0.0
hu.jbuzi.sandbox
gwt-maven-eclipse
war
1.0.0-SNAPSHOT

2.0.2
1.6
1.6



com.google.gwt
gwt-servlet
${gwt.version}
runtime


com.google.gwt
gwt-user
${gwt.version}
provided


hu.jbuzi.sandbox
gwt-maven-eclipse-dependency
1.0.0-SNAPSHOT



junit
junit
4.7
test



target/gwt-maven-eclipse-1.0.0-SNAPSHOT/WEB-INF/classes


org.codehaus.mojo
gwt-maven-plugin
1.2


org.apache.maven.plugins
maven-war-plugin
2.0.2




top