Some of posts from this blog has been moved to dywicki.pl. You will be automatically redirected to new blog if you would submit comment.
New posts are published on dywicki.pl, this blog contains old content and it is not continued.

Niektóre posty z tego bloga zostały przeniesione do dywicki.pl. Zostaniesz automatycznie przekierowany jeśli bedzięsz chciał dodać komentarz.
Nowe posty sa publikowane na dywicki.pl, ten blog zawiera stare treści i nie jest kontynuowany.

Właśnie wyłapałem na Twitterze, że Spring 3.0 RC3 został wydany.

Dla tych, którzy chcieli by pobrać nową wersję do swojego projektu opartego o Mavena drobna informacja – repozytorium z tymi artefaktami znajduje się pod adresem http://maven.springframework.org/milestone. Miejmy nadzieję, że będzie to repozytorium które się nie zmieni po 3 miesiącach na inne.

W razie problemów – można zawsze skorzystać z repozytorium utrzymywanego na serwerze Code-House.

GWT, czyli Google Web Toolkit to nic innego jak zbiór komponentów, które można użyć podczas tworzenia aplikacji. GWT jest łatwe, kod tworzy się szybko i łatwo uruchamia chociażby z poziomu Mavena, jednakże największym problemem nie jest to jak stworzyć okienko, ale jak zorganizować projekt. W tym poście postaram się przedstawić kilka gotowych bibliotek, z którymi się zetknąłem podczas swych bojów z budowaniem sporej aplikacji.


Ilość implementacji MVC dla GWT jest dosyć duża a temat był poruszany na zagranicznych blogach już nie jeden raz, wystarczy zajrzeć na poniższe strony:

  • W marcu 2009 Matt Raible opisał swoje problemy z komponentami GXT oraz problematycznym MVC stworzonym przez autorów tejże biblioteki
  • W kwietniu 2009 pojawiło się pierwsze zestawienie implementacji MVC na blogu Supply Chain Technology
  • W maju 2009 na tym samym blogu pojawiła się aktualizacja z nowymi pozycjami.
  • Również w maju 2009 odbyła się konferencja Google IO na której temat developmentu GWT był szeroko i dokładnie omówiony.

Zacząłem nieśmiało, bo od stworzenia „szyny” którą mogły by się komunikować różne komponenty. Miała ona na celu zmniejszenie ilości bezpośrednich powiązań między elementami aplikacji. Pomogło, ale to nie było jeszcze to, co uporządkowało aplikację.

Drugie podejście mimo obaw wyniesionych z pierwszego linku padło na lightweight MVC z biblioteki GXT. Udało mi się stworzyć działającą aplikację, jednakże brak jasnego podziału co, kto i jak ma robić strasznie mi doskwierał. Problematyczna okazała się również próba stworzenia hierarchii kontrolerów, a zrobienie aplikacji od zera sprowadzało się tak na prawdę do skopiowania i przerobienia przykładu z Mail App. Podobnie jak Matt nigdy też nie wyłapałem różnicy między metodami Dispatcher.dispatch() oraz Dispatcher.forwardEvent().

Trzecie podejście do MVC zakrawało już szaleństwem. Po przejrzeniu dokumentacji GWTruts stwierdziłem, że zapowiada się ciekawie. Odseparowany widok i kontroler, API przypominające nieco Spring MVC, nieco XML do okraszenia całości wstrzykiwaniem zależności. Wszystko to jednak na nic, gdyż całość opiera się na mapowaniu adresów z przeglądarki do kontrolerów. Krzywe koniec końców okazało się też przekazywanie map z argumentami i w dalszym ciągu brak jakichkolwiek idei na zorganizowanie wszystkiego. A zapomniałbym – jeśli chcecie walidacji to musicie pogodzić się z tym że 1 kontroler to 1 klasa do sprawdzania danych. Szkoda że tego nie można „wstrzyknąć”.

Dobra, skoro nie GWTruts, nie GXT, i nie Event Bus to co? Krótki przegląd GWT-MVC skończył się odrzuceniem szkieletu. Skąd niby kontroler ma zajmować się tym gdzie ma wylądować widok? A tak właśnie się dzieje w tym szkielecie ponieważ kontroler wywołuje DOMPlacera by gdzieś umieścić widok. Jak by tych fanaberii było mało doszedł jeszcze interfejs Maskable który ma przykrywać elementy w trakcie renderowania. Dodajmy do tego przykłady które się sprowadzały do przycisków +2, -2 i inputa. Pytanie – co to ma do klasycznego MVC?

