Spring Framework- projektowanie aplikacji w oparciu o koncepcję MDA

Chciałbym podzielić się swoimi doświadczeniami, które zdobyliśmy (ja i mój mały zespół) przy realizacji pewnego rozwiązania opartego o Oracle Portal (OC4J), OID(Oracle Internet Directory) i Spring (Portlet+Web) Framework.

Zadanie polegało na stworzeniu dwóch rzeczy:

  • aplikacji webowej, która pozwoli na zarządzanie danymi o pracownikach działów jednostki
  • sparametryzowany portlet, który możnaby było położyć na stronach poszczególnych działów i przy jego pomocy wyświetlać informacje o zatrudnionych w nim osobach.
Obydwie aplikacje były bardzo proste, spodziewaliśmy się szybkich efektów, więc ochoczo zabraliśmy się do pracy.

Bardzo szybko pojawił się problem - złożoność aplikacji wydawała się tak niewielka, że zaniedbaliśmy fazę projektowania, co zaowocowało tym, że wkrótce pogubiliśmy się we własnym kodzie. Do tego doszły problemy związane z obsługą portletów WSRP przez kontener OC4J - ogólnie wszystko złożyło się na ogólny chaos.

Złe widoki na przyszłość zmusiły nas do refleksji nad sposobami i narzędziami, które możemy wykorzystać do dobrego zaplanowania, a potem zarządzania kodem w naszym projekcie.

Wobec naszej aplikacji zastosowaliśmy rozwiązanie dosyć brutalne i dramatyczne - stworzyliśmy projekt a następnie napisaliśmy wszystko na nowo... ale cóż... według starego powiedzenia... jak ktoś nie ma w głowie...

Z naszych rozważań wyewoluowało pewne rozwiązanie, propozycja sposobu modelowania aplikacji webowych (szczególnie tych springowych ) które opiera się na koncepcji Model Driven Architecture ( nie mylić z DeadLine Driven Architecture ).

Jako narzędzie do modelowania wykorzystaliśmy potężne narzędzie CASE - Enterprise Architect ( jest to środowisko komercyjne, ale stosunkowo niedrogie - a przez to powszechnie dostępne).

W moich rozważaniach chciałbym skupić się nie na samej aplikacji, lecz na stosowanym przez nas sposobie projektowania. Jako przykłady wykorzystam modele stworzone przy realizacji innego, bardziej dowolnego rozwiązania.

Wielkie podziękowania kieruję w stronę BOJARA za dobry pomysł, wspólną pracę nad rozwiązaniem i wsparcie teoretyczne.

Wprowadzenie teoretyczne

Model Driven Architecture (MDA) - wprowadzenie


Opracowano na podstawie MDA Guide Version 1.0.1

Na początek nie da się uciec od definicji kilku podstawowych pojęć, które związane są koncepcją Model Driven Architecture.


Model Driven Architecture
jest koncepcją wspomagającą i porządkującą procesy tworzenia aplikacji i systemów komputerowych. Jej głównym celem jest dostarczenie mechanizmów i narzędzi, które pozwolą spojrzeć na system ze szczególnym naciskiem na jego przenośność, a więc niezależność od platformy systemowej która wykorzystana będzie do jego uruchomienia.

MDA zakłada tworzenie kilku głównych poziomów modeli, obejmujących różne aspekty projektowanego systemu:

Computation Independent Model (CIM) - model domeny - jest to zestaw modeli skupiających się nie na architekturze tworzonej aplikacji,lecz na środowisku, w którym ma ona działać. Modele te składają się na specyfikacja wymagań systemu.

Platform Independent Model (PIM) - są to modele pokazujące tą część architektury systemu która jest całkowicie uniezależniona od platformy pod której kontrolą będzie on działał.

Platform Specific Model (PSM) - zestaw modeli, które powstają po uzupełnieniu modeli PIM o informacje specyficzne dla platformy która będzie wykorzystywana do działania systemu.

Platform Model - jest to model pokazujący najistotniejsze ( z punktu widzenia działania tworzonej aplikacji ) fragmenty architektoniczne platformy, która wykorzystana zostanie do uruchomienia działającego systemu.

Koncepcja MDA szczególny nacisk kładzie na dynamiczne przejście pomiędzy modelami PIM i PSM. Dokonuje się ona w oparciu o proces tzw. transformacji modeli.
Polega on na tym, że model PIM jest uzupełniany o informacje dodatkowe, które determinowane są rodzajem wskazanej platformy docelowej. W wyniku połączenia tych danych otrzymywany jest model PSM.

