O roli języka Java i J2EE w Oraclu

część 4

Sebastian Wyrwał

Przykład projektowy

W poprzednich trzech odcinkach dokonano – pobieżnego zresztą,
przeglądu elementarnych zagadnień związanych z J2EE. Aby przejść do zagadnień
bardziej zaawansowanych, konieczne jest przedstawienie i przeanalizowanie
prostego, ale w miarę kompletnego przykładu. Przykładem tym będzie bardzo prosty
prototyp systemu służącego do przeprowadzania testów wielokrotnego wyboru np. w
szkole. Na potrzeby niniejszego artykułu dokonano bardzo wielu uproszczeń –
system jednak umożliwia przeprowadzenie prostego testu.

Opis systemu do testowania w szkole

System służy do przeprowadzania testów uczniów i umożliwia
zdefiniowanie wielu testów składających się z pewnej ilości pytań. Umożliwia
definiowanie profili testowania, przy czym w systemie będącym prototypem możliwe
jest zdefiniowanie tylko jednego testu. System ma dwie kategorie użytkowników:
nauczycieli i uczniów. Ci pierwsi mogą definiować profile testowania, testy i
pytania (w systemie – prototypie tylko pytania). System gromadzi informacje o
odpowiedziach udzielanych przez uczniów na pytania testowe i postaci samych
pytań testowych. Dane te muszą być odporne na ewentualną późniejszą modyfikację
pytań testowych. System ma umożliwiać drukowanie zestawień i raportów w różnych,
nie określonych na tym etapie postaciach. W systemie – prototypie założono, iż
każde pytanie ma maksymalnie 4 odpowiedzi, z których może być 0, 1, 2, 3 lub 4
odpowiedzi poprawne. Dobrze, aby w przyszłości można było definiować inne typy
pytań, ale w systemie prototypowym wystarczy uwzględnić wyłącznie pytania
testowe. System ma zapewniać rozliczalność użytkowników.

Decyzje projektowe

Pierwsza z decyzji dotycząca tego, czy system w ogóle ma być
implementowany z użyciem J2EE jest o tyle ciekawa, że porównując czas
wytworzenia rzeczywistego systemu testującego (o nieco innym charakterze)
napisanego bez J2EE (Java i dostęp do BD przez JDBC) z antycypowanym na
podstawie niniejszego prototypu czasem wytworzenia przy użyciu J2EE można dojść
do wniosku, że oba czasy są tego samego rzędu i wynikają bardziej ze znajomości
technologii i oferowanych przez dane środowisko programistyczne udogodnień.

Oczywiście nie trudno zgadnąć, że dla większych systemów
przewaga J2EE będzie oczywista. Poza tym, używając tego ostatniego łatwiej
uniknąć wielu błędów. Druga
z decyzji dotyczy tego, czy „cały” system ma być stworzony z wykorzystaniem
J2EE. Należy bowiem pamiętać, że alternatywą dla użycia beanów encji (ang.
entity beans) jest użycie innych rozwiązań zapewniających ich trwałość, jak
choćby Hibernate. W takim przypadku używane będą jedynie sesyjne beany (ang.
session beans). Przesłanką, która przemawiała by, zdaniem autora, za takim
rozwiązaniem jest potrzeba wykorzystania dużych ilości już istniejącego kodu
napisanego w języku Java.

Kolejna decyzja może dotyczyć sposobu realizacji aplikacji
klienckiej. Można zastosować dedykowaną aplikację napisaną w języku Java. Można
również, co wydaje się bardziej sensowne, zastosować przeglądarkę i dostęp
poprzez aplikację webową. W przypadku wyboru tej ostatniej pozostaje wybranie
sposobu jej implementacji. Rozwiązaniem mogą być JSP i Servlety, JSP i Struts,
Spring Framework i inne.

Następna decyzja projektowa dotyczy wyboru środowiska
programistycznego, serwera aplikacji i motoru BD. Dalsze decyzje dotyczyć mogą
sposobu zarządzania projektem informatycznym, podziału pracy, przyjętych
konwencji kodowania itp. Zagadnienia związane z projektowaniem, choć bardzo
ważne, pozostają poza tematyką niniejszego cyklu. Wiele innych decyzji
projektowych wynikać będzie z wymagań (funkcjonalnych i niefunkcjonalnych)
stawianych systemowi.

Na potrzeby niniejszego artykułu podjęto następujące decyzje
projektowe:

  • Całość projektu będzie implementowana z użyciem J2EE,
    z tym, że w obrębie aplikacji webowej zostanie użyty szkielet Struts. Dostęp
    obydwu kategorii użytkowników poprzez przeglądarkę.
  • Jako serwer aplikacji zostanie użyty JBoss.
  • Każdej kategorii użytkowników będzie odpowiadać jeden
    sesyjny EJB.
  • Zagadnienia związane z uwierzytelnianiem, bezpieczeństwem
    itp. zostaną podjęte dopiero w przyszłości. Podjęcie decyzji projektowych w tej
    materii wymaga przeanalizowania wpływu użycia bezpieczeństwa deklaratywnego
    opartego na rolach, definiowanego przez specyfikację J2EE i rozwiązań
    oferowanych przez serwer aplikacji. Pod uwagę mogą być brane również
    niestandardowe rozwiązania, nie opierające się na przed chwilą wymienionych.