Tym oto sposobem dotarłem do ostatniego projektu, który mi zaimponował rozmiarem dokumentacji, spójną koncepcją i masą ograniczeń – Pure MVC. Projekt, który pierwotnie miał sporo do czynienia z Flashem, a który dzięki swojemu porządkowi został przepisany również na inne języki – min. PHP, Javę. Wśród portów nie zabrakło również wersji dla GWT. Nie muszę pisać jak bardzo mnie to ucieszyło, prawda? :-)
Całkiem duży przykład obrazuje powiązania pomiędzy komponentami oraz to jak powinny się komunikować:
Employee Admin Demo

Uzbrojony w ten zakres informacji stworzyłem pierwszą przykładową aplikację z ekranem logowania oraz jedną tabelką. Stopniowo go rozbudowując dotarłem do momentu w którym moja aplikacja w końcu przestała się kręcić wokół tego „które MVC” wybrać i zaczęła nabierać funkcjonalności biznesowej. Po długich bojach i masie straconego czasu, odradzam wszystkie inne implementacje Model View Controller dla GWT. Pure MVC jest po prostu najlepsze. :-)

Kilka dni temu trafiłem na informacje o tym, że firma Atlassian uruchomiła „promocję” w której wyprzedaje licencje startowe na swoje produkty za 10$. Tym oto sposobem za równowartość dobrego obuwia (niecałe 175 zł) wszedłem w posiadanie sześciu licencji na Crowd, Jira, GreenHopper, Confluence, Fisheye i Bamboo. W poście tym krótki opis instalacji na Tomcat 6.x pierwszych czterech.

Przygotowania

Uprzedzam, że instrukcja jest pisana z pamięci więc mogą pojawić się jakieś braki. W przypadku gdy instrukcja nie zadziała proszę o komentarz, a postaram się pomóc i uzupełnić wpis tak by był kompletny.
Aby ułatwić sobie życie i zaoszczędzić pamięć na serwerze wszystkie produkty będzie obsługiwała jedna instancja Tomcat i PostgreSQL. Niestety Fisheye i Bamboo są dostępne tylko w wersji standalone, stąd proces ich instalacji będzie nieco inny. Zanim ruszymy z całością pobieramy od producenta oprogramowanie.
Pobieranie Crowd
Przeskakujemy na zakładkę Linux i następnie klikamy w prawym górnym rogu i pobieramy wersję standalone. Wykonujemy polecenie

tar -xvzf atlassian-crowd-2.0.2.tar.gz

Po rozpakowaniu pojawią się następujące katalogi

  • apache-tomcat
  • client
  • crowd-openidclient-webapp
  • crowd-openidserver-webapp
  • crowd-webapp
  • demo-src
  • demo-webapp
  • etc

Będziemy potrzebować tylko dwóch – crowd-webapp oraz opcjonalnie openidserver. Dystrybucja standalone zawiera już Tomcata, zatem najpotrzebniejsze biblioteki weźmiemy już stąd. :) Kopiujemy biblioteki:

  • activation-1.1.jar
  • mail-1.4.jar
  • jta-1.0.1B.jar

Do katalogu lib naszego tomcata. Na Gentoo jest to /usr/share/tomcat-6/lib.
Pierwsze dwie biblioteki będą potrzebne do używania obiektu javax.mail.Session zapisanego w drzewie JNDI. Dodatkowo pobieramy driver JDBC dla bazy PostgreSQL. Należy pamiętać że wersja JDBC 4 jest przeznaczona do uruchamiania na Java 1.6. Ja ściągnąłem wersję 8.4-701.jdbc4. Mając te biblioteki przystępujemy do konfiguracji kontekstu Tomcata.
W pierwszym kroku dodajemy globalną sesję mailową którą będziemy współdzielić między aplikacjami. W ten sposób zmiana w jednym miejscu będzie skutkować „aktualizacją” konfiguracji wszystkich aplikacji. Edytujemy plik $TOMCAT_HOME/conf/server.xml. W sekcji GlobalNaminResources umieszczamy dodatkowy fragment:

  <GlobalNamingResources>
    <!-- Ten wpis jest domyślnie dostępny -->
    <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />

    <Resource name="mail/Session" auth="Container"
              type="javax.mail.Session"
              description="JavaMail Session"
              username="user"
              password="pass"
              mail.smtp.host="host"
              mail.smtp.auth="true"
              mail.user="user"
              mail.password="pass"
              mail.transport.protocol="smtp"
              />
  </GlobalNamingResources>

Konieczna będzie również zmiana sekcji Server:

    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" URIEncoding="UTF-8" /><!-- dodajemy URIEncoding -->