Definiowanie zasad przekształcenia modelu (Mapping):

W wyniku mapowania określone zostają zasady transformacji , którym poddany ma zostać model PIM by powstał model PSM. Zasady mapowania są determinowane przez wybór platformy do jakiej przekształcony ma zostać model PIM.

Można wyróżnić trzy rodzaje mapowań
  • Model Type Mapping - mapowanie na poziomie metamodelu
  • Model Instance Mapping - mapowanie instancji modelu
  • Combined Type and Instance Mapping- mapowanie łączone
ad. 1: Model Type Mapping
Architekt tworzący model PIM używa typu elementu istniejącego w zadanym języku, a następnie przekształcenie odbywa się według określonych reguł i powstaje typ elementu istniejący w innym języku.

Należy zwrócić uwagę na fakt, że przekształcenie odnosi się nie do samego ELEMENTU, ale do jego TYPU.

Przykładem tego rodzaju mapowania jest przekształcenie Klasy w Encję.

Specjalnym rodzajem tego typu mapowań jest mapowanie metamodelu (Metamodel Mappings). W jego przypadku zarówno elementy modelu PIM jak i PSM są zdefiniowane w repozytorium MOF (Meta-Object Facility). Mapowanie tego typu powoduje przekształcenie typów obiektów znajdujących należących do jednego metamodelu (na podstawie którego określony jest model PIM) na typy elementów należących do innego (na podstawie którego stworzony został model PSM).

ad. 2: Model Instance Mapping
Jest to mapowanie na poziomie obiektów modelu. Jej integralną częścią jest oznaczenie elementów w modelu PIM. Przy wykonywaniu przekształcenia, następuje identyfikacja sposobu transformacji obiektów oznaczonych określonym znakiem, a następnie zastosowanie jej i przekształcenie ich do odpowiedniego elementu w modelu PSM.

Znaczniki nie są częścią modelu PIM. Są one w pewien sposób zależne od platformy.
Specyfikacja mówi:
"The marks can be thought of as being applied to a transparent layer placed over the model"

Istnieje wiele sposobów oznaczania modelu PIM - ciekawym rozwiązaniem wydaje się używanie profili UML. Stanowią one rozszerzenie języka UML. Pozwalają na zdefiniowanie nowego języka modelowania, opartego na UML-u. Ten nowo zdefiniowany język może posłużyć do tworzenia modeli, lub na zastosowanie go do istniejącego modelu. Zastosowany profil może zostać zdjęty z modelu, a wtedy model zachowuje te cechy, które posiadał przed zastosowaniem go. Każdy model który używa profili UML jest ciągle modelem UML.

ad. 2: Combined Type and Instance Mapping
Jest to sposób mapowania oparty na obydwu opisanych wcześniej.


Przekształcanie modeli (Transformation)

Transformacja modelu polega na przekształceniu oznaczonego modelu PIM w model PSM. Znaczniki modelu PIM wykorzystywane są do określenia wymaganego odpowiedniego przekształcenia.

Realizacja koncepcji

Profil UML - jako rozszerzenie metamodelu

Realizację zadania rozpoczniemy od stworzenia nowego języka modelowania opartego na UML.
Posłużymy się opisywanymi wcześniej Profilami UML. Wszystkie przykłady oparte będą na narzędziu Enterprise Architect.

Profil UML musimy traktować jako specjalizowane rozszerzenie UML-a przystosowane do używania w określonej domenie problemowej.
Dla każdego elementu naszego profilu musimy wskazać determinujący jego cechy typ bazowy, który istnieje w definicji języka UML.
Musimy tu odnieść się do struktury samego języka UML - a więc do jego metamodelu.
Ten generyczny tryb występujący w specyfikacji nazywa się metaklasą (Metaclass). Jest on podstawą do tworzenia każdego obiektu występującego w UML.

Cytując za Wiki:
"... a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances..."

Przykładowo wygląd, cechy i zachowanie każdego obiektu Class w UML-u, jest determinowane definicją jego metaklasy Class. Tą zasadę można zastosować dla każdego elementu języka UML.

