Umieszczenie zależności jar do własnego repozytorium z linii poleceń

W jednym z poprzednich wpisów pokazaliśmy jak zdeployować stworzoną zależność po skompilowaniu projektu. Dzisiejszy wpis poświęcony jest umieszczaniu zależności w repozytorium przy pomocy linii komend.
Pomimo najszczerszych chęci wykonanie tego zadania zupełnie z linii komend jest niemożliwe. Należy stworzyć pom.xml o następującej treści:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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>com.example</groupId>
   <artifactId>webdav-deploy-pom</artifactId>
   <packaging>pom</packaging>
   <version>1</version>
   <name>Deploy</name>
   <build>
      <extensions>
         <extension>
            <groupId>org.apache.maven.wagon</groupId>
            <artifactId>wagon-webdav</artifactId>
            <version>1.0-beta-2</version>
         </extension>
      </extensions>
   </build>
 
</project>

Będzie on dodawał obsługę pluginu służącego do wysyłania zależności na zdalny zasób.
W pliku settings.xml, który znajduje się w domyślnym folderze repozytorium .m2 należy dodać wpis, który pozwoli na autentyfikację do repozytorium, na którym będzie umieszczana zależność:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<settings
   xmlns="http://maven.apache.org/SETTINGS/1.0.0"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
    <servers>
        <server>
            <id>xperios</id>
            <username>your-google-id</username>
            <password>your-google-password</password>
            <filePermissions>775</filePermissions>
            <directoryPermissions>775</directoryPermissions>
        </server>
        <server>
            <id>xperios-snapshot</id>
            <username>your-google-id</username>
            <password>your-google-password</password>
            <filePermissions>775</filePermissions>
            <directoryPermissions>775</directoryPermissions>
        </server>
    </servers>
</settings>

Od tego momentu z folderu gdzie znajduje się zasób można wykonać polecenie:

mvn deploy:deploy-file \
-Durl=dav:https://xperios.googlecode.com/svn/maven-repo\
-DrepositoryId=xperios-maven-repository \
-DgroupId=pl.xperios \
-DartifactId=sample-project \
-Dpackaging=jar \
-Dfile=xperios-sample-project.jar \
-Dversion=1.0 \
-DgeneratePom=true

Zostanie dodana zależność do repozytorium znajdującym się pod adresem: https://xperios.googlecode.com/svn/maven-repo o grupie pl.xperios, artefakcie sample-project i wersji 1.0. Należy pamiętać aby identyfikator repositoryId zgadzał się z identyfikatorem w pliku settings.xml.

Szablony w Netbeans

Podczas pracy z różnymi bibliotekami bywa, że pewne czynności są powtarzane nader często. Popularne biblioteki posiadają rozszerzenia, bądź natywne wsparcie ze strony IDE jak np. kreatory.
Niestety nie wszystkie biblioteki posiadają tak ogromne wsparcie jak choćby Spring czy Hibernate, co nie oznacza że są gorsze.
Programista jako użytkownik danej biblioteki pomimo braku wsparcia może jednak sam opracować pewne wzorce, które powtarzają się w trakcie pracy i je zautomatyzować poprzez makra czy też szablony. W dzisiejszym wpisie poświęcimy trochę czasu aby zautomatyzować tworzenie getterów i setterów dla modelu wykorzystywanego przez GXT.
Struktura modelu GXT wygląda nieco inaczej niż zwykłe POJO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package pl.bnsit.rssreader.shared;

import com.extjs.gxt.ui.client.data.BeanModel;

public class ExampleModelModel extends BeanModel{
   
    public static final String _NAME = "name";
    public static final String _URL = "url";
    public static final String _PROMOTED = "promoted";
   
    public String getName() {
        return get(_NAME);
    }

    public void setName(String name) {
        set(_NAME, name);
    }

    public String getUrl() {
        return get(_URL);
    }

    public void setUrl(String url) {
        set(_URL, url);
    }

    public Boolean getPromoted() {
        return get(_PROMOTED);
    }

    public void setPromoted(Boolean promoted) {
        set(_PROMOTED, promoted);
    }
   
}

Jak widać model ten posiada gettery i settery, ale różniące się sposobem przetrzymywania zmiennych. aby stworzyć powyższy plik ręcznie zajęłoby to programiście sporo czasu a przecież o to w pracy nie chodzi.
Z pomocą przychodzą nam szablony, dzięki którym za pomocą pewnej sekwencji znaków można wygenerować szablon, w którym należy uzupełnić elementy, które wyróżniają dany blok kodu.
Szablon stworzymy dla popularnego IDE – Netbeans, który posiada edytor szablonów w Tools->Options->Editor->Code Templates.
Utwórzmy nowy szablon, który wywołamy za pomocą sekwencji:

gs

i o treści:

1
2
3
4
5
6
7
8
9
    public static final String _Property = "property_id";
   
    public String getProperty() {
        return get(_Property);
    }

    public void setProperty(String property_id) {
        set(_Property, property_id);
    }