Rozwiązanie

Z przedstawionego wcześniej opisu i powyższych decyzji
projektowych wynika, iż w systemie będą dwa sesyjne EJB. Zdecydowano, iż będą
one stanowe.

Aplikacja EJB

/**

*  @ejb.bean name="UslugiDlaNauczyciela"
*
  display-name="Name for UslugiDlaNauczyciela"
*
  description="Description for UslugiDlaNauczyciela"
*
  jndi-name="ejb/UslugiDlaNauczyciela"
*
  type="Stateful"
*
  view-type="both"
*  @ejb.util generate="physical"
*/
public class UslugiDlaNauczyciela implements SessionBean {
   private SessionContext context;
   JednostkaTestuLocalHome jednostkaTestuLocalHome;

   public UslugiDlaNauczyciela() {

      super();

   }

  
/**

  
* @throws EJBException

   */

   public void setSessionContext(SessionContext newContext)

      throws EJBException {

  
  
context = newContext;

  
}


  
/* (non-Javadoc)

   * @see javax.ejb.SessionBean#ejbRemove()

   */

   public void ejbRemove() throws EJBException, RemoteException {

   }


   /* (non-Javadoc)

   * @see javax.ejb.SessionBean#ejbActivate()

   */

   public void ejbActivate() throws EJBException, RemoteException {

   }

   /* (non-Javadoc)

   * @see javax.ejb.SessionBean#ejbPassivate()

   */

   public void ejbPassivate() throws EJBException, RemoteException {

   }


  
/**

  
* @throws CreateException

   * @ejb.create-method

   */

   public void ejbCreate(String imie, String nazwisko, String haslo) throws
CreateException {

  
  
try {

      jednostkaTestuLocalHome = JednostkaTestuUtil.getLocalHome();


      } catch (NamingException e) {

           
e.printStackTrace();

      }

   }


   /**
   * @ejb.interface-method view-type="both"
   * @param v
   */
   public void dodajPytania(Vector v){

   for (Iterator iter = v.iterator(); iter.hasNext();) {
   JednostkaTestuValue element = (JednostkaTestuValue) iter.next();
   try {
   JednostkaTestuLocal jtl=jednostkaTestuLocalHome.create();
                       jtl.setPytanie(element.getPytanie());
   jtl.setBodp1(element.getBodp1());
   jtl.setBodp2(element.getBodp2());
   jtl.setBodp3(element.getBodp3());
   jtl.setBodp4(element.getBodp4());

   jtl.setOdp1(element.getOdp1());
   jtl.setOdp2(element.getOdp2());
   jtl.setOdp3(element.getOdp3());
   jtl.setOdp4(element.getOdp4());
   } catch (CreateException e) {
      e.printStackTrace();
   }}}
}

Widać, że w prototypowej wersji tego sesyjnego EJB istnieje
wyłącznie jedna metoda biznesowa; służy ona do dodania dowolnej ilości pytań
testowych. Warto zwrócić uwagę, iż metoda ejbCreate stanowego sesyjnego EJB może
mieć argumenty; w wypadku bezstanowego sesyjnego EJB, musi być bezargumentowa.

W sesyjnym i stanowym EJB UslugiDlaUcznia1 zdefiniowano dwie
metody biznesowe. Pierwsza z nich służy do pobrania zestawu pytań testowych,
druga zaś umożliwia zapisanie historii testowania ucznia.

