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




GWT (2.0) projekt felállítása maven2-vel és m2eclipse-szel

Célok:
  1. pom.xml -ben kelljen csak leírni a projekt függőségeit (ne eclipse függően a .classpath fájlban)
  2. forduljon le a projekt eclipse-ben (m2eclipse-ben igazából)
  3. forduljon le a projekt parancssori maven-nel
  4. indítható legyen a projekt eclipse-ből (m2eclipse-ben igazából)
  5. debugolható legyen a projekt eclipse-ben, de ne remote debugging módon (tehát ne egy debug porton csatlakozva az alkalmazáshoz), hanem rendesen
  6. ne keveredjenek a generált dolgok (itt most a maven által fordítás közben generált dolgokra gondolok) a kézzel létrehozottakkal, hogy könnyen lehessen a verziókövetést (pl. svn) kezelni
  7. kliensoldali módosításokhoz ne kelljen újraindítani a szervert
  8. multiprojekt kezelése m2eclipse-ben működjön: azaz, ha a Workspace Resolution be van kapcsolva, akkor ne jar-ból szedje a projektet, hanem workspace-ből
Ami kell:
  1. J2EE-s eclipse (én a Galileo-t használom)
  2. m2eclipse plugin: nagyjából mindegy, hogy melyik verzió, én jelenleg a 0.10.0.20100209-0800 -t használom
  3. Google Plugin for Eclipse (továbbiakban GEP) : 1.3.1 fölötti kell, én az 1.3.2-eset használom



Projekt felállítása:
  1. jobb gomb package v. project explorerben -> New... -> Dynamic Web Project (!! tehát nem maven-es archetypeból építkezünk m2eclipse-es projektlayouttal)



  2. a Java oldalon be kell allitani a hagyományos maven-es forráskönyvtárakat (src/main/java, src/main/resources, ...) és a default output foldert target/classes-ra kell állítani átmenetileg


  3. Web Module oldalon a Content directory src/main/webapp legyen


  4. Finish után ezt kell látni:


  5. Project properties-ben (jobb gomb a projekten -> Properties) Google -> Web Toolkit -> Use Google Web Toolkit pipa be (ezzel gyakorlatilag beizzítottuk a Google Eclipse Plugint)


  6. Ugyanitt Google -> Web Application -> "This project has a WAR directory"-nál a pipa be + WAR directory: src/main/webapp + "Launch and deploy from this directory" pipa KI (!!)


    Ez a pont az, ami miatt a Google Eclipse Plugin 1.3.1 fölötti verziója kell (amit mellesleg nemrég, 2010. márc. végén adtak ki), ugyanis korábban ez a képernyő nem létezett a pluginban, így a WAR alapját képző forráskönyvtár nem lehetett maven-es layoutban. A "Launch and deploy from this directory"-t azért kell kivenni, mert mi maven-nel a target alá fogjuk elkészíteni a war folder-t (a lefordított class-okkal, meg a belemásolt lib-ekkel), és onnan akarjuk majd indítani a projektet. Lásd később.
  7. Kezdjünk neki a maven-esítésnek. Dobjunk be egy pom.xml-t a projekt könyvtárba. Pl.:



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

    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




  8. Jobb gomb a projekten -> Maven -> Enable Dependency Management
  9. Jobb gomb a projekten -> Maven -> Update project configuration
  10. Csak azért, hogy tesztelhessük az eredményt, csináljuk gyorsan egy sima új (Google) Web Application Project-et, amibe a GEP beleteszi a példa Greetings applikáció fájljait
  11. Másoljuk be a fájlokat (*.java, web.xml, *.css és *.html fájlokat is) a mi projektünkbe. Köv. pont után mutatok egy project layout-ot.
  12. Ha vannak TOMCAT lib-ek a classpathunkon (ezek nem feltétlen kellenek), akkor cseréljük meg a GWT SDK és a TOMCAT libek sorrendjét, tehát a GWT SDK legyen feljebb.


  13. Futtassunk egy "mvn clean package" -t a projektre, amihez felvehetjük az m2eclipse-ben a következő Run configurationt:


  14. Elkészült a gwt-m2eclipse/target/gwt-m2eclipse-1.0.0 könyvtár, amibe belepillantva látható, hogy minden bennevan, ami egy webes projekthez kell, viszont semmi gwt-s dolgot nem tartalmaz. Hogyan is tartalmazhatna, hiszen mavent futtattunk rajta, gwt plugin/fordító parancsok nélkül. Tehát futtassuk le a GEP-os fordítást: jobb klikk a projekten -> Google -> Compile és a "WAR Directory Selection" képernyőn válasszuk ki a project-name/target/project-name-x.x.x könyvtárat, tehát nálam ez a gwt-m2eclipse/target/gwt-m2eclipse-1.0.0.
  15. Indíthatjuk a projektet: jobb klikk -> Run as... -> Web Application. Első alkalommal ki kell választanunk a WAR folder-t, tehát megint ugyanazt, mint fordításnál: project-name/target/project-name-x.x.x könyvtárat, tehát nálam ez a gwt-m2eclipse/target/gwt-m2eclipse-1.0.0

    Hol tartunk?A célok közül az 1, 2, 4, 5, és a 6, eddig teljesülni látszik (ha debuggal indítod, akkor debugolható).


  16. Nézzük, hogyan lehet csak maven-nel, GEP nélkül fordítani GWT-s projektet. Kell hozzá a gwt-maven plugin 1.2-es verziója, tegyük be a pom.xml-be a alá az alábbiakat (végén bekopizom az egész pom.xml-t is):




    org.codehaus.mojo
    gwt-maven-plugin
    1.2


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



  17. mvn clean gwt:compile package -t futtassunk rajta. Így lefordul maven-esen is (3-as cél pipa), hasznos, ha például hudsonnal automatikusan akarjuk buildeltetni.


  18. Ahhoz, hogy a kliensoldali változások azonnal életbe lépjenek, át kell állítanunk az output foldert a fordításnál (jobb klatty -> Properties -> Java Build Path menüpont alatt). Lényeg, hogy az src/main/java és src/main/resources könyvtárakból az eredmény a project-name/target/project-name-x.x.x/WEB-INF/classes generálódjon:




Tehát a célok közül az utolsón kívül az összes teljesül. Az utolsóhoz még nem találtam meg a megoldást, minden esetben jar-ba kell csomagolni a függőségeket (mvn install-t kell futtatni rájuk), hogy a classpathra kerülhessen a változás.

Végül egyben az egész 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
gwt-m2eclipse
war
1.0.0

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





top