Po zapisaniu można przetestować dziaÅ‚anie wpisujÄ…c w edytorze klasy gs a nastÄ™pnie wcisnąć klawisz TAB – tekst zapisany automatycznie siÄ™ generuje. Nie jest to jednak zadowalajÄ…cy nas rezultat ponieważ pomimo wygenerowanego tekstu nazwy oraz typy należy poprawić. Aby mieć wpÅ‚yw na elementy szablonu należy przedstawić elementy rozszerzajÄ…ce szablony:

  • ${cursor} – definiuje pozycjÄ™ karetki kursora po wygenerowaniu kodu,
  • ${selection} – definuje pozycjÄ™ wklejenia tekstu, który zostaÅ‚ zaznaczony; użycie tego znacznika spowoduje pojawianie siÄ™ tego szablonu jedynie po zaznaczeniu pewnego fragmentu tekstu,
  • ${ELEM newVarName default=”Property”} – definiuje nowÄ… zmiennÄ… ELEM o domyÅ›lnej wartoÅ›ci Property
  • ${DEFTYPE leftSideType default=”Object”} – definiuje nowy zwracany typ danych w zmiennej DEFTYPE o domyÅ›lnej klasie Object
  • ${INITTYPE rightSideType default=”Object”} – definiuje nowy inicjalizowany typ danych w zmiennej INITTYPE o domyÅ›lnej klasie Object

Powyższe zmienne to tylko dobry początek do tego aby zapoznać się z szerszą gamą typów, które można wykorzystywać w szablonach a w przypadku Netbeans-a jest to wspaniała kolekcja, która pozwala na tworzenie genialnych szablonów.
Ostatecznie dynamiczny szablon ma postać:

1
2
3
4
5
6
7
8
9
    public static final String _${ELEM newVarName default="Property"} = "${ID newVarName default="id"}";
   
    public ${TYPE lefttSideType default="Object"} get${ELEM}() {
        return get(_${ELEM});
    }

    public void set${ELEM}(${TYPE} _input) {
        set(_${ELEM}, _input);
    }

Szablon wymaga do uzupełnienia:

  • nazwy pseudo pola,
  • identyfiakatora pola,
  • typu pola,

po których można się przełączać za pomocą klawisza TAB.

Aktualizacja deskryptora wdrożenia web.xml

Tworząc nowy projekt czy to za pomocą kreatora dołączonego do IDE czy za pomocą archetypu Maven zdarza się, że utworzona wersja deskryptora wdrożenia jest dość nieaktualna.
Pracując z Netbeans chcąc dodać nowy servlet jeżeli deskryptor będzie w wersji 2.3 bądź starszej, zostaniemy poinformowani komunikatem, że nie jest to możliwe:

W tym celu należy w pliku web.xml skasować dotychczasowy schemat walidacji DTD:

1
2
3
<!DOCTYPE web-app
   PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
   "http://java.sun.com/dtd/web-app_2_3.dtd">

a następnie dodać nowy schemat walidacji XSD w głównym elemencie pliku xml:

1
2
3
4
5
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:web="http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
        version="2.4">

Od tej pory można bez przeszkód dodawać servlety przez wspomniany kreator.

Maven + Hibernate + Annotation + Derby

W dzisiejszym poÅ›cie zaprezentujemy jak stworzyć projekt, który może stanowić wejÅ›cie dla przyszÅ‚ych projektów wykorzystujÄ…cych Hibernate oraz Mavena. Do zamockowania bazy danych posÅ‚użymy siÄ™ wbudowanÄ… (embeded) bazÄ… danych – Derby.

Tworzenie nowego projektu

Nalezy utworzyć nowy projekt za pomoca polecenia:

mvn -DarchetypeGroupId=org.apache.maven.archetypes
-DarchetypeArtifactId=maven-archetype-quickstart
-DarchetypeVersion=1.1
-DarchetypeRepository=http://repo1.maven.org/maven2
-DgroupId=pl.xperios
-DartifactId=HibernateMavenStarter
-Dversion=1.0-SNAPSHOT
-Dpackage=pl.xperios.HibernateMavenStarter
-Dbasedir=C:\\Projects\\Starters
-Darchetype.interactive=false –batch-mode archetype:generate

Konfiguracja zależności

Po stworzeniu struktury projektu należy dodać zależności Hibernate. Ponieważ nie występują w repozytorium centralnym najpierw należy dodać odpowiednie repozytoria do pliku pom.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    <repositories>
        <repository>
            <id>sonatype-snapshots</id>
            <name>Sonatype Snapshots</name>
            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
        <repository>
            <url>http://download.java.net/maven/2/</url>
            <id>download.java.net</id>
            <layout>default</layout>
            <name>Hibernate support</name>
        </repository>
    </repositories>