Atrybut URIEncoding będzie wymagany przez min. confluence i umożliwia przesyłanie krzaczków w adresie. Warto również zwiększyć limity pamięci Tomcata ponieważ na domyślnych długo nie pociągnie i prawdopodobnie już przy instalacji JIRA albo Confluence poleci OutOfMemory.

Drugi krok to ustawienie zmiennej crowd-home. W tym celu należy wyedytować plik crowd-webapp/WEB-INF/classes/crowd-init.properties.

crowd.home=/var/lib/tomcat-6/atlassian/crowd-home

W razie problemów można „zresetować” instalację usuwając folder home, czego oczywiście nie radzę robić. :) Trzeci krok to konfiguracja kontekstu Crowd.

<Context path="/crowd" docBase="/var/lib/tomcat-6/atlassian/crowd-webapp" debug="0">
    <Resource name="jdbc/CrowdDS" auth="Container" type="javax.sql.DataSource"
            username="user"
            password="pass"
            driverClassName="org.postgresql.Driver"
            validationQuery="select 1"
            url="jdbc:postgresql://localhost:5432/database"/>

    <ResourceLink name="mail/Session"
            global="mail/Session"
            type="javax.mail.Session" />
</Context>

W miejscu docBase należy podać folder w którym znajduje się crowd. Kopiujemy plik crowd.xml do katalogu $TOMCAT_HOME/conf/Catalina/localhost. Ok wygląda na to, że to wszystko co było potrzebne, zatem teraz restart Tomcata i powinno działać.. Powinno, ale nie działa. Problem, z którym męczyłem się bardzo długo to niewidoczny zasób jdbc/CrowdDS w procesie instalacji. O dziwo mail/Session był widoczny. Przy pierwszym podejściu poddałem się i skonfigurowałem połączenie w instalatorze podając te same dane co w konfiguracji kontekstu. Jednakże, po chwili zastanowienia i tych samych problemach z JIRA postanowiłem powtórzyć proces. Logi wskazywały że data source nie uruchamia się, ponieważ pojawiały się wpisy WARN o nieodnalezionej klasie. Poszperałem i dowiedziałem się że potrzebuję biblioteki tomcat-dbcp.jar, której oczywiście nie ma w dystrybucji. Całe szczęście znalazłem w serwisie Java2S. Po ściągnięciu zipa, należy go rozpakować i skopiować tomcat-dbcp.jar do $TOMCAT_HOME/lib.

Instalacja CROWD

Status aplikacji powinien być widoczny w managerze Tomcata. Jeśli wszystko jest w porządku, przechodzimy do instalatora. W pierwszej kolejności zostaniemy zapytani o licencję, następnie o połączenie z bazą danych i sesję mailową. Podajemy zatem co trzeba – ścieżka do data source to java:comp/env/jdbc/CrowdDS a do maila java:comp/env/mail/Session. Konfigurację grup i tak dalej pominę i odeślę do oficjalnej dokumentacji.

Instalacja JIRA

JIRA jako oprogramowanie bardziej wymagające będzie wymagała kilku dodatkowych bilbiotek. Przede wszystkim pobieramy jira-tomcat6-jars.zip. Całość należy wpakować do $TOMCAT_HOME/lib. Następnie pobieramy wersję WAR/EAR:
Pobieranie JIRA
Po rozpakowaniu

unzip atlassian-jira-enterprise-4.0.zip

przechodzimy do katalogu atlassian-jira-enterprise-4.0/edit-webapp/WEB-INF/classes. Musimy tutaj zmienić końcówkę pliku entityengine.xml:

    <datasource name="defaultDS" field-type-name="postgres72"
      schema-name="public"
      helper-class="org.ofbiz.core.entity.GenericHelperDAO"
      check-on-start="true"
      use-foreign-keys="false"
      use-foreign-key-indices="false"
      check-fks-on-start="false"
      check-fk-indices-on-start="false"
      add-missing-on-start="true"
      check-indices-on-start="true">
        <jndi-jdbc jndi-server-name="default" jndi-name="java:comp/env/jdbc/JiraDS"/>
<!-- Orion format: <jndi-jdbc jndi-server-name="default" jndi-name="jdbc/JiraDS"/> -->
<!-- JBoss format: <jndi-jdbc jndi-server-name="default" jndi-name="java:/DefaultDS"/> -->
<!-- Weblogic format: <jndi-jdbc jndi-server-name="default" jndi-name="JiraDS"/> -->
    </datasource>

Poszczególne atrybuty oznaczają:

  • field-type-name – rodzaj bazy danych, wartość postgres72 oznacza PostgreSQL 7.2 i nowsze.
  • schema-name – nazwa domyślnego schematu, trzeba przepisać z PUBLIC, które akceptuje HSQL na public
  • jndi-jdbc/jndi-name – pozostawiamy bez zmian, nasz data source będzie w lokalizacji java:comp/env/jdbc/JiraDS