/**
*
  
@ejb.interface-method view-type="both"
*
  
@return
*/
public Vector pobierzPytania(){
    Vector doOdeslania = new Vector();
try {
    Collection v = jednostkaTestuLocalHome.findAll();

for (Iterator iter = v.iterator(); iter.hasNext();) {
    JednostkaTestuLocal element = (JednostkaTestuLocal) iter.next();
    if (element!=null){
    JednostkaTestuValue jtv = new JednostkaTestuValue(element.getId(),

    element.getPytanie(),
    element.getOdp1(),
    element.getOdp2(),
    element.getOdp3(),
    element.getOdp4(),
    element.getBodp1(),
    element.getBodp2(),
    element.getBodp3(),
    element.getBodp4()
    doOdeslania.add(jtv);
    }
    } catch (FinderException e) {
            e.printStackTrace();
    }
    return doOdeslania;
    }

/**
* @ejb.interface-method view-type="both"
* @param v
*/

public void zapiszHistorie(Vector v){
    try {
    UczenLocal ul = uczenLocalHome.create();
    ul.setImie(_imie);
    ul.setNazwisko(_nazwisko);
    for (Iterator iter = v.iterator(); iter.hasNext();) {
        HistoriaUSEEValue element = (HistoriaUSEEValue) iter.next();
        HistoriaUSEELocal h = historiaLocalHome.create();
        h.setUczenLokal(ul);

        h.setBodp1(element.getBodp1());
        //analogicznie dla Bodp2, 3, 4

        h.setOdp1(element.getOdp1());
        //analogicznie dla odp2, 3, 4

        h.setBuodp1(element.getBuodp1());
        //analogicznie dla Budp2, 3, 4
        h.setPytanie(element.getPytanie());
        }
        } catch (CreateException e) {
                e.printStackTrace();
    }
}

Poniżej zostaną przedstawione dwa beany encje: Uczeń
i HistoriaUSEE. Są one w relacji 1:n, co oznacza, iż uczeń może mieć wiele
wpisów w historii testowania a dany wpis dotyczy konkretnego ucznia. EJB encja
odpowiadająca uczniowi ma atrybuty: id, imię oraz nazwisko.

/**
* @ejb.bean name="Uczen"
*  display-name="Name for Uczen"
*  description="Description for Uczen"
*  jndi-name="ejb/UczenHome"
*  type="CMP"
*  cmp-version="2.x"
*  view-type="local"
*  local-jndi-name="ejb/UczenLocalHome"
*  primkey-field="id"
* @ejb.persistence table-name="tuczen"
* @ejb.value-object match="*" name="UczenEJB"
* @jboss.persistence create-table="true" pk-constraint="true"
* @ejb.util generate="physical"
*/
public abstract class UczenEJB implements EntityBean {

    private EntityContext context;

    public UczenEJB() {
        super();
    }
/**
    * @throws CreateException
    * @ejb.create-method
    */
    public String ejbCreate() throws CreateException {
        setId(UczenUtil.generateGUID(this));
        return getId();
    }

    /**
    * @throws CreateException
    */
    public void ejbPostCreate() throws CreateException {
    }

    /**
    * @throws EJBException
    */
    public void setEntityContext(EntityContext newContext) throws EJBException{
        context = newContext;
    }

    /**
    * @throws EJBException
    */
    public void unsetEntityContext() throws EJBException {
        context = null;
    }

    /* (non-Javadoc)
    * @see javax.ejb.EntityBean#ejbRemove()
    */
    public void ejbRemove()
        throws RemoveException,
        EJBException,
        RemoteException {}

    /* (non-Javadoc)
    * @see javax.ejb.EntityBean#ejbActivate()
    */
    public void ejbActivate() throws EJBException, RemoteException {}

    /* (non-Javadoc)
    * @see javax.ejb.EntityBean#ejbPassivate()
    */
    public void ejbPassivate() throws EJBException, RemoteException {}

    /* (non-Javadoc)
    * @see javax.ejb.EntityBean#ejbLoad()
    */
    public void ejbLoad() throws EJBException, RemoteException {}

    /* (non-Javadoc)
    * @see javax.ejb.EntityBean#ejbStore()
    */
    public void ejbStore() throws EJBException, RemoteException {}

    /**
    *@ejb.pk-field
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fid"
    *@return
    */
    public abstract String getId();

    /**
    *@ejb.interface-method view-type = "local"
    *@param id
    */
    public abstract void setId(String id);

    /**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fimie"
    *@return
    */
    public abstract String getImie();

    /**
    *@ejb.interface-method view-type = "local"
    *@param imie
    */
    public abstract void setImie(String imie);
    /**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fnazwisko"
    *@return
    */
    public abstract String getNazwisko();

    /**
    *@ejb.interface-method view-type = "local"
    *@param nazwisko
    */
    public abstract void setNazwisko(String nazwisko);

    /**
    *@ejb.interface-method
    *    view-type="local"
    *
    *@ejb.relation
    *    name="WynikiTestowUcznia"
    *    target-role-name="wynik-testu-ucznia"
    *    target-ejb="HistoriaUSEE"
    *    target-multiple="Many"
    *    role-name="uczen-robil-ten-test"
    *
    *@return
    */
    public abstract Collection getTesty();

    /**
    *@ejb.interface-method view-type = "local"
    *@param testy
    */
    public abstract void setTesty(Collection testy);
}

W beanie HistoriaEJB zdefiniowano następujące atrybuty

/**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fpytanie"
    *@return
    */
    public abstract String getPytanie();

    /**
    *@ejb.interface-method view-type = "local"
    *@param pytanie
    */
    public abstract void setPytanie(String pytanie);

    /**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fodp1"
    *@return
    */
    public abstract String getOdp1();

    /**
    *@ejb.interface-method view-type = "local"
    *@param odp1
    */
    public abstract void setOdp1(String odp1);

    //analogicznie dla odp2,3,4

    /**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fbodp1"

    *@return
    */
    public abstract int getBodp1();

    /**
    *@ejb.interface-method view-type = "local"
    *@param bodp1
    */
    public abstract void setBodp1(int bodp1);

    //analogicznie dla Bodp2,3,4

    /**
    *@ejb.interface-method view-type="local"
    *@ejb.persistence column-name = "fbuodp1"

    *@return
    */
    public abstract int getBuodp1();

    /**
    *@ejb.interface-method view-type = "local"
    *@param buodp1
    */
    public abstract void setBuodp1(int buodp1);

    //analogicznie dla Budop2,3,4

    /**
    *@ejb.interface-method view-type="local"
    *
    *@ejb.relation
    * name="WynikiTestowUcznia"
    * role-name="wynik-testu-ucznia"
    * target-ejb="Uczen"
    * target-multiple="One"
    * target-role-name="uczen-robil-ten-test"
    *
    *@jboss.relation
    * related-pk-field="id"
    * fk-column="uczen_id"
    * fk-constraint="true"
    *
    *@return
    */
    public abstract UczenLocal getUczenLokal();

    /**
    *@ejb.interface-method view-type = "local"
    *@param uczen
    */
    public abstract void setUczenLokal(UczenLocal uczen);

Bean JednostkaTestowa zdefiniowany jest analogicznie do
historii, z tym, że nie posiada atrybutów buodp1..4, które reprezentują
odpowiedzi udzielane przez ucznia i nie ma metod getUczenLokal oraz
setUczenLocal, ponieważ pytanie nie może być w relacji z uczniem.

Aplikacja Webowa

W obrębie prototypu aplikacji webowej wyodrębniono
następujące przypadki użycia:

  • rozpocznij
  • ustawPytanie
  • róbTest
  • definiujTest
  • wyniki
  • koniec

Każdemu z tych PU odpowiada Akcja Struts np. RozpocznijAction
pełniąca rolę kontrolera wzorca MVC, FormBean pełniący rolę modelu (np.
RozpocznijForm) i strona jsp będąca (widokiem dla omawianego PU będzie to
rozpocznij.jsp). Wyjątkiem jest akcja ustawPytanie, która nie ma ani widoku ani
własnego modelu. Trzy akcje: ustawPytanie, definiujTest i rotTest korzystają z
tego samego modelu QuestionForm.

1. Formy

Przykładowa Forma rozpocznijForm wygląda następująco:

public class RozpocznijForm extends ActionForm {

boolean nauczyciel;
String imie, nazwisko;
public String getImie() {
return imie;
}
public void setImie(String imie) {
this.imie = imie;
}
public String getNazwisko() {
return nazwisko;
}
public void setNazwisko(String nazwisko) {
this.nazwisko = nazwisko;
}

public void reset(ActionMapping mapping, HttpServletRequest request) {
nauczyciel = false;

}

      
public boolean isNauczyciel() {
             
return nauczyciel;
      
}
      
public void setNauczyciel(boolean nauczyciel) {
             
this.nauczyciel = nauczyciel;
      
}
}

Generalnie każda z form jest „zwykłym” beanem (nie EJB), więc
dla każdego atrybutu ma zdefiniowane odpowiednie metody getXXX i setXXX. Metoda
reset ustawia wartość atrybutu nauczyciel jako fałsz; jest to wymagane dla
działania kontrolki checkbox na stronie rozpocznij.jsp.

Dla oszczędności miejsca nie będzie podany kod pozostałych
Form – mają one następujące atrybuty:

QuestionForm (wspólna dla definiowania pytań testowych i
ich rozwiązywania):

       private String doo;
      
private String id=null;
      
private String pytanie = null;
      
private String odp1, odp2, odp3, odp4;
      
private boolean bodp1, bodp2, bodp3, bodp4;

Atrybuty typu boolean muszą być ustawiane na fałsz
w metodzie reset ponieważ w widoku reprezentuje je kontrolka CheckBox. Znaczenie
atrybutów jest następujące:

  • doo – parametr akcji;
  • id – klucz główny pytania testowego;
  • pytanie – treść pytania testowego;
  • odp1-4 – treści odpowiedzi wariantowych;
  • bodp1-4 – przy definiowaniu ćwiczeń określa czy dana
    odpowiedź wariantowa jest prawidłowa, przy rozwiązywaniu określa, czy uczeń
    wybrał tą odpowiedź wariantową.

WynikiForm zawiera informacje o podsumowaniu, które są
reprezentowane użytkownikowi. Uczniowi prezentuje się wyniki testu,
nauczycielowi zestawienie dodanych pytań.

boolean obliczone = false;
Collection wyniki = null;
String naglowek;

Znaczenie tych atrybutów jest następujące:

Atrybut obliczone określa, czy akcja WynikiAction ustawiła
już zawartość kolekcji wyniki. Ta ostatnia zawiera odpowiednie posumowanie.
Atrybut nagłówek zawiera napis określający, czy prezentowane wyniki to
odpowiedzi ucznia, czy podsumowanie dodanych pytań. Przejścia
z akcji do akcji zorganizowano dla uproszczenia w ten sposób, że każda akcja
realizuje forward do widoku akcji docelowej. Czyli akcja robTest posiada forward
do strony wyniki.jsp. Oczywiście postępowanie takie jest kwestią pewnego wyboru
(niekoniecznie najwłaściwszego) i rodzi pewne konsekwencje. Takie jak
sprawdzanie, czy już ustawiono wyniki w akcji wyniki. Jeśli ich jeszcze nie
ustawiono, następuje forward do akcji WynikiAction. Wyjątkiem omawianego sposobu
realizacji przejść jest akcja UstawPytanieAction.

KoniecForm nie zawiera atrybutów.

2. Akcje

Dla oszczędności miejsca zostaną podane wyłącznie metody
execute bez klas.

  • RozpocznijAction – na postawie danych wprowadzonych przez
    użytkownika do widoku (rozpocznij.jsp) określa, czy użytkownik jest
    nauczycielem, czy uczniem. W obrębie sesji tworzony jest atrybut PYT będący
    wektorem pytań, atrybuty IMIE, NAZWISKO, oraz zdalny interfejs do sesyjnego EJB
    – UDN (UsługiDlaNauczyciela) bądź UDU (Usługi dla ucznia)
    w zależności do tego, czy użytkownik jest nauczycielem czy uczniem. Jeżeli
    użytkownik jest uczniem to pobierany jest zestaw pytań testowych, które będą
    użyte do testowania
    i ustawiany jest atrybut sesji LICZNIK określający, które pytanie testowe jest
    aktualnie rozwiązywane przez ucznia. Warto podkreślić, iż od razu pobierany jest
    cały zestaw pytań testowych. W zależności od rodzaju użytkownika następuje
    przejście do rozwiązywania testu lub definiowania testu.
public ActionForward execute(
   
ActionMapping mapping,
   
ActionForm form,
   
HttpServletRequest request,
   
HttpServletResponse response) {

   
RozpocznijForm rozpocznijForm = (RozpocznijForm) form;
   
Vector pytania = new Vector();
   
request.getSession().setAttribute("PYT",pytania);
   
String tryb="";

   
String imie = rozpocznijForm.getImie();
   
String nazwisko = rozpocznijForm.getNazwisko();
   
request.getSession().setAttribute("IMIE",imie);
   
request.getSession().setAttribute("NAZWISKO",nazwisko);

   
if (rozpocznijForm.isNauczyciel()){
   
tryb=new String("N");
   
request.getSession().setAttribute("TRYB",tryb);
   
try {
   
UslugiDlaNauczycielaHome udnh = UslugiDlaNauczycielaUtil.getHome();
   
UslugiDlaNauczyciela udn=udnh.create(imie,nazwisko,null);

           
request.getSession().setAttribute("UDN",udn);
   
} catch (Exception e) {

   
e.printStackTrace();
   
}
   
return mapping.findForward("definiujTest");

    }else{

   
tryb=new String("U");

    try {

    UslugiDlaUczniaHome uduh = UslugiDlaUczniaUtil.getHome();

    UslugiDlaUcznia udu = uduh.create(imie,nazwisko,"","");
           
request.getSession().setAttribute("UDU",udu);

           
request.getSession().setAttribute("TRYB",tryb);
   
Vector pyt = udu.pobierzPytania();

   
for (Iterator iter = pyt.iterator(); iter.hasNext();) {
   
JednostkaTestuValue element = (JednostkaTestuValue) iter.next();
   
PytanieTestoweFake fake1 = new PytanieTestoweFake(element);
   
pytania.add(fake1);
   
}
   
} catch (Exception e) {
       
e.printStackTrace();
   
}
   
Integer licznik = new Integer(0);
   
request.getSession().setAttribute("LICZ",licznik);

   
return mapping.findForward("robTest");
}}

  • DefiniujTestAction – w tej bardzo prostej akcji, z modelu
    pobierana jest definicja właśnie dodanego pytania testowego. Tworzone jest nowe
    pytanie, które jest dodane do wektora, który przechowuje pytania w ramach sesji.
    Klasa pomocnicza PytanieTestoweFake pobiera i ustawia dane z modelu. Posiada
    również metody pozwalające konwertować „swoje” dane do obiektów:
    JednostkaTestuValue i HistoriaValue. Z akcji tej – w zależności od wartości parametru doo – następuje przejście do
    dalszego dodawania pytań lub zakończenie wykonywania pytań. Parametr ten jest
    ustawiany na poziomie widoku przez bardzo prosty skrypt w języku Java Script
    w zależności od tego, który przycisk został wciśnięty przez użytkownika.
public ActionForward execute(
    ActionMapping mapping,
    ActionForm form,
    HttpServletRequest request,
    HttpServletResponse response) {
    QuestionForm definiujTestForm = (QuestionForm) form;

    if (definiujTestForm.getDoo().equalsIgnoreCase("dalej")){

    try {
    Vector pytania=(Vector)request.getSession().getAttribute("PYT");
    pytania.add(new PytanieTestoweFake(definiujTestForm));
    } catch (RuntimeException e) {
            return
mapping.findForward("blad");

    }
    form.reset(mapping,request);
            return
mapping.findForward("dalej");
    }
    if (definiujTestForm.getDoo().equalsIgnoreCase("koniec")){
            return
mapping.findForward("koniec");
    }
    return null;
    }

  • UstawPytanieAction – jeżeli uczeń nie wykonał wszystkich
    pytań testowych, pobierane jest kolejne i jego dane przekazywane są w modelu, po
    czym następuje przejście do akcji RobTestAction, a konkretnie do jej widoku.
    Jeżeli uczeń wykonał wszystkie pytania, następuje przejście do akcji wyniki.
public ActionForward execute(
   
ActionMapping mapping,
   
ActionForm form,
   
HttpServletRequest request,
   
HttpServletResponse response) {

try {
   
Vector pytania = (Vector)request.getSession().getAttribute("PYT");
   
Integer licznik = (Integer)request.getSession().getAttribute("LICZ");

    if (licznik.intValue()>=pytania.size()){
          
return mapping.findForward("koniec");
   
}

   
PytanieTestoweFake pytanieFake = (PytanieTestoweFake)pytania.elementAt(licznik.intValue());
   
pytanieFake.setInitial((QuestionForm)form);
          
request.getSession().setAttribute("AKT",pytanieFake);
          
request.getSession().setAttribute("LICZ",new Integer(licznik.intValue()+1));

   
} catch (RuntimeException e) {
          
mapping.findForward("blad");
   
}
          
return mapping.findForward("robTest");
   
}

  • RobTestAction – akcja pobiera z modelu odpowiedzi dane
    potrzebne do stworzenia wpisu w historii (odpowiedzi ucznia, definicję pytania),
    dokonuje oceny. W zależności od tego, czy uczeń chce kontynuować test, następuje
    przejście do dalszego jego wykonywania lub do wyników.
public ActionForward execute(
   
ActionMapping mapping,
   
ActionForm form,
   
HttpServletRequest request,
   
HttpServletResponse response) {
   
QuestionForm robTestForm = (QuestionForm) form;

   
try {
   
if (robTestForm.getDoo().equalsIgnoreCase("dalej")){
       
ocen(request,robTestForm);
   
return mapping.findForward("dalej");

   
}
   
if (robTestForm.getDoo().equalsIgnoreCase("koniec")){
       
ocen(request,robTestForm);
   
return mapping.findForward("koniec");
   
}
   
} catch (RuntimeException e) {
   
return mapping.findForward("blad");

   
}
   
return mapping.findForward("blad");

   
}
   
private void ocen(HttpServletRequest request,QuestionForm form){

   
PytanieTestoweFake pytanie=(PytanieTestoweFake)request.getSession().getAttribute("AKT");
   
pytanie.test(form);

   
}

WynikiAction – jeżeli użytkownikiem jest uczeń, wówczas akcja
prezentuje wyniki wykonania testu i odsyła do aplikacji EJB historię testu tego
ucznia; jeżeli użytkownik jest nauczycielem, to wyświetlane jest zestawienie
dodanych pytań i zdefiniowane pytania odsyłane są do aplikacji EJB.

public class WynikiAction extends Action {

public ActionForward execute(
   
ActionMapping mapping,
   
ActionForm form,
   
HttpServletRequest request,
   
HttpServletResponse response) {
   
WynikiForm wynikiForm = (WynikiForm) form;

   
if (wynikiForm.isObliczone()==false){
   
wynikiForm.setObliczone(true);

   
try {
   
Vector pytania = (Vector)request.getSession().getAttribute("PYT");
   
Vector odpowiedzi = new Vector();
   
String tryb = (String)request.getSession().getAttribute("TRYB");
   
Vector doOdeslania = new Vector();

   
if (tryb.equalsIgnoreCase("N")){
   
wynikiForm.setNaglowek("Dodano następujące pytania:");
   
UslugiDlaNauczyciela udn = (UslugiDlaNauczyciela)request.getSession().getAttribute("UDN");

   
for (Iterator iter = pytania.iterator(); iter.hasNext();) {
   
PytanieTestoweFake element = (PytanieTestoweFake) iter.next();
   
JednostkaTestuValue jtVal = element.getJednostkaTestowaValue();

   
if (jtVal!=null){
           
doOdeslania.add(jtVal);
}
}
try {
    udn.dodajPytania(doOdeslania);
} catch (RemoteException e1) {
    e1.printStackTrace();
    return mapping.findForward("blad");
}
}
if (tryb.equalsIgnoreCase("U")){
wynikiForm.setNaglowek("Wyniki testu:");
UslugiDlaUcznia udu = (UslugiDlaUcznia)request.getSession().getAttribute("UDU");

for (Iterator iter = pytania.iterator(); iter.hasNext();) {
   
PytanieTestoweFake element = (PytanieTestoweFake) iter.next();
   
HistoriaUSEEValue hValue = element.getHistoriaValue();

   
if (hValue!=null){

            doOdeslania.add(hValue);
   
}

   
}
    try {
           
udu.zapiszHistorie(doOdeslania);
   
} catch (RemoteException e1) {
   
e1.printStackTrace();
   
return mapping.findForward("blad");

    }

   
}

   
for (Iterator iter = pytania.iterator(); iter.hasNext();) {
    PytanieTestoweFake element = (PytanieTestoweFake) iter.next();
            odpowiedzi.add(element.getInfo());
    }
    wynikiForm.setWyniki(odpowiedzi);
   
} catch (RuntimeException e) {
       
return mapping.findForward("blad");
   
}

               
return mapping.findForward("podsumuj");
        }

       
return mapping.findForward("koniec");
   
}

}

3. Strony JSP

Dla zaoszczędzenia miejsca zostanie zaprezentowana wyłącznie
zwartość między znacznikami <BODY></BODY>. W prawdziwym systemie właściwości takie jak klucze główne id, czy
parametry akcji nie byłyby prezentowane użytkownikowi (użycie tagu <html: hidden>).

  • rozpocznij.jsp strona ta będąca widokiem akcji
    RozpocznijAction pozwala na wprowadzenie danych (imię, nazwisko) użytkownika i
    określenie, czy użytkownikiem jest uczeń, czy nauczyciel.
<h2>Witamy w systemie TESTOWYM</h2>
<html:form action="/rozpocznij" enctype="multipart/form-data" method="POST">
Imię: <html:text property="imie"/>
Nazwisko: <html:text property="nazwisko"/>
Nauczyciel? <html:checkbox property="nauczyciel"></html:checkbox>
<html:submit/><html:cancel/>
</html:form>
  • definiowaniePytania.jsp

W sekcji HEAD należy zdefiniować następujący skrypt
ustawiający wartość parametru doo, w zależności od naciśniętego przycisku

        <SCRIPT>
                function set(target){
                    document.forms[0].doo.value=target;
                }
        </SCRIPT>
(zawartość w sekcji <BODY>)

<h2>Definiowanie nowego pytania</h2>
<jsp:useBean id="questionForm" class="com.yourcompany.struts.form.QuestionForm"
scope="session" />
<html:form action="/definiujTest" method="POST" enctype="multipart/form-data">
Pytanie: <html:text property="pytanie" size="80"/>
Odp. 1: <html:checkbox property="bodp1"/><html:text property="odp1" />
<%—analogicznie dla odp2,3,4 —%>
id: <html:text property="id"/>
doo: <html:text property="doo"/>
<html:submit onclick="set(dalej);" value="Dalej"/>
<html:submit onclick="set(koniec);" value="Zakończ"/>
</html:form>

  • robTest.jsp – jest to widok akcji RobTest. Pozwala on na
    rozwiązanie pytania testowego przez ucznia.

(W sekcji HEAD tej strony należy zdefiniować skrypt zaprezentowany dla strony
definiowanie pytania.)

<body>
    <h2>Definiowanie nowego pytania</h2>
    <jsp:useBean id="questionForm" class="com.yourcompany.struts.form.QuestionForm"
scope="session" />
            <html:form action="/robTest" method="POST" enctype="multipart/form-data">
Pytanie: <bean:write name="questionForm" property="pytanie"/>Odp. 1: <html:checkbox
property="bodp1"/></td><td><bean:write name="questionForm" property="odp1" />
<%— analogicznie dla odp2,3,4—%>
id: <html:text property="id"/>
doo: </td><td colspan="2"><html:text property="doo"/>
<html:submit onclick="set(dalej);" value="Dalej"/>
<html:submit onclick="set(koniec);" value="Zakończ"/>
</html:form>
  • wyniki.jsp – widok ten prezentuje wyniki rozwiązanego testu
    lub zestawienie dodanych pytań.
<jsp:useBean id="wynikiForm" class="com.yourcompany.struts.form.WynikiForm"
scope="session"/>
<logic:equal name="wynikiForm" property="obliczone" value="false">
    <logic:forward name="wyniki" />
</logic:equal>

<h2><bean:write name="wynikiForm" property="naglowek"/></h2>

    <logic:notEmpty name="wynikiForm" property="wyniki">

    <logic:iterate id="w" name="wynikiForm" property="wyniki">

    <bean:write name="w"/>
</logic:iterate>
</logic:notEmpty>

  • blad.jsp – wypisuje informacje o błędzie. Dla
    zaoszczędzenia miejsca kod tej strony nie jest prezentowany.

4. Pik struts-config.xml – plik ten jest w zasadzie
„sercem” prezentowanego prototypu aplikacji webowej. W sekcji from-beans
zdefiniowano 4 beany będące modelami dla akcji występujących w prototypie. W
dalszej części pliku zdefiniowano 3 globalne forwardy. Największą część tego
pliku stanowią definicje akcji.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts-config PUBLIC "-//Apache Software Foundation//DTD Struts
Configuration 1.1//EN" "http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd">
<struts-config>
    <data-sources />

<form-beans >

<form-bean name="questionForm" type="com.yourcompany.struts.form.QuestionForm"/>

<form-bean name="rozpocznijForm" type="com.yourcompany.struts.form.RozpocznijForm"
/>

<form-bean name="wynikiForm" type="com.yourcompany.struts.form.WynikiForm" />

<form-bean name="koniecForm" type="com.yourcompany.struts.form.KoniecForm" />

</form-beans>

    <global-exceptions />
    <global-forwards >
        <forward name="welcome" path="/default.do" redirect="true" />
        <forward name="blad" path="/form/blad.jsp" />
        <forward name="wyniki" path="/wyniki.do"></forward>

    </global-forwards>

<action-mappings >

    <action
    attribute="questionForm"
    input="/form/robTest.jsp"
    name="questionForm"
    parameter="doo"
    path="/robTest"
    scope="session"
    type="com.yourcompany.struts.action.RobTestAction">
        <forward name="dalej" path="/ustawPytanie.do" />
        <forward name="koniec" path="/form/wyniki.jsp" />
    </action>

    <action
    attribute="questionForm"
    input="/form/definiujTest.jsp"
    name="questionForm"
    parameter="doo"
    path="/definiujTest"
    type="com.yourcompany.struts.action.DefiniujTestAction"
    validate="false">
        <forward name="dalej" path="/form/definiujTest.jsp" />
        <forward name="koniec" path="/form/wyniki.jsp" />
    </action>

    <action
    attribute="rozpocznijForm"
    input="/form/rozpocznij.jsp"
    name="rozpocznijForm"
    path="/rozpocznij"
    scope="request"
    type="com.yourcompany.struts.action.RozpocznijAction">
        <forward name="definiujTest" path="/form/definiujTest.jsp" />
        <forward name="robTest" path="/ustawPytanie.do" />
    </action>

    <action
    attribute="wynikiForm"
    input="/form/wyniki.jsp"
    name="wynikiForm"
    path="/wyniki"
    scope="session"
    type="com.yourcompany.struts.action.WynikiAction">
        <forward name="podsumuj" path="/form/wyniki.jsp" />
        <forward name="koniec" path="/form/koniec.jsp" />
        <forward name="wyniki" path="/wyniki.do"></forward>
    </action>

    <action
    attribute="koniecForm"
    input="/form/koniec.jsp"
    name="koniecForm"
    path="/koniec"
    scope="request"
    type="com.yourcompany.struts.action.KoniecAction" />

    <action forward="/form/rozpocznij.jsp" path="/default" unknown="true"/>

    <action
    attribute="questionForm"
    name="questionForm"
    path="/ustawPytanie"
    type="com.yourcompany.struts.action.UstawPytanieAction"
    validate="false">
        <forward name="koniec" path="/form/wyniki.jsp" />
        <forward name="robTest" path="/form/robTest.jsp" />
    </action>

</action-mappings>

<message-resources parameter="com.yourcompany.struts.ApplicationResources" />
</struts-config>

Prezentowany prototyp aplikacji nie realizuje żadnej
funkcjonalności związanej z bezpieczeństwem, a także wielu innych wymagań.
Zaprezentowane fragmenty kodu wraz z opisem, powinny w połączeniu z informacjami
zawartymi w poprzednich odcinkach pozwolić nie tylko na przeanalizowanie
prezentowanego bardzo prostego prototypu (a raczej fragmentu), ale również na
stworzenie takiej aplikacji. Powyższa aplikacja będzie punktem wyjścia do
prezentowania bardziej złożonych zagadnień w następnych odcinkach.

c.d.n.


1 Kod źródłowy tego ejb i odpowiednie znaczniki narzędzia Xdoclet są bardzo
podobne do kodu pierwszego z sesyjnych beanów. Atrybut jndi-name ma wartość „ejb/UslugiDlaUcznia”.