Od tej pory do projektu możemy dołączać również zależności, które nie znajdują się w repozytorium centralnym:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.5.6</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate</artifactId>
            <version>3.2.5.ga</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>3.3.2.GA</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>4.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>javax.sql</groupId>
            <artifactId>jdbc-stdext</artifactId>
            <version>2.0</version>
        </dependency>
        <dependency>
            <groupId>javax.transaction</groupId>
            <artifactId>jta</artifactId>
            <version>1.0.1B</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>ejb3-persistence</artifactId>
            <version>1.0.1.GA</version>
        </dependency>
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derbyclient</artifactId>
            <version>10.8.1.2</version>
        </dependency>
    </dependencies>

Po zapisaniu projektu i pierwszej kompilacji zostaną pobrane zależności do lokalnego repozytorium. W tym celu należy wydać polecenie:

mvn clean install

Klasa pomocnicza HibernateUtil

Pracę zaczynamy od stworzenia specjalnej klasy narzędziowej, która inicjuje konfigurację oraz pozwala na dostęp do usług Hibernate poprzez metody statyczne:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package pl.xperios.hibernatestarter;

import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.SessionFactory;

public class HibernateUtil {

private static final SessionFactory sessionFactory;

static {
try {
sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
} catch (Throwable ex) {
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}

public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}

Konfiguracja Hibernate

Powyższa klasa inicjuje konfigurację opartą na anotacjach a następnie odczytuje konfiguracje z jednego z możliwych źródeł. My posłużymy się plikiem konfiguracyjnym zawartym w pliku xml. W tym celu należy stworzyć plik hibernate.cfg.xml w folderze src/main/resources o następującej treści:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
  <session-factory>
    <property name="hibernate.dialect">org.hibernate.dialect.DerbyDialect</property>
    <property name="hibernate.connection.driver_class">org.apache.derby.jdbc.ClientDriver</property>
    <property name="hibernate.connection.url">jdbc:derby://localhost:1527/sample</property>
    <property name="hibernate.connection.username">app</property>
    <property name="hibernate.connection.password">app</property>
    <property name="hibernate.show_sql">true</property>
    <property name="hibernate.hbm2ddl.auto">create-drop</property>
    <property name="hibernate.current_session_context_class">thread</property>
    <property name="hibernate.transaction.auto_close_session">true</property>
  </session-factory>
</hibernate-configuration>

Powyższy plik definiuje między innymi:

  • połączenie z bazÄ… danych:
  • sterownik (hibernate.connection.driver_class)
  • url (hibernate.connection.url)
  • login (hibernate.connection.username)
  • hasÅ‚o (hibernate.connection.password)
  • dialekt (hibernate.dialect)
  • pokazywanie w konsoli wykonywanych zapytaÅ„ do bazy (hibernate.show_sql)
  • kontekst aplikacji (hibernate.current_session_context_class)
  • automatyczne zamykanie transakcji (hibernate.transaction.auto_close_session), które upraszcza obsÅ‚ugÄ™ transakcji
  • strategia zarzÄ…dzania strukturÄ… w bazie danych (hibernate.hbm2ddl.auto)
  • Tworzenie pierwszej encji

    W celu przetestowania poprawności konfiguracji stworzymy i zarejestrujemy prostą encję użytkownika naszej aplikacji. W tym celu stworzymy prostą klasę encji:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    package pl.xperios.hibernatestarter.model;

    import java.io.Serializable;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;

    @Entity
    public class UserData implements Serializable {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        Long userId;
        String name;
        String fullName;

        public String getFullName() {
            return fullName;
        }

        public void setFullName(String fullName) {
            this.fullName = fullName;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Long getUserId() {
            return userId;
        }

        public void setUserId(Long userId) {
            this.userId = userId;
        }

        @Override
        public String toString() {
            return "UserData{" + "userId=" + userId + ", name=" + name + ", fullName=" + fullName + '}';
        }
    }

    Powyższa encja została oznaczona za pomocą anotacji @Entity, dzięki czemu Hibernate będzie stworzononą klasę traktował jako encję.
    niektóre właściwości również posiadają anotacje i tak pole userId zostało opatrzone anotacją ID, które przekłada się na to że pole to traktowane jest jako klucz główny wraz ze strategią generowania identyfikatorów w sposób automatyczny.

    Powyższą encję należy zarejestrować w pliku konfiguracyjnym Hibernate (hibernate.cfg.xml):

    1
    2
    3
    4
    5
    ...
        <property name="hibernate.transaction.auto_close_session">true</property>
        <mapping class="pl.xperios.hibernatestarter.model.UserData"/>
      </session-factory>
    ...

    Test

    Mając wszystkie elementy tworzymy test, który doda parę rekordów do bazy a następnie pobierze je i wyświetli w konsoli:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    package pl.xperios.hibernatestarter;

