Witam w pierwszej części przewodnika „Vaadin jako klient webservice”. W tej części zajmiemy się przygotowaniem projektu. Jest to ważny etap ponieważ pozwoli on nam na zrozumienie jak działa aplikacja oraz przy okazji poznamy pewne sztuczki związane z optymalizacją aplikacji Vaadin za pomocą Springa.
Cały proces przygotowania można podzielić na kilka etapów.

  • Przygotowanie pom.xml
  • Dodatkowa konfiguracja Vaadin
  • Podstawowa konfiguracja Springa i JPA
  • Wygenerowanie klienta WS za pomocą Axis
  • Posprzątanie wygenerowanych klas i ich konfiguracja jako beanów Springa

Kroki są podane w najwygodniejszej, moim skromnym zdaniem, kolejności. Najlepiej wykonywać je na raz (w ramach jednej sesji kodowania), ponieważ brak któregokolwiek, w przypadku naszej aplikacji, będzie powodował dość nieprzyjemny fail.
Zaczynamy…

Przygotowanie pom.xml

Z linii poleceń generujemy projekt Vaadin. Następnie dodajemy niezbędne zależności do Sprina, JPA2, Hibernate, bazy danych (postgres) oraz dodatkowo wykorzystywany w tym konkretnym miejscu ICEPush. Komponent Vaadin pozwalający na wykonywanie żądań typu push z serwera do przeglądarki (tzw. Comet). To ostatnie rozwiązanie wymaga pobrania komponentu z ICEPush-gwt ze strony icepush.org. Całość na licencji MPL no i trzeba się rejestrować. Jeżeli chcemy korzystać z tego komponentu należy dodatkowo skonfigurować Vaadin.

Dodatkowa konfiguracja Vaadin

Komponent ICEPush wymaga dwóch rzeczy. Po pierwsze należy wymienić klasę servletu obsługującego naszą aplikację. Nowa postać pliku web.xml:

Listing 1. web.xml skonfigurowany dla ICEPush

<servlet><servlet-name>Vaadin Application Servlet</servlet-name><servlet-class>org.vaadin.artur.icepush.ICEPushServlet</servlet-class><init-param><description>Vaadin application class to start</description><param-name>application</param-name><param-value>pl.koziolekweb.vaadin.codecompiler.MyVaadinApplication</param-value></init-param><init-param><param-name>widgetset</param-name><param-value>pl.koziolekweb.vaadin.codecompiler.widgetset.MyAppWidgetSet</param-value></init-param><load-on-startup>1</load-on-startup></servlet>

Piękne to nie jest, ale działa. Kolejnym krokiem jest stworzenie pliku MyAppWidgetSet.gwt.xml w wybranym pakiecie. Którym dokładnie? Wszystko jedno, bo to tylko sprawa organizacyjna. Plik ten powinien wyglądać w następujący sposób:

Listing 2. MyAppWidgetSet.gwt.xml skonfigurowany dla ICEPush

<module><inherits name="com.vaadin.terminal.gwt.DefaultWidgetSet"></inherits><inherits name="org.icepush.gwt.ICEpush"></inherits><inherits name="org.vaadin.artur.icepush.IcepushaddonWidgetset"></inherits><set-property name="user.agent" value="gecko"></set-property></module>

Ostatni wpis należy usunąć gdy chcemy budować aplikację produkcyjnie. Przy testowaniu lokalnym znacznie przyspiesza pracę.

Czyli podsumowując te dwa etapy. Mamy projekt Vaadin z dodatkowymi modułami, które go pom.xml wygląda tak:

Listing 3. pom.xml skonfigurowany dla projektu