Najprostszym sposobem rozszerzania metamodelu, powszechnie stosowanym przy tworzeniu Profili jest zastosowanie stereotypów. Przy tworzeniu profilu wskazujemy określoną metaklasę, której cechy chcemy rozszerzać, a następnie rozszerzamy ją określonym stereotypem. Dla tego nowego elementu możemy zdefiniować nowe cechy, wygląd i zachowanie. Taki nowy element może być położony na każdym modelu, do którego zostanie zaimportowany dany profil.

Należy zwrócić uwagę na pewną cechę rozszerzania metamodelu przy pomocy stereotypów. Rozszerzenie metaklasy Class stereotypem X, po zaimportowaniu takiego profilu do nowego modelu nie pozwoli nam na używanie obiektów typu X, lecz obiektów Class o stereotypie X. Nie jest tworzona nowa metaklasa - następuje tylko rozszerzanie już istniejącej. Poniżej znajduje się ilustracja opisywanego przypadku - na początek definicja profilu:


a następnie wynik uzyskany po zaimportowaniu danego profilu do nowego modelu i użyciu na nim elementu X.

Zgodnie z oczekiwaniami dodana została nowa klasa o stereotypie X oraz cechach (w tym przypadku zobrazowanych kolorem) zdefiniowanych w profilu dla elementu X.

Profil można traktować jako model modelu przystosowanego do specyficznej dziedziny problemowej.

Wsparcie oferowane przez narzędzie EA w zakresie zarządzania profilem

Program Enterprise Architect daje pełne wsparcie do tworzenia i obsługi profili UML. Pozwala na stworzenie modelu będącego profilem, a następnie wyeksportowanie go do pliku XML. Plik taki może bez problemu zostać zaimportowany do dowolnego modelu. Wtedy otrzymamy w nim nową belkę narzędziową, na której znajdą się elementy stworzone w profilu.

Okno służące do esportu danego profilu do pliku xml wygląda tak:

Bardzo użyteczną, oferowaną przez niego funkcją jest możliwość sortowania elementów. Można w dowolny sposób zarządzać kolejnością ich występowania w belce narzędziowej.

Wyeksportowany plik można zaimportować do dowolnego modelu:

Dodatkowo w menu kontekstowym uruchamianym prawym przyciskiem myszki na nazwie profilu (podświetlona na poprzednim ekranie) można wybrać opcję "Show Profile in UML Toolbox", co spowoduje umożliwieniem dostępu do nich przy pomocy palety narzędziowej:


Stworzenie profilu Spring w narzędziu Enterprise Architect

Chcielibyśmy, aby tworzony przez nas język modelowania wspierał nie tylko planowanie statycznej architektury aplikacji ( np. struktura klas ) , ale także pozwalał na zaplanowanie jej dynamiki (np. tworzenie map przepływów sterowania pomiędzy różnymi częściami aplikacji).

Pierwszym krokiem było stworzenie elementów występujących w warstwie webowej i interfejsu użytkownika.

Zdefiniowaliśmy stereotypy dla podstawowych klas używanych w warstwie webowej, a więc:
  • SimpleValidatorController
  • SimpleController
  • SimpleFormController
  • Widok JSP
Aby na diagramach przepływów sterowania i struktury od pierwszego spojrzenia wiedzieć z jakimi elementami ma się do czynienia - każdy stereotyp oznaczyliśmy odpowiednim kolorem.

Potem dwa elementy charakterystyczne dla warstwy biznesowej:

W warstwie biznesowej wyróżniliśmy dwa odrębne elementy:
  • BusinessInterface
  • SimpleBusinessClass

W celu łatwego rozróżnienia na diagramach będą one wyświetlane odpowiednim kolorem.

Warstwa danych:

W warstwie danych wyróżniono jeden wspólny interfejs - DAOInterface i kilka klas narzędziowych które wspomagają łączenie się z ldapem, lub z bazą danych za pośrednictwem Hibernate. Są to:
  • DAOHibernate
  • DAOLdap
  • LdapAttributeMapper
Dodatkowo, na potrzeby diagramów modelowania przepływu sterowania konieczne staje się zdefiniowanie kilku elementów, które będą oddawały dynamikę systemu, będą pokazywały sposób przekazywania komunikatów pomiędzy poszczególnymi elementami budowanej aplikacji. Elementy te będą łącznikami pomiędzy innymi elementami, a więc muszą być odmianą jakiegoś łącznika. Wybrano metaklasę "Dependency":

Utworzone elementy to:
  • Forward
  • Redirect
  • Request
  • Submit
Na wypadek gdyby trzeba było ręcznie dodać którychś z nich na przekształconym modelu zdefiniowano kilka elementów wchodzących w skład PSM:


Są to:
  • WebSimpleController
  • WebSimpleFormController
  • PortletSimpleController
  • PortletSimpleFormController
Kod tranformacji modelu PIM->PSM
Koncepcja MDA zakłada istnienie przekształcenia, które pozwoli na przetworzenie oznaczonego modelu PIM w model PSM, którego wygląd i struktura jest determinowana rodzajem wybranej technologii. Narzędzie EA pozwala na definiowanie szablonów takiego przekształcenia. Są one wykorzystane w procesie automatycznej translacji.
Poniżej zamieszczono fragmenty kod transformacji dla przykładowego elementu z profilu SpringSimpleController. Jest on napisany w wewnętrzny języku definiowania transformacji modeli narzędzia EA.

...

%TRANSFORM_REFERENCE("Class")%
%TRANSFORM_CURRENT("language","stereotype")%
language="Java"
stereotype="WebSimpleController"

Parent
{
name="Controller"
type="Implements"
}
Attribute{
name="logger"
type="Log"
default="LogFactory.getLog(getClass())"
scope="private"
}
Operation
{
name="handleRequest"
type="ModelAndView"
scope="public"
Parameter
{
name="request"
kind="in"
type="HttpServletRequest"
}
Parameter
{
name="response"
kind="in"
type="HttpServletResponse"
}
Tag
{
name="throws"
value="Exception"
}
}
...

Powyższa transformacja przypisana jest do modelu PIM, do elementu o stereotypie SimpleController. Jej dopasowanie i zastosowanie spowoduje stworzenie w modelu PSM klasy Java o stereotypie WebSimpleController. W kodzie tego szablonu można dopatrzeć się definicji właściwości(sekcja Attribute) i sparametryzowanych metod (sekcja Operation), które są determinowane przez implementację interfejsu Contoller (Sekcja Parent).

Generowanie kodu źródłowego

Nieodłącznym etapem tworzenia oprogramowania jest generowanie kodu. Powstaje on z diagramów PSM.

Poniżej zamieszczono fragment szablonu wykorzystywanego do generowania kodu źródłowego klas Java o stereotypie WebSimpleController zdefiniowany w wewnętrznym języku skryptów generujących kod EA.
...

import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

%ClassNotes%
%PI=" "%
%ClassDeclaration%
%ClassBody%
...

W sytuacji, gdy z modelu PSM generowany będzie kod aplikacji i nastąpi dopasowanie do klasy Java o stereotypie "WebSimpleController" to wykonany zostanie powyższy kod - spowoduje to automatyczne wygenerowanie zestawu importów przewidzianych dla tego elementu. Jest to warunkiem wygenerowania kodu projektu, który bez nanoszenia dodatkowych poprawek będzie gotowy do kompilacji.

Przykład

Mapa przepływu dla kilku przykładowych przypadków użycia:


Model PIM dla tego fragmentu aplikacji wygląda następująco:


Model PSM po transformacji Spring Portlet:


Model PSM po transformacji Spring Web:


Wygenerowane modele PSM mogą posłużyć do stworzenia plików źródłowych aplikacji:

Kod SimpleController po tranformacji Spring Web:

package net.beetle.web.projects;

import java.util.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
/**
* @version 1.0
* @created 09-kwi-2007 09:16:44
*/
public class ProjectsListController implements Controller {

private Log logger = LogFactory.getLog(getClass());

public ProjectsListController(){

}

public void finalize() throws Throwable {

}

/**
*
* @param request
* @param response
* @exception Exception
*/
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception{
return null;
}

}

Kod SimpleController po tranformacji Spring Portlet:

package net.beetle.web.projects;

import java.util.*;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.web.portlet.mvc.SimpleFormController;
import org.springframework.web.portlet.mvc.AbstractController;
import org.springframework.web.portlet.ModelAndView;
/**
* @version 1.0
* @created 09-kwi-2007 09:15:30
*/
public class ProjectsListController extends AbstractController {

private Log logger = LogFactory.getLog(getClass());

public ProjectsListController(){

}

public void finalize() throws Throwable {
super.finalize();
}

/**
*
* @param request
* @param response
* @exception Exception
*/
protected void handleActionRequestInternal(ActionRequest request, ActionResponse response)
throws Exception{

}

/**
*
* @param request
* @param response
* @exception Exception
*/
protected ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)
throws Exception{
return null;
}

}