    import java.util.List;
    import junit.framework.Test;
    import junit.framework.TestCase;
    import junit.framework.TestSuite;
    import org.hibernate.Transaction;
    import org.hibernate.classic.Session;
    import pl.xperios.hibernatestarter.HibernateUtil;
    import pl.xperios.hibernatestarter.model.UserData;

    public class AppTest extends TestCase {

        public AppTest(String testName) {
            super(testName);
        }

        public static Test suite() {
            return new TestSuite(AppTest.class);
        }

        public void testApp() {

            Session session = HibernateUtil.getSessionFactory().getCurrentSession();
            Transaction transaction = session.beginTransaction();
            UserData data = new UserData();
            data.setFullName("FullName1");
            data.setName("Name1");
            session.save(data);
            data = new UserData();
            data.setFullName("FullName2");
            data.setName("Name3");
            session.save(data);
            transaction.commit();


            session = HibernateUtil.getSessionFactory().getCurrentSession();
            transaction = session.beginTransaction();
            List output = session.createCriteria(UserData.class).list();
            System.out.println("Size: " + output.size());
            System.out.println("Size: " + output);
            transaction.commit();
        }
    }

    W konsoli powinno zostać wyświetlony następujący wynik:

    Hibernate: insert into UserData (fullName, name, userId) values (?, ?, ?)
    Hibernate: insert into UserData (fullName, name, userId) values (?, ?, ?)
    Hibernate: select this_.userId as userId0_0_, this_.fullName as fullName0_0_, this_.name as name0_0_ from UserData this_
    Size: 2
    Size: [UserData{userId=1, name=Name1, fullName=FullName1}, UserData{userId=2, name=Name3, fullName=FullName2}]

    Lekki serwer Jetty

    W trakcie tworzenia web aplikacji przychodzi moment kiedy należy go umieścić na jakimś serwerze. Do dyspozycji mamy naprawdę szeroką pulę począwszy od lekkiego Tomcata po serwery aplikacji takie jak GlassFish czy JBoss.
    Dzisiaj zaprezentujemy malutki i zgrabniutki kontener serwletów Jetty. Jego główną zaletą nad pozostałymi jest banalna konfiguracja oraz szybkość uruchamiania.
    Konfiguracja całości w projekcie budowanym za pomocą Mavena sprowadza się do dodania następującego plugina w pliku pom.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
          <plugin>
            <groupId>org.mortbay.jetty</groupId>
            <artifactId>maven-jetty-plugin</artifactId>
            <configuration>
            <scanIntervalSeconds>10</scanIntervalSeconds>
              <contextPath>/</contextPath>
            </configuration>
          </plugin>
        </plugins>

    Uruchomienie serwera odbywa się za pomocą: jetty:run bądź jetty:run-exploded. Przed uruchomieniem nastąpi build projektu a następnie jego automatyczny deploy pod kontekstem /. Jeżeli wszystko zakończyło się powodzeniem aplikacja staje się dostępna pod adresem: localhost:8080.
    Bardzo przydatną funkcjonalnością w każdym serwerze jest hot deploy. Jest to możliwość serwera do automatycznego redeploya (ponownego umieszczenia projektu na serwerze) po wprowadzeniu zmian w kodzie źródłowym. Co pewien czas skanowany jest kod i jeżeli ten się zmienił to wówczas następuje redeploy.

    Tworząc typowy projekt wszystko odbywa się automatycznie w interwale podanym w parametrze scanIntervalSeconds, jednak tworząc projekt GWT częścią efektu wyjściowego jest skompilowany do JavaScript-u kod kliencki, dlatego aby zobaczyć zmiany należy najlepiej skompilować cały projekt od nowa. Zwykle w tym celu wykonuje się polecenie

    1
    mvn clean install

    ale to nie zadziała, gdy serwer jest uruchomiony, ponieważ serwer tworzy podfolder work w folderze target, który jest zablokowany i każda próba zakończy się niepowodzeniem.
    Aby redeployować projekt GWT należy uruchomić go poleceniem:

    1
    mvn jetty:run-exploded

    a następnie jeżeli chcemy odświeżyć część kliencką wykonujemy kompilację bez kasowania folderu target:

    1
    mvn install

    W trakcie kompilacji co 10 sekund serwer będzie deployował projekt a po zakończeniu kompilacji można odświeżyć stronę z wprowadzonymi zmianami.

    Zautomatyzowane umieszczanie (deploy) projektu J2EE na serwerze Tomcat 7.X za pomocÄ… maven

    W pewnym punkcie pracy z projektem J2EE, (projekt webowy) dochodzimy do punktu, w którym przychodzi potrzeba umieszczenia go na serwerze czy to kontenerze aplikacji. Po umieszczeniu na serwerze można przetestować jego działanie ale również uruchomić np testy integracyjne.

    Ponieważ takich serwerów może istnieć wiele (lokalny, zdalny, klient, …) poszczególne warianty umieszcza siÄ™ w oddzielnych profilach.