Pozostaje ustawienie jira.home w pliku jira-application.properties i uruchomienie polecenia build.sh. Po tych krokach tworzymy plik jira.xml w którym zdefiniujemy kontekst tej aplikacji.

<Context path="/jira" docBase="/var/lib/tomcat-6/atlassian/atlassian-jira-4.0.war" debug="0">
    <Resource name="jdbc/JiraDS" auth="Container" type="javax.sql.DataSource"
            username="user"
            password="pass"
            driverClassName="org.postgresql.Driver"
            validationQuery="select 1"
            url="jdbc:postgresql://localhost:5432/database"/>
    <Resource name="UserTransaction" auth="Container" type="javax.transaction.UserTransaction"
            factory="org.objectweb.jotm.UserTransactionFactory" jotm.timeout="60"/>
    <ResourceLink name="mail/Session"
            global="mail/Session"
            type="javax.mail.Session" />
</Context>

Plik kopiujemy do $TOMCAT_HOME/conf/Catalina/localhost. Procedura instalacji powinna przebiegać podobnie jak w przypadku Crowd, czyli podajemy licencję, określamy bazę danych (java:comp/env/jdbc/JiraDS) i kilka kolejnych parametrów.

Instalacja GreenHopper

GreenHopper jako dodatek do JIRA nie wymaga konfiguracji bazy danych. Jedyne co robimy po jego pobraniu to kopiujemy go do $JIRA_HOME/plugins/installed-plugins. Reload kontekstu z managera Tomcata, i przechodzimy do administracji naszym bugtrackerem. W lewym menu szukamy sekcji System/GreenHopper Licence. Po podaniu klucza należy stworzyć projekt. Bez niego nie zobaczymy GreenHoppera w akcji.

Instalacja Confluence

Ostatni krok to instalacja Confluence. Pobieramy dystrybucję WAR/EAR:
Pobieranie Confluence

Tutaj też czeka nas nieco zabawy, ale nie tak dużo. Po rozpakowaniu

unzip confluence-3.0.2.zip

edytujemy plik confluence-3.0.2/confluence/WEB-INF/classes/confluence-init.properties:

confluence.home=/var/lib/tomcat-6/atlassian/confluence-home

Mając to wszystko, możemy spokojnie zająć kontekstem confluence.xml:

<Context path="/confluence" docBase="/var/lib/tomcat-6/atlassian/confluence-3.0.2.war" debug="0">
    <Resource name="jdbc/ConfDS" auth="Container" type="javax.sql.DataSource"
            username="user"
            password="pass"
            driverClassName="org.postgresql.Driver"
            validationQuery="select 1"
            url="jdbc:postgresql://localhost:5432/database"/>

  <ResourceLink name="mail/Session"
            global="mail/Session"
            type="javax.mail.Session" />

</Context>

Plik podobnie jak poprzednie zapisujemy w $TOMCAT_HOME/conf/Catalina/localhost. Po tym wszystkim instalujemy.

Konfiguracja Apache

Skakanie po portach i kontekstach nie jest bardzo wygodne, dlatego też warto skonfigurować Apache i mod_proxy aby wszystko było dostępne z poziomu portu 80, a nie 8080. W ten oto sposób Nexus którego używamy jest widoczny pod adresem repository.code-house.org, a nie localhost:8080. W konfiguracji vhosta umieszczamy:

<IfModule mod_proxy.c>
ProxyRequests On

ProxyPass /jira http://localhost:8080/jira
ProxyPassReverse /jira http://localhost:8080/jira
ProxyPreserveHost On

ProxyPass /confluence http://localhost:8080/confluence
ProxyPassReverse /confluence http://localhost:8080/confluence
ProxyPreserveHost On

ProxyPass /crowd http://localhost:8080/crowd
ProxyPassReverse /crowd http://localhost:8080/crowd
ProxyPreserveHost On

</IfModule>

Podsumowanie

Instalacja tych 3 aplikacji (nie licząc GreenHoppera) zajęła mi praktycznie cały dzień. Przyczyną największych problemów był Tomcat i wszystko inne – czyli np. źle wypakowane archiwum. Diagnostyka z logów Tomcata jest trudna, nie uświadczyłem w nich stack trace z wyjątkiem że nie może połączyć się do bazy. Nie mogłem zestawić również log4j na poziomie kontenera – który kłócił się później z Nexusem, a konfiguracja commons-logging jest jak na moje oko pokręcona. Po tym jak już dowiedziałem się co Tomcat powinien mieć w lib/ poszło z górki. Jeszcze nie zdecydowałem się na integrację JIRA i Confluence z Crowd ponieważ pierwsze podejście było nieudane i JIRA odmówiła uruchomienia się grzecznie zgłaszając java.lang.NullPointerException. :)
Mam nadzieję, że dzięki tej krótkiej instrukcji Wy drodzy czytelnicy nie będziecie mieli tylu problemów.