Aplikacja pokazująca sposób wykorzystania i zalety zaprezentowanego modelowania - gdy tylko czas pozwoli...

Springowe portlety JSR-168 - JBoss Portal

Przed przystąpieniem do realizacji spójrz na mój poprzedni post.

Krok 1: Przygotowanie środowiska
Przygotowanie środowiska polega na rozpakowaniu na dysku pliku zip ze sciągniętym, prekonfigurowanym pod kątem używania go w portalu serwera JBoss i zarejestrowania go w NetBeans.

  1. Należy rozpakować serwer
  2. Uruchomić NetBeans
    Następne kroki należy wykonać w NetBeans:
  3. Tools->Server Manager
  4. Add Server
  5. 5. Z górnej listy rozwijanej należy wybrać opcję "JBoss Application Server 4", w dolnym polu tekstowym należy wpisać nazwę pod jaką ma być widziany nasz serwer i kliknąć Next
  6. Należy wskazać katalog główny naszego rozpakowanego serwera i wcisnąć przycisk Next

  7. W następnym oknie wszystkie opcje należy pozostawić niezmienione i naisnąć przycisk Finish

  8. Nowo dodany serwer powinien pojawić się na liście serwerów.
  9. Jeśli wszystko jest w porządku należy zamknąć okno Server Manager.
Krok 2: Założenie pustego projektu WEB
  1. File -> New Project
  2. W oknie dialogowym "Categories" należy wybrać Web, a w oknie "Projects" - Web Application i wcisnąć przycisk Next
  3. Należy wpisać nazwę projektu (spring-portlets) a listę "Source Structure" ustawić na Jakarta - reszta opcji bez zmian i wcisnąć przycisk Finish

  4. Okno ulegnie zamknięciu i zostanie utworzony nowy projekt o nazwie (spring-portlets)
Krok 3: Przekopiowanie źródeł przykładu do nowo utworzonego projektu
  1. Należy pobrać plik spring-portlets.zip
  2. Zawartość katalogu spring-portlet-sample (bez samego katalogu!!!) należy rozpakować i umieścić w katalogu NetBeans_Workspace/spring-portlets/web
  3. Istnienie domyślnych plików zakładanych standardowo, przy tworzeniu projektu przez NetBeans będzie generowało ostrzeżenia podczas kopiowania- nie trzeba się tym przejmować - przy pierwszym ostrzeżeniu należy kliknąć "Zamień wszystkie" - lub zamieniać każdy plik po kolei.
  4. Ponieważ wprowadziłem kilka drobnych modyfikacji do oryginalnego, dostarczanego z przykładami skryptu "/web/build.xml" najprościej będzie go podmienić przez ten zmodyfikowany, pobrany stąd.
  5. Po uruchomieniu NetBeans drzewo projektu powinno wyglądać mniej więcej tak:

  6. Dołączenie do projektu wymaganych bibliotek.
    Przede wszystkim konieczne jest dodanie do projektu biblioteki /lib/portlet-api.jar . W tym celu należy:
    1. Kliknąć prawym przyciskiem na nazwie projektu.
    2. Z menu kontekstowego wybrać opcję Properties
    3. W nowo otwartym oknie dialogowym, w polu "Categories" wskazać opcję "Libraries"
    4. Wybrać przycisk "Add JAR/Folder"
    5. Wskazać katalog NetBeans_Workspace/spring-portlets/web/lib, w którym znajduje się plik portlet-api.jar zawierający implementację tagów potrzebnych do obsługi portletów przeznaczoną dla serwera JBoss. W oknie dialogowym należy wyprać plik portlet-api.jar i potwierdzić wybór. Do projektu zostanie dodana nowa biblioteka.


      Analogiczne kroki należy powtórzyć dla wszystkich bibliotek z katalogu NetBeans_Workspace/spring-portlets/web/WEB-INF/lib . Dodanie bibliotek umożliwi ponowną kompilację całego projektu.

  7. Przedefiniowanie katalogu zawierającego pliki źródłowe Java.
    1. W oknie właściwości projektu w polu "Categories" należy wskazać opcję "Sources"
    2. W oknie "Source Packages Folders" należy wskazać katalog "src" i usunąć go.
    3. Należy dodać nowy katalog przez użycie przycisku "Add Folder" i wskazać katalog NetBeans_Workspace/spring-portlets/web/WEB-INF/src . Wynik powinien być mniej więcej następujący:

    4. Teraz należy można zamknąć okno właściwości projektu - projekt jest przygotowany do tworzenia nowego portletu.