    Zdefiniujmy profil, który będzie wykorzystywał plugin cargo, który bedzie łączył się ze zdalnym serwerem tomcat 7.x:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <profile>
    <id>tomcat-remote</id>
    <build>
    <plugins>
    <plugin>
    <groupId>org.codehaus.cargo</groupId>
    <artifactId>cargo-maven2-plugin</artifactId>
    <version>1.1.3</version>
    <configuration>
    <container>
    <containerId>tomcat7x</containerId>
    <type>remote</type>
    </container>
    <configuration>
    <type>runtime</type>
    <properties>
    <cargo.server.settings>my-tomcat-credentials</cargo.server.settings>
    <cargo.remote.uri>http://10.10.10.105:8080/manager/text</cargo.remote.uri>
    </properties>
    </configuration>
    </configuration>
    </plugin>
    </plugins>
    </build>
    </profile>

    Ostatnią rzeczą jaką należy dodać to dane, które pozwolą pluginowi na uwierzytelnienie na serwerze. Dane te wprowadzimy w pliku settings.xml, aby nieporządane osoby nie mogły poznać loginów i haseł:

    1
    2
    3
    4
    5
    6
    7
    <server>
    <id>my-tomcat-credentials</id>
    <configuration>
    <cargo.remote.username>mylogin</cargo.remote.username>
    <cargo.remote.password>mypassword</cargo.remote.password>
    </configuration>
    </server>

    Tu należy wyjaśnić kilka kwesti:
    Plugin cargo pozwala na zarzÄ…dzanie serwerem / kontenerem poprzez: uruchamianie, zatrzymywanie, undeploying, deploying i wiele innych
    Zarządzać można serwerem zainstalowanym lokalnie, zdalnym oraz można podać link do archiwum zip na stronie internetowej z dyrektywą aby plugin sam zainstalował serwer w podanej lokalizacji. Oczywiście projekt można deployować na serwerach zatrzymanych jak i uruchomionych.

    Url do tomcata w wersji 7.x należy podać do managera tekstowego. Aby móc zarządzać kontenerem należy użytkownikowi, za pomocą którego łączymy się z serwerem dodać uprawnienie manager-script. Aby to zrobić należy wyedytować plik tomcat-users.xml znajdujący się w folderze %TOMCAT_DIR%/conf/tomcat-users.xml a następnie dodać po przecinku w roles wskazanego użytkownika manager-script.

    Deploy pliku war następuje po wydaniu polecenia:

    mvn -Ptomcat-remote clean verify org.codehaus.cargo:cargo-maven2-plugin:deploy

    Po parametrze -P podajemy nazwę pluginu, który jest aktywowany podczas uruchomienia. Jeżeli nie zostanie wskazany domyślne parametry zostaną wykorzystane a wtedy na 90% zostanie rzucony wyjątek.
    Oczywiście zaprezentowany przykład zadziała jedynie gdy serwer jest uruchomiony i nie istnieje wcześniej zdeployowany aktualny projekt.

    Serwer należy uruchomić, przejść do managera aplikacji a następnie wykonać undeploy.

    Zautomatyzowane wydanie projektu w formie archiwum zip przy pomocy mavena

    Jednym z elementów cyklu życia projektu jest wydanie jego wersji. W zależności co tak na prawdę tworzymy efektem końcowym jest aplikacja gotowa do uruchomienia przez klienta, bądź biblioteka wykorzystywana w innych projektach. Oto czym się głównie charakteryzują:

    • aplikacja – posiada strukturÄ™ dostosowanÄ… do projektu (jednakowa nazwa artefaktu np.: aplikacja.jar, zależnoÅ›ci umieszczone w folderze lib, zasoby porozmieszczane w oddzielnych folderach np.: Pomoc, Img, Log, Raporty itp.), dokumentacja użytkownika, przykÅ‚adowe pliki konfiguracyjne, dołączone jedynie binaria
    • biblioteka – ustandaryzowana struktura wydania (dobra konwencja), dołączenie binariów, bardzo czÄ™sto nie dołącza siÄ™ zależnoÅ›ci, przykÅ‚ady, dokumentacja techniczna, strona site-ów wraz z wynikami testów, javadoc itp.

    Oczywiście powyższy opis może odbiegać nawet znacząco od realiów panujących w waszej firmie, niemniej chodzi o to, że istnieją znaczące różnice w sposobach wydawania wersji.
    W dniu dzisiejszym zajmiemy się tworzeniem archiwum zip, które będzie zawierało gotową do publikacji aplikację / bibliotekę. Do tego celu posłuży nam maven w wersji 3.0.3. Już samo stosowanie mavena wymusza w naszym projekcie usystematyzowaną strukturę. Dzięki temu łatwiej możemy otrzymać formę wynikową opracowaną i akceptowaną przez ogromne rzesze użytkowników.
    Pierwszym elementem jaki należy dodać odpowiedni plugin assembly:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     <plugin>
    <artifactId>maven-assembly-plugin</artifactId>
    <configuration>
    <descriptors>
    <descriptor>src/main/assembly/bin.xml</descriptor>
    </descriptors>
    <finalName>${artifactId}</finalName>
    </configuration>
    </plugin>