Do tej pory tworząc kod z użyciem typów generycznych zawsze borykałem się z problemem – jak dobrać się do klasy na podstawie któregoś parametru. Rozwiązanie znalazłem przeglądając w internecie kod jakieś przykładowej aplikacji. Ot znowu kilka linii które uporządkują kod i zmniejszą ilość powtórzeń. :-)

Dbanie o porządek w kodzie to obowiązek każdego programisty. Stąd podział aplikacji na warstwy, wyodrębnianie wspólnego kodu do typów abstrakcyjnych i tak dalej. Są jednak miejsca, których do tej pory nie potrafiłem uporządkować dobrze – były to wszelkiego rodzaju DAO, które zawsze wyglądały podobnie. Interfejs, klasa abstrakcyjna, pochodna wywołująca chroniony konstruktor. Wszystko po to by w klasie abstrakcyjnej móc zdefiniować metodę readById. Oto przykład takiego kodu:

// Ogólny interfejs dostępu do danych
// T to typ encji, K to typ klucza w danej encji.
public interface GenericDAO<T, K> {
    void save(T instance);
    void create(T newInstance);
    void update(T existingInstance);
    boolean delete(T instance);
    T readById(K id);
    List<T> getAll();
}
// Implementacja oparta o mechanizmy Springa
public abstract class AbstractDAO<T extends BaseEntity, K> extends
    JpaDaoSupport implements GenericDAO<T, K> {
    private Class<T> typeClass;
    protected AbstractDAO(Class typeClass) {
        this.typeClass = typeClass;
    }
    // pobranie obiektu na bazie id
    public T readById(K id) {
        return getJpaTemplate().find(typeClass, id);
    }
}

// Przekazanie User.class do AbstractDAO by te mogło 
// użyć go do odpytywania bazy danych
public class UserJpaDAO extends AbstractDAO<User, Long> implements UserDAO {
    public UserJpaDAO() {
        super(User.class);
    }
}

W tym wypadku typ generyczny zamiast uprościć życie nieco je komplikuje – w ostatniej klasie musimy pisać dwa razy User – raz jako parametr typu, dwa przekazując go do konstruktora klasy abstrakcyjnej. Logicznie rzecz biorąc. Znany jest typ generyczny zatem to nazwa klasy też powinna być do wykrycia. I tak rzeczywiście jest. Od JDK 1.5 istnieje interfejs ParameterizedType, który zawiera metodę getActualTypeArguments, Wystarczy dobrać się do tego obiektu i będzie z górki. Oto jak ja to rozwiązałem:

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.manager.dataaccess.jpa.util;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

/**
 * Klasa pomocnicza do operowania na typach generycznych.
 *
 * @author Łukasz Dywicki luke@code-house.org
 *
 * $Id$
 */
public class GenericTypeUtil {

    /**
     * Konstruktor prywatny, na wypadek gdyby ktoś chciał stworzyć instancję
     * utila. :)
     */
    private GenericTypeUtil() {}

    /**
     * Pobranie argumentów dla danego typu na bazie klasy.
     */
    public static Class[] getGenericTypes(Class type) {
        Type superclass = type.getGenericSuperclass();
        if (superclass instanceof ParameterizedType) {
            ParameterizedType genericTypeClass = (ParameterizedType) superclass;
            return cast(genericTypeClass.getActualTypeArguments());
        }
        return new Class[0];
    }

    // Rzutowanie w dół z Type do Class.
    public static Class[] cast(Type[] types) {
        Class[] classTypes = new Class[types.length];
        for (int i = 0, j = types.length; i < j; i++) {
            classTypes[i] = (Class) types[i];
        }
        return classTypes;
    }
}

Co zwróci nam wywołanie GenericTypeUtil.getGenericTypes(UserJpaDAO.class)? To czego potrzebujemy! :)

[class org.code_house.manager.domain.User, class java.lang.Long]

Czy znaliście ten sposób? A może tylko ja byłem nieświadom tego rozwiązania?

W poprzednim wpisie przedstawiłem sposób na redukcję kodu w encjach przy pomocy dziedziczenia i adnotacji @MappedSuperclass. Rozwiązanie to możemy również stosować aby tworzyć kod bardziej przenośny, który niewielkim kosztem można użyć w innych projektach.