<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>pl.koziolekweb.vaadin.codecompiler</groupid><artifactid>code-compiler</artifactid><packaging>war</packaging><version>0.1</version><name>Vaadin Web Application</name><properties><project.build.sourceencoding>UTF-8</project.build.sourceencoding><spring.version>3.0.3.RELEASE</spring.version></properties><build><plugins><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-compiler-plugin</artifactid><configuration><source>1.6</source><target>1.6</target></configuration></plugin><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-surefire-plugin</artifactid><version>2.6</version><configuration><suitexmlfiles><suitexmlfile>src/test/testng.xml</suitexmlfile></suitexmlfiles></configuration></plugin><plugin><groupid>org.apache.maven.plugins</groupid><artifactid>maven-eclipse-plugin</artifactid><version>2.8</version><configuration><downloadsources>true</downloadsources><downloadjavadoc>true</downloadjavadoc></configuration><executions><execution><id>eclipse</id><phase>clean</phase><goals><goal>clean</goal><goal>eclipse</goal></goals></execution></executions></plugin><plugin><groupid>org.mortbay.jetty</groupid><artifactid>maven-jetty-plugin</artifactid><version>6.1.24</version><configuration><stopport>9966</stopport><stopkey>code-compiler</stopkey><scanintervalseconds>0</scanintervalseconds><webappconfig><contextpath>/code-compiler</contextpath><baseresource implementation="org.mortbay.resource.ResourceCollection"><resourcesascsv>src/main/webapp,${project.build.directory}/${project.build.finalName}</resourcesascsv></baseresource></webappconfig></configuration></plugin><plugin><groupid>org.codehaus.mojo</groupid><artifactid>gwt-maven-plugin</artifactid><version>1.3-SNAPSHOT</version><configuration><webappdirectory>${project.build.directory}/${project.build.finalName}/VAADIN/widgetsets</webappdirectory><extrajvmargs>-Xmx512M -Xss1024k</extrajvmargs><runtarget>clean</runtarget><hostedwebapp>${project.build.directory}/${project.build.finalName}</hostedwebapp><noserver>true</noserver><port>8080</port><soyc>false</soyc></configuration><executions><execution><goals><goal>resources</goal><goal>compile</goal></goals></execution></executions></plugin><plugin><groupid>com.vaadin</groupid><artifactid>vaadin-maven-plugin</artifactid><version>1.0.1</version><executions><execution><goals><goal>update-widgetset</goal></goals></execution></executions></plugin></plugins></build><repositories><repository><id>vaadin-snapshots</id><url>http://oss.sonatype.org/content/repositories/vaadin-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository><repository><id>vaadin-addons</id><url>http://maven.vaadin.com/vaadin-addons</url></repository><repository><id>jboss</id><url>http://repository.jboss.org/maven2</url></repository></repositories><dependencies><dependency><groupid>com.vaadin</groupid><artifactid>vaadin</artifactid><version>6.4.7</version></dependency><dependency><groupid>org.vaadin.addons</groupid><artifactid>icepush</artifactid><version>0.2.0</version></dependency><dependency><groupid>org.icepush</groupid><artifactid>icepush</artifactid><version>2.0</version></dependency><dependency><groupid>org.icepush</groupid><artifactid>icepush-gwt</artifactid><version>2.0</version></dependency><dependency><groupid>com.google.gwt</groupid><artifactid>gwt-user</artifactid><version>2.0.4</version><scope>provided</scope></dependency><dependency><groupid>axis</groupid><artifactid>axis</artifactid><version>1.4</version></dependency><dependency><groupid>org.hibernate.java-persistence</groupid><artifactid>jpa-api</artifactid><version>2.0-cr-1</version></dependency><dependency><groupid>org.hibernate</groupid><artifactid>hibernate-entitymanager</artifactid><version>3.5.0-Beta-2</version></dependency><dependency><groupid>postgresql</groupid><artifactid>postgresql</artifactid><version>8.4-702.jdbc4</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-core</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-web</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-beans</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-context</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-aop</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-context-support</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-tx</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-orm</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-jdbc</artifactid><version>${spring.version}</version></dependency><dependency><groupid>org.springframework</groupid><artifactid>spring-test</artifactid><version>${spring.version}</version></dependency><dependency><groupid>javax.servlet</groupid><artifactid>servlet-api</artifactid><version>2.5</version></dependency><dependency><groupid>org.slf4j</groupid><artifactid>slf4j-api</artifactid><version>1.4.2</version></dependency><dependency><groupid>org.slf4j</groupid><artifactid>slf4j-log4j12</artifactid><version>1.4.2</version></dependency><dependency><groupid>org.testng</groupid><artifactid>testng</artifactid><version>LATEST</version><scope>test</scope></dependency><dependency><groupid>org.mockito</groupid><artifactid>mockito-all</artifactid><version>1.8.1</version><scope>test</scope></dependency></dependencies></project>