    Definiujemy w nim ścieżkę deskryptora, czyli pliku w którym określimy co zostanie dołączone do archiwum. Gol assembly:assembly uruchamiający pakowanie powinien zostać wywołany po fazie build dzięki czemu w folderze target będziemy mieli już stworzoną pełną strukturę z której będziemy dysponowali skompilowanymi plikami, które możemy dołączać do archiwum.

    Oczywiście następnym krokiem będzie stworzenie pliku src/main/assembly/bin.xml, który w naszym przypadku może wyglądać następująco:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <?xml version="1.0" encoding="UTF-8"?>
    <assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd">>
    <id>bin</id>
    <formats>
    <format>zip</format>
    </formats>
    <includeSiteDirectory>true</includeSiteDirectory>
    <fileSets>
    <fileSet>
    <includes>
    <include>Config.properties</include>
    <include>start.bat</include>
    </includes>
    </fileSet>
    <fileSet>
    <directory>target</directory>
    <outputDirectory>.</outputDirectory>
    <includes>
    <include>${artifactId}.jar</include>
    </includes>
    </fileSet>
    <fileSet>
    <directory>target/Pomoc</directory>
    <outputDirectory>Pomoc</outputDirectory>
    </fileSet>
    <fileSet>
    <directory>target/Fonts</directory>
    <outputDirectory>Fonts</outputDirectory>
    </fileSet>
    <fileSet>
    <directory>target/lib</directory>
    <outputDirectory>lib</outputDirectory>
    </fileSet>
    </fileSets>

    </assembly>

    Więcej na temat możliwości archiwizowania można znaleźć tu.

    Aby wygenerować plik zip należy wydać polecenie:

    clean install site:site assembly:assembly

    Jeżeli w pliku zip umieszczamy site to gol assembly musi zostać poprzedzony fazą generowania strony.

    Plik taki możemy umieścić na stronie firmowej, bądź na hostingu typu code.google.com.

    Dokumentacja techniczna projektu jako strona generowana przez Maven

    Jak wiadomo kolejnym etapem po, a jeszcze lepiej w trakcie realizacji projektu należy przedstawić klientowi dokumentację zarówno użytkownika jak i techniczną. Abstrahując zupełnie od dokumentacji użytkownika dokumentacja techniczna powinna zawierać przynajmniej takie podstawowe elementy jak:

    • javadoc,
    • FAQ,
    • opis użycia,
    • pokrycie kotu testami,
    • wyniki testów,
    • i wiele innych zależących od wymogów klientów, góry czy wÅ‚asnych wymagaÅ„.

    Oprócz części opisowej, którÄ… prowadzić programista musi samodzielnie, w wiÄ™kszoÅ›ci powyższych przypadków może wyrÄ™czyć nas maven. Dokumentacja generowana jest do postaci strony HTML, którÄ… później można udostÄ™pnić w internecie bÄ…dź intranecie podobnie jak robi to dosyć spora rzesza producentów oprogramowania w tym sam twórca mavena – apache.

    Definicja struktury strony

    Definicja struktury strony musi znajdować się w pliku: \src\site\site.xml i powinna mieć strukturę zgonie z dokumentacją:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    <?xml version="1.0" encoding="ISO-8859-1"?>
    <project name="Maven">

        <bannerLeft>
            <name>Maven</name>
            <src>http://maven.apache.org/images/apache-maven-project.png</src>
            <href>http://maven.apache.org/</href>
        </bannerLeft>
        <bannerRight>
            <src>http://maven.apache.org/images/maven-small.gif</src>
        </bannerRight>
        <body>
            <links>
                <item name="Project Name" href="http://www.project_name.company_name.com/" />
            </links>

            <menu name="Maven 2.0">
                <item name="Introduction" href="index.html"/>
                <item name="Download" href="download.html"/>
                <item name="Release Notes" href="release-notes.html" />
                <item name="General Information" href="about.html"/>
                <item name="For Maven 1.x Users" href="maven1.html"/>
                <item name="Road Map" href="roadmap.html" />
                <item name="FAQ" href="faq.html" />
            </menu>

            <menu ref="modules" />
            <menu ref="reports"/>

        </body>
    </project>

    Niestety od czasu mavena 2 wprowadzono reorganizację struktury projektu co zaowocowało zmianą filozofii wykorzystywania niektórych modułów w tym generowania site-ów. Tym sposobem dokumentacja przeznaczona dla wcześniejszych wersji, której jest ogromna ilość w internecie może powodować zamęt i błędy podczas próby generowania strony. W wersji 3.0.x należy dodać następujący plugin w pliku pom.xml:

    1
    2
    3
    4
    5
    6
    7
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.0-beta-2</version>
                </plugin>
            </plugins>