Większość aplikacji webowych stosuje autoryzację opartą o role (ang. Role Based Access Control), w takich wypadkach mamy zazwyczaj encję User oraz Role, pierwszą odpowiedzialną za przetrzymywanie informacji o użytkowniku a druga nazwy ról. O ile role są obszarem stałym – zawsze mają nazwę – o tyle użytkownicy często mają różne wariacje i relacje. Ot choćby powiązanie konta użytkownika z firmą.

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.security.domain;

import java.util.Calendar;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.ManyToMany;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.code_house.domain.BaseEntity;

/**
 * Klasa reprezentująca użytkownika. Powinna ona zostać nadpisana w docelowym
 * systemie poprzez plik orm.xml.
 * 
 * @author Łukasz Dywicki.
 */
@MappedSuperclass
public class SecurityUser extends BaseEntity {

    /**
     * Hasło użytkownika.
     */
    @Column
    private String password;

    /**
     * Login użytkownika.
     */
    @Column
    private String username;

    /**
     * Konto wygasło.
     */
    @Column
    @Temporal(TemporalType.TIMESTAMP)
    private Calendar expire;

    /**
     * Grupy do których przynależy użytkownik.
     */
    @ManyToMany(mappedBy = "users")
    private Set<Role> roles;

    /**
     * Konto zablokowane np. po 3 nieudanych próbach logowania.
     */
    @Column
    private boolean locked;

    /**
     * Konto wyłączone np. przez administratora.
     */
    @Column
    private boolean enabled;

   // Dalej gettery i settery
}

Rola ma niekompletną definicję relacji do klasy SecurityUser, którą należy uzupełnić o informacje na temat encji docelowej i definicji tabeli pośredniczącej.

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.security.domain;

import javax.persistence.Entity;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.code_house.domain.NamedEntity;

/**
 * Klasa reprezentująca rolę. Może to być np ADMIN, USER bądź cokolwiek innego.
 * 
 * @author Łukasz Dywicki luke@code-house.org
 */
@Entity
@Table(name = "roles")
public class Role extends NamedEntity {

    @ManyToMany
    @JoinTable(name = "users_roles",
        joinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
    )
    private Set<SecurityUser> users;

    // gettery i settery
}

Oto kawałek konfiguracji XML, która uzupełni metadane i pozwoli na uruchomienie kodu. Atrybut target-entity pozwala na określenie tabeli docelowej relacji podczas gdy element inverse-join-column uzupełnia definicję tabeli pośredniczącej.

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="1.0" 
        xmlns="http://java.sun.com/xml/ns/persistence/orm"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">

    <package>org.code_house.security.domain</package>

    <entity class="Role">
        <attributes>
            <many-to-many name="users" target-entity="org.code_house.management.domain.User">
                <join-table>
                    <inverse-join-column name="user_id" referenced-column-name="id" />
                </join-table>
            </many-to-many>
        </attributes>
    </entity>
    <entity class="Memento">
        <attributes>
            <one-to-one name="user" target-entity="org.code_house.management.domain.User">
                <join-column name="user_id" />
            </one-to-one>
        </attributes>
    </entity>
</entity-mappings>

Przykład ten można wykorzystać by uzupełniać relacje, których nie sposób odzwierciedlić w kodzie.

Z pewnością nie jest to odkrycie godne podziwu czy też coś, co może realizować z nowinkami JPA 2.0, nie mniej jest to kod bez którego żaden projekt obejść się nie może – mianowicie identyfikacja encji.

Borykałem się z problemem dosyć pospolitym, mianowicie pozbyciem się z każdej encji kodu:

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

Nic dziwnego, 40 encji to aż 120 powtórzonych, niepotrzebnie powielonych linii. Nie są obce mi wzorce dziedziczenia, które Martin Fowler opisywał w swojej książce, w tym przypadku chodzi o dziedziczenie mapowanych pól a nie fizyczne odwzorowanie tej hierarchii, czyli zagrywka czysto techniczna.

Odpowiedni kod znalazłem w przykładowej aplikacji z dystrybucji Spring Framework 3. Jedyny szkopuł to że całość spisana w XML. W mapowaniu jest jednak rozwiązanie zagadki – mianowicie element mapped-superclass. Po nitce do kłębka – trafiłem na adnotację @MappedSuperclass. Kilka linii które uprościły mój kod to:

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.domain;

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;

/**
 * Klasa bazowa dla encji.
 * 
 * @author Łukasz Dywicki luke@code-house.org
 */
@MappedSuperclass
public class BaseEntity {

    /**
     * Identyfikator encji.
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    /**
     * Pobranie wartości pola id.
     * 
     * @return Wartość id.
     */
    public Long getId() {
        return id;
    }

