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 related to Code-House company.


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 związane z firmą Code-House.

Spring 3.0 RC3 + Maven

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.

Najnowsze wpisy

post GWT oraz implementacje MVC

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. :-)


post Instalowanie oprogramowania Atlassian cz. 1

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.


post Typy generyczne od teraz są mniej anonimowe

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?


post XML i Adnotacje – kod ogólnego przeznaczenia i JPA

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.

Older Posts

Dwie klasy redukujące ilość kodu w encjach JPA

ServiceMix IDE integration

Javarsovia 2009

Materiały z Eclipse DemoCamp

Warszawski Eclipse DemoCamp Galileo 2009