Podstawowa konfiguracja Springa i JPA

Przejdźmy teraz do konfiguracji Springa i JPA. Na konfigurację składa się kilka plików. Jednak zanim je pokażę to muszę omówić to jak wygląda classpath w obecnym stanie aplikacji. Otóż jeżeli chcemy prawidłowo skonfigurować aplikację do współpracy z JPA to będziemy musieli stworzyć plik META-INF/persistance.xml. Problem tkwi w tym, gdzie jest prawidłowe położenie katalogu META-INF. Okazuje się, że w musi się on znajdować w ${project.base}/src/main/webapp/WEB-INF/classes/META-INF/. Można to obejść odpowiednio konfigurując resource-plugin. Ja jednak nie miałem na to siły. Oto lista plików konfiguracyjnych:

  • persistance.xml – konfiguracja JPA.
  • service.properties – konfiguracje różne, których nie chcemy w pliku springa.
  • spring.xml – konfiguracja springa.
  • log4j.xml – konfiguracja log4j.

Jadąc po kolei.

persistance.xml

W wersji dla Postgresa. Pamiętajcie by mieć już założoną bazę danyc.

Listing 4. persistance.xml wersja postgresowa

<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://java.sun.com/xml/ns/persistence"><persistence-unit name="codecompilerPU" transaction-type="RESOURCE_LOCAL"><properties><property name="hibernate.hbm2ddl.auto" value="update"></property><property name="hibernate.show_sql" value="true"></property><property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"></property><property name="hibernate.connection.password" value="******"></property><property name="hibernate.connection.username" value="******"></property></properties></persistence-unit></persistence>

Później do tego pliku będziemy dopisywać mapowane klasy. To już jednak twój problem by o tym pamiętać 😀

service.properties

Czyli różne ustawienia, które warto mieć gdzieś pod ręką, ale nie koniecznie w pliku springowym.

Listing 5. service.properties

ideone.user=******
ideone.pass=******

Mam tu na razie tylko informacje o danych dostępowych do serwisu.

spring.xml

Konfiguracja springa. Najważniejszy element układanki. Też będzie rósł, ale będę wam mówił co dodałem

Listing 6. spring.xml

<?xml version="1.0" encoding="UTF-8"??><beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd  http://www.springframework.org/schema/context  http://www.springframework.org/schema/context/spring-context-3.0.xsd  http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-3.0.xsd  http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"><property-placeholder location="classpath:service.properties"></property-placeholder><annotation-config></annotation-config><component-scan base-package="pl.koziolekweb.vaadin.codecompiler"></component-scan><annotation-driven transaction-manager="transactionManager"></annotation-driven><bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"></bean><bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource" p:driverclassname="org.postgresql.Driver" p:url="jdbc:postgresql://localhost:5432/codecompiler"></bean><bean class="org.springframework.orm.jpa.JpaTransactionManager" id="transactionManager" p:entitymanagerfactory-ref="entityManagerFactory"></bean><bean class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" id="entityManagerFactory" p:datasource-ref="dataSource" p:jpavendoradapter-ref="jpaAdapter"><property name="loadTimeWeaver"><bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"></bean></property><property name="persistenceUnitName" value="codecompilerPU"></property></bean><bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" id="jpaAdapter"></bean></beans>

Na razie tyle wystarczy.

log4j.xml

Wbrew pozorom istotny plik. Jak coś nie bangla i nie sypie błędami to właśnie tu można poszukać przyczyny.

Listing 7. log4j.xml wersja postgresowa