    /**
     * Ustawienie wartości pola id.
     * 
     * @param id Nowa wartość pola id.
     */
    public void setId(Long id) {
        this.id = id;
    }
}

Drugi przypadek to encja nazwana, czyli coś co ma nazwę. Może to być encja reprezentująca klienta bądź firmę. Cokolwiek, co można nazwać. Ot, chociażby samochód (dla przykładu na swoje seicento zwykłem mówić ciężarówka), ilość takich przypadków można naturalnie mnożyć, ale nie o to nam chodzi.

// Copyright (C) 2009 Code-House
// All rights reserved
package org.code_house.domain;

import javax.persistence.Column;
import javax.persistence.MappedSuperclass;

/**
 * Encja zawierająca pole z nazwą.
 *
 * @author Łukasz Dywicki luke@code-house.org
 */
@MappedSuperclass
public class NamedEntity extends BaseEntity {

    /**
     * Nazwa przypisana do encji.
     */
    @Column
    private String name;

    /**
     * Pobranie wartości pola name.
     *
     * @return Wartość name.
     */
    public String getName() {
        return name;
    }

    /**
     * Ustawienie wartości pola name.
     *
     * @param name Nowa wartość pola name.
     */
    public void setName(String name) {
        this.name = name;
    }

}

Zamierzony efekt osiągnięty – definicje encji krótsze, kod sprzątnięty. Całość projektu, razem z dokumentacją i tym podobnymi jest dostępna w repozytorium Code-House.

Jakiś czas temu zapraszaliśmy na tym blogu zainteresowanych na Warszawski Eclipse DemoCamp Galileo 2009. Jest nam niezmiernie miło ogłosić, że temat poruszany na tym spotkaniu przez Code-House Software został kontynuowany. Wsparcie dla Apache ServiceMix w oparciu o Spring IDE zainteresowało społeczność jak i organizacje skupione wokół projektu, między innymi FUSE Source. Nie będzie to oczywiście alternatywa dla narzędzi oferowanych przez tą firmę a raczej dopełnienie funkcjonalności, których w tej chwili trudno szukać.

Strona projektu znajduje się na razie na wiki bug trackera Code-House i jest na nim kilka informacji odnośnie celów projektu i nawet parę zadań. :-) Na pytanie kiedy pojawi się działająca wersja ciężko nam odpowiedzieć ponieważ w obecnej chwili siły przerobowe naszej firmy oprócz zaangażowania w kontrakt muszą podołać przygotowaniu akcji marketingowej w prasie oraz internecie. Miejmy nadzieję, że przed końcem wakacji pojawi się działająca wersja. Oczywiście nie jest to jedyny sposób w jaki Code-House wspiera społeczność projektu Apache ServiceMix, w planach mamy wspomaganie paru komponentów które wzbogacą funkcjonalność magistrali.

Obecnie projekt ma przygotowaną infrastrukturę w oparciu o Apache Maven 2 tak by całość mogła być budowana automatycznie. Sporym problemem z tworzeniem rozszerzeń do Eclipse IDE jest właśnie ciągła integracja. Zazwyczja cały proces jest realizowany przy pomocy generowanych plików zgodnych z Apache Ant. Niestety by je uruchomić trzeba i tak mieć lokalną instalację Eclipse. Po kilku dniach wysiłków udało się jednak znaleźć rozwiązanie a lokalną instalację wykorzystywać tylko w przypadku konieczności. Zainteresowanych zapraszam do repozytorium ze źródłami projektu.

Ogólnie rzecz biorąc budowanie wtyczek, update site (czy repozytoriów p2), bez użycia Eclipse jest zadaniem trudnym. Mimo tego, że P2 Publisher wygląda na niezależny od samego Eclipse nie można go uruchomić poza OSGi/Equinox. Próbowaliśmy obejść ten problem dodając własne implementacje części interfejsów jednak ilość zależności w kodzie jest bardzo duża i po pół dnia walki niestety trzeba było zrezygnować by nie zabrnąć w ślepą uliczkę. Podobnie ma się kwestia z taskami ant, które są równie oporne w działaniu. Alternatywą był projekt Buckminster i adaptery do Mavena. Budowanie jednak również musiało by wówczas być takie jak zwykle w świecie Eclipse – przez Ant.