Tworzenie nowego portletu

Aby zaprezentować kroki które należy wykonać aby stworzyć portlet WSRP napisany przy wykorzystaniu frameworku Spring Portlet MVC posłuże się banalną mutacją portletu HelloWorld.

Stworzenie klas kontrolerów Spring
Na początek stwórzmy dwie zwykłe klasy - jedna niech nazywa się GreetingController i GoodByeController - ja umieściłem je w pakiecie mw.firstspringportlet:


Konstrukcja kontrolerów opartych na Spring Portlet MVC jest bardzo zbliżona do analogicznych kontrolerów wchodzących w skład Spring Web MVC.

W najprostszym przypadku (a przecież o taki nam tu chodzi ) nasz dowolny kontroler dziedziczy z klasy AbstractController . Programista musi nadpisać metodę

ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response)

Służy ona do implementacji logiki i zwraca obiekt ModelAndView, który zawiera informacje pozwalające na przekazanie sterowania do odpowiedniego widoku.

Dla GreetingController wygląda ona następująco:

public ModelAndView handleRenderRequestInternal(RenderRequest request, RenderResponse response) throws Exception {
return new ModelAndView("greetingView");
}


Dla klasy GoodByeController postać metody jest prawie identyczna - jedyną różnicą jest to, że w parametrze konstruktora obiektu ModelAndView podana zostaje inna nazwa widoku: goodByeView.

Konfiguracja głównego kontekstu springowego

Główna część konfiguracji kontekstu springowego znajduje się w pliku
NetBeans_Workspace/spring-portlets/web/WEB-INF/context
/applicationContext.xml


Plik ten zawiera informacje wspólne dla wszystkich aplikacji (portletów) działających w kontekście Springa. Konfiguracja kontekstu w obrębie każdego z portletów jest umiejscowiona w pliku konfiguracyjnym dedykowanemu poszczególnym portletom. Informacja o lokalizacji głównego pliku konfiguracyjnego zawarta jest w pliku web.xml - deskryptorze rozmieszczenia.

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/context/applicationContext.xml</param-value>
</context-param>

Bardzo ważnym elementem konfiguracji głównego kontekstu springowego jest definicja resolvera (sic! ale nie po polskiemu) widoku. Jego zadaniem jest przekształcanie logicznych nazw podawanych we wnętrzu obiektu ModelAndView zwracanego z kontrolerów na konkretne zasoby jsp. W definicji tego beana podaje się klasę widoku, która będzie odpowiedzialna za obsługę stron JSP z wykorzystaniem tagów JSTL.

<!-- Default View Resolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="cache" value="false"/>
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>

Właściwości prefix i suffix powodują, że każda logiczna nazwa zwrócona z kontrolera poprzedzona zostanie wartością prefix i uzupełniona wartością suffix.

Jeśli w kodzie kontrolera wystąpi następujący kod:

return new ModelAndView("greetingView");

to będzie on znaczył, że w wyniku działania resolvera widoku (przy jego powyższej konfiguracji) nastąpi próba odwołania się do zasobu:

/WEB-INF/jsp/greetingView.jsp

Dla potrzeb tej prezentacji predefiniowana zawartość pliku konfiguracji kontekstu jest wystarczająca i nie wymaga nanoszenia w nim jakichkolwiek zmian. Konieczne będzie tylko stworzenie pliku konfiguracji kontekstu dedykowanego do obslugi naszego portletu.


Konfiguracja kontekstu springowego dla portletu

Springowy kontekst portletów może być przechowywany w dowolnym podkatalogu katalogu WEB-INF. W naszym przypadku pliki konfiguracyjne umiejscowione są w katalogu /WEB-INF/context/portlet .
Pierwszym krokiem będzie założenie pliku firstspringportlet.xml umiejscowionego w tymże katalogu.


W pliku tym musi zostać zdefiniowanych kilka bardzo istotnych elementów.

W pierwszej jego części definiowane są dwa beany

<bean name="greetingController" class="mw.firstspringportlet.GreetingController"/>
<bean name="goodByeController" class="mw.firstspringportlet.GoodByeController"/>

Parametr "name" określa logiczną nazwę beana, a "class" wskazanie na klasy kontrolerów, które zdefiniowane zostały w jednym z poprzednich kroków.