    Aby przetestować działanie pluginu należy wydać polecenie:

    clean:clean site:site

    Po uruchomieniu strony znajdującej się w target\site\index.html naszym oczom ukaże się strona jak na załączonym zrzucie:

    Strona HTML czyli site może zostać wygenerowany domyślnie z kilku typów źródeł danych takich jak:

    • apt - Almost Plain Text (strony statyczne)
    • xdoc (strony statyczne)
    • fml - FAQ

    oraz poprzez dostarczenie dodatkowych treści poprzez pluginy. Stronę taką można internacjonalizować oraz zmieniać jej wygląd poprzez skórki. Więcej informacji na temat pluginu oraz jego możliwości dostępnych jest na oficjalnej stronie wygenerowanej również przy pomocy mavena.

    Ostatnią rzeczą jaką będziemy chcieli zrobić to umieszczenie naszego site-a na serwerze, do którego będą mieli dostęp użytkownicy naszego projektu. W tym celu musimy do naszego pliku pom.xml dodać wpis gdzie chcemy ją umieścić:

    1
    2
    3
    4
    5
    6
        <distributionManagement>
            <site>
                <id>mycompany</id>
                <url>ftp://mycompany.com/public_html/site/my_product_site</url>
            </site>
        </distributionManagement>

    oraz należy stworzyć plik settings.xml obok pom.xml, w którym podamy dane niezbędne do logowania się przez protokół ftp:

    1
    2
    3
    4
    5
    6
    7
    8
    9
        <servers>
            <server>
                <id>mycompany</id>
                <username>username</username>
                <password>password</password>
                <filePermissions>775</filePermissions>
                <directoryPermissions>775</directoryPermissions>
            </server>
        </servers>

    Hasło niestety należy podać w postaci jawnej, dlatego nie należy pliku settings.xml wysyłać do repozytorium. Każdy użytkownik, który jest odpowiedzialny za publikację site-a powinien mieć zdefiniowany własny login i hasło poza udostępnionymi elementami.

    Oczywiście nie jest to wciąż najbezpieczniejsza metoda przechowywania hasła przed nieporządanym dostępem dlatego można hasła szyfrować za pomocą tej metody.

    Aby umieścić stronę na zdalnym serwerze należy wydać polecenie:

    site:deploy

    Własne repozytorium Maven na code.google.com

    Często tworząc nowy projekt (z reguły opensource) wykorzystujemy jako repozytorium popularny code.google.com. Aby podnieść walor biznesowy i jakość świadczonych usług normalnym jest częste wydawanie nowej wersji naszej aplikacji klientom / społeczności. Proces ten czasmi jest dosyć tendencyjny i uciążliwy. Powtarzające się procesy oczywiście dociekliwy programista jest w stanie sobie zautomatyzować w bardzo zwinny sposób.

    Dzisiaj pokażemy jak posiadając projekt na code.google.com stworzyć z niego własne repozytorium wykorzystując do tego mavena w wersji 3.0.3.

    plik pom.xml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <project>

    <repositories>
    <repository>
    <id>google</id>
    <name>[Project Name] Maven Repository</name>
    <url>https://[project-name].googlecode.com/svn/maven-repo/releases</url>
    </repository>
    </repositories>
    <distributionManagement>
    <repository>
    <id>google</id>
    <name>Maven Repository for Config Processor Plugin (releases)</name>
    <url>dav:https://[project-name].googlecode.com/svn/maven-repo/releases</url>
    <uniqueVersion>false</uniqueVersion>
    </repository>
    <snapshotRepository>
    <id>google</id>
    <name>Maven Repository for Config Processor Plugin (snapshots)</name>
    <url>dav:https://[project-name].googlecode.com/svn/maven-repo/snapshots</url>
    <uniqueVersion>false</uniqueVersion>
    </snapshotRepository>
    </distributionManagement>

    <build>
    <extensions>
    <extension>
    <groupId>org.apache.maven.wagon</groupId>
    <artifactId>wagon-webdav</artifactId>
    <version>1.0-beta-2</version>
    </extension>
    </extensions>
    </build>

    i dodatkowo w pliku settings.xml:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <servers>
    <server>
    <id>google</id>
    <username>[my-login]</username>
    <password>[my password]</password>
    <filePermissions>775</filePermissions>
    <directoryPermissions>775</directoryPermissions>
    </server>
    </servers>

     

    Objaśnienia poszczególnych elementów w pliku pom.xml:

    • repositories -> repository – wskazuje miejsce wyszukiwania artefaktów umieszczonych z innych projektów
    • distributionManagement -> repository – wskazuje miejsce, gdzie nowo skompilowane wersje zostanÄ… uploadowane w fazie deploy; w tym miejscu pojawiÄ… siÄ™ jedynie wersje, które nie sÄ… opatrzone suffixem SNAPSHOT w wersji projektu np: <version>1.0.1</version>
    • distributionManagement -> snapshotRepository – wskazuje miejsce, gdzie nowo skompilowane wersje zostanÄ… uploadowane w fazie deploy; w tym miejscu pojawiÄ… siÄ™ jedynie wersje, które sÄ… opatrzone suffixem SNAPSHOT w wersji projektu np: <version>1.0.1-SNAPSHOT</version>
    • extension - dodany connector, który obsÅ‚uguje protokół webDav, który jest implementowany przez subversion wykorzystywany przez code.google.com

    Objaśnienia poszczególnych elementów w pliku settings.xml:

    • servers -> server – definiuje loginy i hasÅ‚a do konta dostÄ™powego z prawami zapisu do projektu

     

    Jeżeli występuje błąd 401 oznacza to błąd autoryzacji dla powyższych danych.

    Projekt zostanie uploadowany na serwer w fazie deploy dlatego aby przetestować nową funkcjonalność wydajemy polecenie:

    mvn clean install deploy:deploy

    Zamykanie wszystkich połączeń w aplikacji wykorzystującej C3P0

    W przypadku, gdy aplikacja do przechowywania danych wykorzystuje relacyjnÄ… bazÄ™ danych bardzo czÄ™sto siÄ™ zdarza, że korzysta z takich narzÄ™dzi wspomagajÄ…cych jak Hibernate czy MyBatis (okreÅ›lenie „narzÄ™dzie” jest tu celowe, ponieważ najczęściej o tej warstwie mówi siÄ™ skrótowo ORM, przy czym MyBatis ORM-em nie jest).

    Biblioteki te (jak i również inne) mogą wykorzystywać zewnętrzne mechanizmy transakcji, czy zarządzania źródłami danych i tu zmierzam do C3P0.

    C3p0 jest biblioteką, która wnosi warstwę obsługi wielu połączeń z bazą danych. W skrócie: gdy klient/usługa chce pobrać dane z bazy połączenie z bazą danych zostaje zajęte przez sesję klienta, które zwalniane jest dopiero po przetworzeniu i zwróceniu wyników. W przypadku, gdy wielu klientów chce naraz pobrać dane jeden użytkownik okupuje połączenie a pozostali czekają w kolejce dopóki się nie zwolni. Biorąc pod uwagę skalę, czyli duża ilość użytkowników korzysta z aplikacji, która wykonuje skomplikowane, powolne zapytania na dużej bazie danych może to być niewątpliwie spory problem.

    C3P0 pozwala na wprowadzenie wiÄ™kszej iloÅ›ci niezależnych połączeÅ„ do bazy – taka wielowÄ…tkowość dla wÄ…tków :-) .

    Więcej informacji na ten temat można znaleźć na stronie projektu: http://sourceforge.net/projects/c3p0/ bądź na stronach bibliotek wykorzystujących C3P0.

    Dzisiaj jednak zgodnie z tytułem zajmiemy się problemem, który może stanowić o stabilnej pracy naszej aplikacji. Gdy już przekonamy się do korzystania z biblioteki problem pojawia się podczas undeployingu na bazie Tomcat. Biblioteka nie zamyka i wyrejestrowuje nawiązanych połączeń. Podczas próby ponownego umieszczenia projektu na serwerze mogą pojawić się problemy, ponieważ niektóre klasy pozostają w pamięci. W ostateczności może nie dojść do poprawnego umieszczenia aplikacji. Od wersji 6.9 Tomcata została zaaplikowana usługa retrospekcji dla wybranych klas, co pozwala wyrejestrować je automatycznie przez kontener, ale z doświadczenia zauważyłem, że nie dzieje się to za każdym razem. Rozwiązaniem może być ręczne pozamykanie połączeń, które wywołamy w momencie gdy serwer jest zatrzymywany. Służy do tego listener który dodajemy w web.xml:

    1
    2
    3
    4
    5
    <listener>

    <listener-class>pl.xperios.rdk.server.startup.ServerStarter</listener-class>

    </listener>

    Poniżej znajduje się implementacja zamykania wszystkich połączeń dla C3p0, którą należy umieścić w listenerze Tomcata. Przykład:

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public abstract class MainServerModuleManager implements EventListener {

    @Override

    public void contextInitialized(ServletContextEvent servletContextEvent) {

    ...

    }

     

    @Override

    public void contextDestroyed(ServletContextEvent servletContextEvent) {

    try {

    connection = dataSource.getConnection();

    } catch (SQLException ex) {

    } finally {

    try {

    if (connection != null) {

    connection.close();

    }

    if (dataSource != null) {

    try {

    DataSources.destroy(dataSource);

    dataSource = null;

    } catch (Exception e) {

    }

    }

    } catch (SQLException sQLException) {

    XLog.error(sQLException);

    }

    }

    }

    }

     

    Kalendarz
    Maj 2012
    P W Åš C P S N
    « lut    
     123456
    78910111213
    14151617181920
    21222324252627
    28293031