Rozwiązaniem okazało się połączenie maven-eclipse-plugin:to-maven oraz maven-bundle-plugin z projektu Apache Felix. Pierwszy zapewnił wyeksportowanie platformy Eclipse do naszego repozytorim Mavena podczas gdy drugi generuje odpowiednie manifesty zgodne z OSGi. Update site jest już końcową fazą budowania. Z użyciem maven-assembly-plugin tworzymy strukturę właściwą Eclipse. Opcjonalnym elementem, który wymaga instalacji Eclipse na komputerze jest stworzenie repozytorium P2 uruchamianym przez maven-exec-plugin:exec. Pracy było dużo ale mamy nadzieję że zaprocentuje ona w przyszłości przyśpieszając development i wprowadzanie zmian do projektu. Po więcej informacji o postępach w pracach zapraszam ponownie na blog do kategorii ServiceMix IDE.

Javarsovia 2009

29 Cze
2009

Javarsovia Logo W najbliższy weekend odbędzie się największa warszawska, darmowa, konferencja poświęcona Javie. Nasza firma wsparła przedsięwzięcie niewielką kwotą stąd też zapraszamy do wzięcia udziału w konferencji. :) Tematy zostały podzielone na trzy kategorie:

  • Java – technologicznie, czyli o nowinkach i bibliotekach (7 prezentacji)
  • Java – pragmatycznie, optymalizacja, testy (8 prezentacji)
  • Java – koncepcyjnie, niezwiązane bezpośrednio z kodem (3 prezentacje)

Jak widać jest wiele tematów do wyboru. Nowością jest dodatkowa ścieżka warsztatowa, podczas której odbędzie się tylko 1 prezentacja. Dla tych, którzy nie będą mogli pojawić się na konferencji niespodzianka – organizatorzy będą starać się nagrywać wszystkie sesje tak by nic nie uciekło. Całość będzie naturalnie dostępna później, najprawdopodobniej na Parleys.

Raz jeszcze zapraszamy na konferencję. Będzie to znakomity początek lata dla programistów i nie tylko! :-)

Zgodnie z obietnicą z poprzedniego postu zamieszczam materiały z prezentacji którą wczoraj przeprowadziłem w ramach Warszawskiego Eclipse DemoCamp Galileo 2009.

Spring IDE Celem prezentacji było dodanie obsługi nowej (niestandardowej) przestrzeni nazw w Spring IDE. Jako dobry przykład rozbudowanej przestrzeni nazw wybrałem konfigurację komponentu servicemix-file. Jako że nie udało mi się przed prezentacją zmusić Apache XBean do pracy wewnątrz Eclipse musiałem stworzyć własny namespace handler. Dopiero wczoraj, późno w nocy stworzyłem patch który umożliwia podpięcie normalnego handlera. Jakkolwiek przykład dla spójności pozostał ze starym kodem.

Zapraszam do pobierania slajdów z prezentacji oraz kodu kodu źródłowego projektu.

Niestety nie udało mi się omówić wszystkich aspektów które chciałem, zatem możecie spodziewać się publikacji na temat rozszerzania Spring IDE. :-).

W dniu 2 czerwca 2009 odbędzie się Warszawski Eclipse DemoCamp Galileo 2009, nasza firma wspiera to wydarzenie merytorycznie oraz finansowo. Głównym sponsorem jest fundacja Eclipse.
Działanie to doskonale wpisuje się w profil naszej firmy, której celem jest współpraca ze społecznością oraz promowanie rozwiązań Open Source – Spring IDE – o którym będzie mowa to projekt z otwartym kodem źródłowym rozwijanym w dużej mierze przez SpringSource.

Tematy które będą prezentowane:

  • 18:00-18:15 Spring IDE, czyli o wsparciu dla Springa wewnątrz IDE. Łukasz Dywicki
  • 18:20-18:35 Wyślij swoją myszę na urlop, czyli o skrótach i szablonach w Eclipse. Wojciech Erbetowski
  • 18:40-18:55 Wtyczka do Eclipse w 5 … no może 15 minut. Mateusz Zięba
  • 19:00-19:15 Rozszerzanie JDT dzięki Equinox Aspects i AJDT weaving service. Michał „migi” Grzejszczak
  • 19:20-19:35 Z Eclipsem w chmurach w 15 minut, czyli jak użyć GoogleApp Engine wraz z pluginem do Eclipse, aby uruchomić aplikację w 15 minut. Łukasz Lenart
  • 19:40-19:55 Oracle JRockit Mission Control, czyli demonstracja profilowania i diagnostyki aplikacji Java przy użyciu Eclipse. Waldemar Kot

Pierwsza pogrubiona prezentacja to nasza :-). Co kryje się w podtytule wsparcie dla Springa wewnątrz IDE? Głownie obszary związane z edycja kontekstu zapisanego w XML, co dokładnie? Tego dowiecie się już na prezentacji. Materiały jak i przykłady zostaną opublikowane na tym blogu po wtorku. Raz jeszcze serdecznie zapraszam na świętowanie nowego wydania. :)

top