Każdy portlet może występować w trzech alternatywnych stanach "edit", "help" i "view". W najprostszym przypadku portlet występuje w trybie widoku "view" .

W pliku konfiguracyjnym kotekstu springowego portletu definiuje się prosty obiekt przechwytujący
PortletModeHandlerMapping którego zadaniem jest zdefiniowanie kontrolera domyślnego, który skojarzony będzie z danym trybem portletu.

<bean id="portletModeHandlerMapping" class="org.springframework.web.portlet.handler
.PortletModeHandlerMapping">

<property name="portletModeMap">
<map>
<entry key="view"><ref bean="greetingController"/></entry>
</map>
</property>
</bean>


Powyższy wpis określa, że w przypadku przełączenia portletu w tryb widoku (lub zainicjalizowania go) jako domyślny uruchomiony powinien zostać kontroler "greetingController".

Jeżeli zaistnieje potrzeba odwoływania się do różnych kontrolerów ( w zależności od parametru ), przy założeniu że stan portletu nie ulega zmianie należy zdefiniować nowy obiekt przechwytujący - PortletModeParameterHandlerMapping . Wpis dotyczący tej sytuacji wygląda następująco:

<bean id="portletModeParameterHandlerMapping" class="org.springframework.web.portlet.handler.
PortletModeParameterHandlerMapping">
<property name="portletModeParameterMap">
<map>
<entry key="view">
<map>
<entry key="bye"><ref bean="goodByeController"/></entry>
</map>
</entry>
</map>
</property>
</bean>

Taka definicja oznacza, że w przypadku gdy w żądaniu znajdzie się parameter o wartości "bye" to sterowanie należy przekazać do kontrolera o logicznej nazwie "goodByeController".

Utworzenie plików widoku

W katalogu /WEB-INF/jsp konieczne jest utworzenie dwóch plików : greetingView.jsp i goodByeView.jsp.



W treści pliku znajdował się będzie krótki komunikat tekstowy i link powodujący wysłanie do kontrolera żądania z odpowiednim parametrem. Link umieszczony we wnętrzu portletu musi mieć specjalną konstrukcję, dlatego musi on zostać wygenerowany przy wykorzystaniu dedykowanych do tego celu tagów:

<a href=" <portlet:renderURL portletMode="view">
<portlet:param name="action" value="bye"/>
</portlet:renderURL>">Say Good Bye</a>


Definiowanie portletu

Definicja portletu ma miejsce w pliku konfiguracyjnym /WEB-INF/portlet.xml . Należy dodać w nim następującą sekcję:

<portlet>
<portlet-name>firstspringportlet</portlet-name>
<portlet-class>org.springframework.web.portlet.DispatcherPortlet</portlet-class>
<init-param>
<name>contextConfigLocation</name>
<value>/WEB-INF/context/portlet/firstspringportlet.xml</value>
</init-param>
<supports>
<mime-type>text/html</mime-type>
<portlet-mode>view</portlet-mode>
</supports>
<portlet-info>
<title>This is the first portlet in Spring Technology.</title>
</portlet-info>
</portlet>

Definiuje ona portlet, a następnie jako parametr podaje lokalizację pliku z definicją kontekstu springowego portletu.

Definiowanie instancji portletu

Specyfikacja dopuszcza istnienie wielu instancji tego samego portletu. Definicja instancji odbywa się w pliku /WEB-INF/portlet-instances.xml . Jest to plik związany z kontenerem JBoss.

Należy dodać w nim następującą sekcję:

<deployment> <instance> <instance-id>FirstSpringPortletInstance</instance-id> <portlet-ref>firstspringportlet</portlet-ref> </instance> </deployment>

Zdefiniowana została instancja portletu i odwołanie do definicji portletu przez jego nazwę.

Definiowanie ułożenia instancji portletu na stronie

Każda instancja portletu może zostać w różny sposób umieszczona na stronie. Za definicję strony odpowiada plik /WEB-INF/springportlet-objects.xml

Należy w nim dodać sekcję informującą gdzie ma leżeć dana instancja portletu - odwołanie następuje przez nazwę instancji.

<window> <window-name>SpringHellowWorldPortletWindow<
/window-name>
<instance-ref>FirstSpringPortletInstance
</instance-ref>
<region>left</region> <height>3</height> </window>

Uruchomienie serwera aplikacji