<?xml version="1.0" encoding="UTF-8" ??><configuration xmlns:log4j="http://jakarta.apache.org/log4j/"><appender class="org.apache.log4j.ConsoleAppender" name="console"><param name="Target" value="System.out"></param><layout class="org.apache.log4j.PatternLayout"><param name="ConversionPattern" value="%-5p %c{1} - %m%n"></param></layout></appender><root><priority value="info"></priority><appender-ref ref="console"></appender-ref></root><logger name="org.springframework.web.context.ContextLoader"><level value="info"></level><appender-ref ref="console"></appender-ref></logger></configuration>

Wygenerowanie klienta WS za pomocą Axis

Zrób to w Eclipse. W zasadzie tyle. Adres pliku wsdl znajdziesz w dokumentacji lub jak sam go tworzysz to wiesz gdzie jest.

Posprzątanie wygenerowanych klas i ich konfiguracja jako beanów Springa

Wygenerowane klasy mają zapewne brzytkie nazwy i jeszcze gorsze pakiety. Generalnie usuwamy podkreślenia z nazw, a całosć przenosimy do jakiegoś rozsądnego pakietu. Kolejnym etapem jest dopisanie konfiguracji do pliku spring.xml:

Listing 8. Konfiguracja API Ideone w springu

	
	<bean class="pl.koziolekweb.vaadin.codecompiler.data.ServiceUser" id="ideoneServiceUser"><constructor-arg value="${ideone.user}"></constructor-arg><constructor-arg value="${ideone.pass}"></constructor-arg></bean><bean class="pl.koziolekweb.vaadin.codecompiler.com.ideone.api.IdeoneServiceServiceLocator" id="ideoneServicePortFactory"></bean><bean class="pl.koziolekweb.vaadin.codecompiler.com.ideone.api.IdeoneServicePort" factory-bean="ideoneServicePortFactory" factory-method="getIdeoneServicePort" id="ideoneServicePort"></bean>

Oczywiście pytanie jak wygląda ServiceUser:

Listing 8. ServiceUser dane do logowania

package pl.koziolekweb.vaadin.codecompiler.data;


public class ServiceUser {

	public final String user;
	public final String pass;

	public ServiceUser(String user, String pass) {
		super();
		this.user = user;
		this.pass = pass;
	}

	public String getUser() {
		return user;
	}

	public String getPass() {
		return pass;
	}

}

No i mamy konfigurację…

Optymalizacja za pomocą Springa

Drugą rzeczą jaką się dziś zajmiemy jest „optymalizacja” aplikacji Vaadin za pomocą Springa. Przez optymalizację rozumiem tu ograniczenie ilości pamięci zużywanej przez Vaadin.
Na początek musimy przypomnieć sobie informacje o tym jak Spring tworzy instancje poszczególnych ziaren. Jedyne co nas interesuje to informacja, że domyślnie każde ziarno jest singletonem. Ta informacja jest ważna z kilku powodów. Po pierwsze musimy pamiętać, że spring nie wie, że pracuje w trybie web oraz, że warto by było gdyby niektóre komponenty dostarczał świeże. Samodzielnie musimy zagwarantować takie zachowanie poprzez odpowiednią konfigurację. Po drugie skoro wiemy, że komponenty GUI powinny być dostarczane na świeżo musimy zobaczyć, które z nich mogą być współdzielone pomiędzy użytkownikami. Zazwyczaj są to komponenty statyczne, których treść nie jest edytowana i nie zależy od stanu sesji użytkownika. Na przykład stopka. Po trzecie należy wiedzieć i pamiętać, że Vaadin przechowuje stan ekranu po stronie serwera! Zatem jeżeli 100 użytkowników otworzy 100 okien (sesji http) to otrzyma np. 100 obiektów stopki. Niby nic, ale dużo. Po stronie serwera będą leżeć identyczne, niemodyfikowalne obiekty w dużych ilościach. Zatem można spokojnie zamienić je na jeden współedzielony obiekt. Spring zadba by nie stała mu się krzywda np. usunięcie z pamięci.

Przykład będzie w kolejnej części w której połączymy się z WS, wywołamy testową metodę oraz zaczniemy budować UI.