Przed przeniesieniem aplikacji na serwer należy go uruchomić. W tym celu w środowisku NetBeans należy wybrać Window->Runtime, a następnie w oknie Runtime odnaleźć nazwę serwera JBoss, wskazać go myszką, kliknąć prawym przyciskiem, a z otwartego menu kontekstowego należy wybrać opcję Start.



Deploy aplikacji
Po wystartowaniu aplikacji należy najechać na plik /web/build.xml, kliknąć prawym przyciskiem, z menu kontekstowego wybrać opcję Run Target i potem deployAll. Cały projekt zostanie skomilowany spakowany do jednego pliku war i przekopiowany na serwer aplikacji.


Wyniki:

Po wpisaniu adresu:

http://localhost:3333/portal

otrzymujemy stronę główną portalu. Należy zwrócić uwagę na podanie odpowiedniego portu - 3333 nie jest opcją standardową.

Po wyświetleniu okna portalu, w portlecie Pages znajduje się link do naszej strony: Spring Portlets Sample Page

Po kliknięciu na link ukazuje się strona z naszym portletem.


Materiały:

Zarchiwizowany projekt NetBeans


Springowe portlety JSR-168

Dzisiejszy dzień straciłem na przypominanie sobie rzeczy, które już kiedyś udało mi się zrobić ( zresztą sytuacja ta nie zdarzyła się pierwszy raz ). Jako że pamięć jest zawodna - naprawiam swój błąd - w kilku zdaniach postaram opisać się problemy jakie występują przy uruchamianiu portletów Spring-może jeszcze komuś się to przyda.

Pierwotnym źródłem moich wariacji na temat wykorzystania Springa do pisania portletów jest przykład upubliczniany na stronie projektu Spring Portlets MVC. W przypadku JBoss wykorzystałem jego wersję prekonfigurowaną, przystosowaną do uruchamiania w JBoss Portal.

Na początek przytoczę kilka zasad, które pochodzą z readme dołączanego do przykładów. Należy bezwzględnie zwrócić na nie uwagę - są one bardzo ważne i mają kluczowe znaczenie przy próbach uruchomienia portletów na różnych kontenerach.

  • Należy zwrócić szczególną uwagę na to, by nie doszło do konfliktu pomiędzy bibliotekami dołączanymi do projektu a tymi, które znajdują się w współdzielonych bibliotekach kontenera.
  • Przykłady oparte są na standardzie JSP 2.0 i JSTL 1.1. Nie będą poprawnie działać na kontenerach, które nie wspierają tych standardów. W przypadku podjęcia próby uruchomienia portletów na starszych kontenerach należy usunąć z pliku web.xml wszystkie wpisy dotyczące taglibów JSTL.
  • Bezpośrednie umieszczenie przykładów na serwerze jest niewystarczające. W zależności od rodzaju kontenera, aplikacja musi zostać odpowiednio uruchomiona (deploy). Kontenery nie zawsze wyposażone są w mechanizmy auto-deploy, dlatego często wymagane modyfikacje plików konfiguracyjnych należy przeprowadzić samemu. Najczęstsze zmiany to:
    1. Modyfikacje pliku web.xml. Należy przede wszystkim zamieścić informacje w jaki sposób kontener obsługuje kontekst portletów. Np. może to być jeden serwlet sterujący, bądź jeden serwlet dedykowany jednemu portletowi.

    2. Należy bezwzględnie pamiętać o dostarczeniu bibliotek zawierających zdefiniowane w standardzie JSR-168 tagi JSP, oraz klasy implementujące je. Implementacja tych tagów, jest zależna od rodzaju kontenera. Najczęściej są one zgrupowane w pliku portlet.tld (w przypadku przykładów umieszczony jest w katalogu /lib). Dla kontenerów realizujących specyfikację Servlet 2.4 biblioteki te mogą być udostępnione przez dołączenie ich do class-loadera (np /WEB-INF/lib), dla innych konterów powinny zostać dołączaone jako plik znajdujący się w podkatalogu /WEB-INF.

    3.Należy pamiętać, że czasami kontenery wymagają konfiguracji dodatkowej.
Jak do tej pory własne springowe portlety WSRP udało mi się uruchomić na dwóch kontenerach - JBoss(prekonfigurowany pod portal) i OC4J. Następnie udało mi się je zarejestrować w dwóch portalach JBoss Portal i Oracle Portal. Niestety nie było prosto bo dokumentacja jest mocno dziurawa i niekompletna - żeby uchronić się przed ulotnością wiedzy spróbuję to trochę opisać.

Wykorzystywane narzędzia: