Instalacja i infrastruktura
Do realizacji przykładowego projektu potrzebujemy Javy 1.5+, Seam oraz serwer Tomcat lub JBoss. (Oczywiście można też użyć innych serwerów zgodnych z JEE). Użyjemy też środowiska IDE by nie pisać kodu w notatniku :)Skąd pobrać:
| Seam 2.2.0 | http://seamframework.org/ |
| Tomcat | http://tomcat.apache.org/ |
| JBoss | http://jboss.org/jbossas/ |
| Java | http://java.sun.com/javase/downloads/index.jsp |
| Netbeans IDE | http://www.netbeans.org/ |
| Eclipse IDE | http://www.eclipse.org/ |
Przykładowa dziedzina projektu
Na potrzeby przykładu będziemy używać dziedziny porad eksperckich. System ma umożliwiać zadawanie pytań do ekspertów, przeglądanie pytań i udzielanie odpowiedzi. Wszystko oczywiście w bardzo uproszczonej formie.Lista kroków do realizacji projektu
- Przygotowanie Tomcat'a
- Przygotowanie struktury projektu pod kątem użycia Maven'a
- Napisanie klas encyjnych, biznesowych
- Stworzenie interfejsu użytkownika
- Kompilacja i zbudowanie projektu
- Instalacja zbudowanej aplikacji w środowisku Tomcat'a
- Wywołania stron aplikacji w przeglądarce WWW
Przygotowanie Tomcat'a
Wystarczy zainstalować Tomcat'a. Jeśli planujemy używać bazy danych, to sterownik do niej wgrywamy do katalogu TOMCAT_HOME/lib. W naszym przypadku użyjemy PostgreSQL, ale zmiana w jednym miejscu umożliwi użycie praktycznie dowolnej bazy danych. Mechanizmowi zależności Maven'a pozostawimy zajęcie się pozostałymi bibliotekami, z których nasza aplikacja ma korzystać. Doda on wymagane przez Seam'a biblioteki do paczki WAR. Zależności te opiszemy w deskryptorach POM.Przygotowanie struktury projektu pod kątem użycia Maven'a
Przy tworzeniu projektu można się posłużyć Seam'owym narzędziem seamgen, który wygeneruje nam cały szkielet aplikacji, a później można z jego pomocą generować poszczególne komponenty. To podejście daje nam szybki start i przygotowuje strukturę projektu do stosowania hotdeploymentu na JBoss. Niestety wadą tego podejścia jest brak możliwości łatwego wykorzystania maven'a do zarządzania zależnościami, procesem kompilacji, testów i deploy'mentu. Jeśli zależy nam na maven'ie, to możemy użyć istniejących plugin'ów, które wygenerują nam szkielet aplikacji Seam zgodnej z filozofią maven'a, albo tworzymy taką strukturę ręcznie. Ze względu na pewne wady podejścia 1 i 2 wybralismy wariant 3 i samodzielnie stworzyliśmy odpowiednią strukturę.Poniżej struktura oraz deskryptory opisujące poszczególne moduły.
Projekt został podzielony na następujące moduły i opiasny deskryptorami POM:
- ExpertAdvice-businessapi - zawiera interfejsy logiki biznesowej (w tym DAO)
- ExpertAdvice-businessimpl - klasy implementujące logikę biznesową
- ExpertAdvice-domain - klasy reprezentujące obiekty dziedzinowe, reprezentujące dane trwałe
- ExpertAdvice-war - moduł grupujący warstwę prezentacji (widoki)
- ExpertAdvice - moduł rodzic, grupujący pozostałe moduły
|
Widok katalogów: ExpertAdvice |- businessapi |- businessimpl |- domain |- war |
pom.xml pom.xml pom.xml pom.xml |
Napisanie klas encyjnych, biznesowych
Adekwatnie do podziału na moduły piszemy najpierw klasy dziedzinowe (encyjne), później interfejsy biznesowe oraz DAO. W kolejnym kroku tworzymy implementacje i kontroler do obsługi interfejsu użytkownika.| QuestionUC | Interfejs fasady do logiki biznesowej |
| QuestionDAO, UserDAO | Interfejsy DAO |
| QuestionUCBean | Implementacja fasady do logiki biznesowej |
| QuestionDAOBean, UserDAOBean | Implementacja DAO |
| Question, User | Klasy dziedzinowe (encyjne) |
| QuestionBrowser | Kontroler do obsługi interfejsu użytkownika |
Do dzieła. Na początek klasa encyjna Question - zawiera pytanie, udzieloną odpowiedź oraz relacje do osoby zadającej pytanie i udzielającej odpowiedzi. W podstawowej wersji aplikacji nie będziemy obsługiwać tych relacji. Używając JPA mapujemy ją do relacyjnej tabeli
QUESTION.Do klasy dodajemy dwie metody
public String getAnswerPreview() i public String getQuestionPreview(), które zwracają jedynie zajawkę pytania i odpowiedzi (pierwszych 20 znaków, z wyłączeniem znaczników HTML). Zajawki te są nam potrzebne by przy wyświetlaniu listy pytań widzieć w każdym wierszu tylko skrót tekstów i to bez formatowania HTML.
pl\expert\domain\Question.java
package pl.expert.domain; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToOne; import org.apache.commons.lang.StringUtils; @Entity public class Question implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private long id; private String question; private String answer; @ManyToOne private User answerAuthor; @ManyToOne private User questionAuthor; public String getQuestion() { return question; } public void setQuestion(String question) { this.question = question; } public String getQuestionPreview() { String t = question; if (t != null) { t = t.replaceAll("\\<.*?>",""); t = StringUtils.abbreviate(t, 20); } return t; } public String getAnswer() { return answer; } public void setAnswer(String answer) { this.answer = answer; } public String getAnswerPreview() { String t = answer; if (t != null) { t = t.replaceAll("\\<.*?>",""); t = StringUtils.abbreviate(t, 20); } return t; } public User getAnswerAuthor() { return answerAuthor; } public void setAnswerAuthor(User answerAuthor) { this.answerAuthor = answerAuthor; } public User getQuestionAuthor() { return questionAuthor; } public void setQuestionAuthor(User questionAuthor) { this.questionAuthor = questionAuthor; } public long getId() { return id; } @Override public String toString() { return "Question [answer=" + answer + ", id=" + id + ", question=" + question + "]"; } }
W drugim kroku tworzymy interfejsy: QuestionDAO i bazowy DAO.
pl\expert\server\dao\api\QuestionDAO.java
package pl.expert.server.dao.api; import pl.expert.domain.Question; public interface QuestionDAO extends DAO<Question> { }
pl\expert\server\dao\api\DAO.java
package pl.expert.server.dao.api; import java.util.List; import pl.expert.server.service.api.ExpertAdviceException; public interface DAO<E> { public E find(long id); public List<E> findAll(int start, int howMany); public long getAllCount(); public E insert(E entity); public E update(E entity); public void remove(long id) throws ExpertAdviceException; }
Teraz interfejs fasadowy QuestionUC. Zawiera wszystkie operacje biznesowe jakie chcemy udostępnić w aplikacji.
pl\expert\server\service\api\QuestionUC.java
package pl.expert.server.service.api; import java.util.List; import pl.expert.domain.Question; import pl.expert.domain.User; public interface QuestionUC { public Question addQuestion(Question question) throws ExpertAdviceException; public void answerQuestion(long id, String answer, User answerAuthor) throws ExpertAdviceException; public Question getQuestion(long id) throws ExpertAdviceException; public List<Question> getAllQuestions(int start, int howMany) throws ExpertAdviceException; public long getAllQUestionsCount(); public void removeQuestion(long id) throws ExpertAdviceException; public void updateQuestion(Question question); }
Kolej na implementacje: QuestionDAOBean, BaseDAO oraz QuestionUCBean. BaseDAO w sposób generyczny realizuje podstawowe operacje CRUD (ang. Create, Retrieve, Update, Delete).
Komponenty te są bezstanowe, więc nadajemy im też taki zakres życia we frameworku Seam
@Scope(ScopeType.STATELESS). Dostęp do bazy danych (czyli operacje persist(), find() itd.) dostarcza nam obiekt z interfejsem EntityManager. Jest on wstrzyknięty automatycznie przez Seam'a przy uruchomieniu naszego komponentu DAO. Służy to tego adnotacja @In
@In
private EntityManager em;
pl\expert\server\dao\impl\QuestionDAOBean.java
package pl.expert.server.dao.impl; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import pl.expert.domain.Question; import pl.expert.server.dao.api.QuestionDAO; //@Stateless @Local @Name("questionDAO") @Scope(ScopeType.STATELESS) public class QuestionDAOBean extends BaseDAO<Question> implements QuestionDAO { public QuestionDAOBean() { super(Question.class); } }
pl\expert\server\dao\impl\BaseDAO.java
package pl.expert.server.dao.impl; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.Query; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.log.Log; import pl.expert.server.dao.api.DAO; import pl.expert.server.service.api.ExpertAdviceException; public abstract class BaseDAO<E> implements DAO<E> { @Logger Log log; private Class<E> entityType; // @PersistenceContext @In private EntityManager em; public BaseDAO(Class<E> entityType) { this.entityType = entityType; } public E find(long id) { return em.find(entityType, id); } @SuppressWarnings("unchecked") public List<E> findAll(int start, int howMany) { Query q = em.createQuery("select o from " + entityType.getSimpleName() + " o"); q.setFirstResult(start); q.setMaxResults(howMany); return q.getResultList(); } public long getAllCount() { Query q = em.createQuery("select count(o) from " + entityType.getSimpleName() + " o"); return (Long)q.getSingleResult(); } public E insert(E entity) { em.persist(entity); return entity; } public E update(E entity) { return em.merge(entity); } public void remove(long id) throws ExpertAdviceException { E entity = em.find(entityType, id); if(entity!=null) { em.remove(entity); } else { throw new ExpertAdviceException("Entity with id: " + id + " doesn't exist."); } } }
Dostęp do bazy danych definiujemy w trzech miejscach:
-
persistence.xml (deskryptor ten zdefiniowany jest przez specyfikację JPA) - zawiera definicję jednostki trwałości (ang. Persistence Unit) -
context.xml (deskryptor opisujący kontekts naszej aplikacji - używany przez Tomcat'a) - zawiera definicję źródła danych (DataSource), dostęp do bazy danych przez JDBC-
components.xml (deskryptor zdefiniowany przez Seam) - zawiera między innymi definicję EntityManager'a.ExpertAdvice\war\src\main\resources\META-INF\persistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0"> <persistence-unit name="expertAdviceDB" transaction-type="RESOURCE_LOCAL"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:comp/env/jdbc/expertAdviceDB</jta-data-source> <class>pl.expert.domain.Question</class> <class>pl.expert.domain.User</class> <properties> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.show_sql" value="true"/> <!-- <property name="hibernate.cache.provider_class" value="org.hibernate.cache.HashtableCacheProvider"/> --> <!-- RESOURCE_LOCAL <property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/> --> <!-- alternative <property name="jboss.entity.manager.factory.jndi.name" value="java:/jpaBookingEntityManagerFactory"/> --> </properties> </persistence-unit> </persistence>
ExpertAdvice\war\src\main\webapp\META-INF\context.xml
<Context path="/expertAdvice" docBase="expertAdvice" debug="5" reloadable="true" crossContext="true" antiResourceLocking="true"> <!-- <Resource name="jdbc/expertAdviceDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="sa" driverClassName="org.hsqldb.jdbcDriver" url="jdbc:hsqldb:."/> --> <Resource name="jdbc/expertAdviceDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="postgres" password="admin" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/expertAdvice"/> </Context>
ExpertAdvice\war\src\main\webapp\WEB-INF\components.xml
<?xml version="1.0" encoding="UTF-8"?> <components xmlns="http://jboss.com/products/seam/components" xmlns:core="http://jboss.com/products/seam/core" xmlns:persistence="http://jboss.com/products/seam/persistence" xmlns:transaction="http://jboss.com/products/seam/transaction" xmlns:security="http://jboss.com/products/seam/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.2.xsd http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.2.xsd http://jboss.com/products/seam/transaction http://jboss.com/products/seam/transaction-2.2.xsd http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.2.xsd http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.2.xsd"> <core:manager conversation-timeout="120000" concurrent-request-timeout="500" conversation-id-parameter="cid"/> <transaction:entity-transaction entity-manager="#{em}"/> <persistence:entity-manager-factory name="expertAdviceDB"/> <persistence:managed-persistence-context name="em" auto-create="true" entity-manager-factory="#{expertAdviceDB}"/> <security:identity authenticate-method="#{authenticator.authenticate}"/> </components>
Wpis
<transaction:entity-transaction entity-manager="#{em}"/> definiuje komponent EntityManager i udostępnia go w aplikacji Seam pod nazwą em, aby mieć do niej dostęp w naszym komponencie wstawiamy pokazaną już wczesniej adnotację @In.QuestionUCBean w zasadzie tylko deleguje wywołania do QuestionDAO, który dzięki adnotacji
@In jest automatycznie wstrzykiwany przez Seam'a podczas uruchamiania naszego komponentu. Również definiujemy ga jako bezstanowy @Scope(ScopeType.STATELESS).
pl\expert\server\service\impl\QuestionUCBean.java
package pl.expert.server.service.impl; import java.util.List; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Scope; import pl.expert.domain.Question; import pl.expert.domain.User; import pl.expert.server.dao.api.QuestionDAO; import pl.expert.server.service.api.ExpertAdviceException; import pl.expert.server.service.api.QuestionUC; //@Stateless @Local @Name("questionUC") @Scope(ScopeType.STATELESS) public class QuestionUCBean implements QuestionUC { // @EJB @In(value="questionDAO", create=true) private QuestionDAO questionDAO; public Question addQuestion(Question question) { return questionDAO.insert(question); } public void answerQuestion(long id, String answer, User answerAuthor) { Question question = questionDAO.find(id); question.setAnswer(answer); question.setAnswerAuthor(answerAuthor); questionDAO.update(question); } public List<Question> getAllQuestions(int start, int howMany) { return questionDAO.findAll(start, howMany); } public Question getQuestion(long id) { return questionDAO.find(id); } public long getAllQUestionsCount() { return questionDAO.getAllCount(); } public void removeQuestion(long id) throws ExpertAdviceException { questionDAO.remove(id); } public void updateQuestion(Question question) { questionDAO.update(question); } }
Na koniec został nam do napisania kontroler odpowiedzialny za obsługę interfejsu użytkownika QuestionBrowser. Zgodnie ze stosowaną już poprzednio techniką nadajemy mu nazwę za pomocą adnotacji
@Name("questionBrowser"). W odróżnieniu od poprzednich komponentów (które były bezstanowe) wybieramy dla niego zakres życia @Scope(ScopeType.CONVERSATION). Zakres ten oznacza, że dla każdego otwartego okna przeglądarki bądź karty użytkownik będzie miał do dyspozycji krótkie, niezależne od siebie sesje i w ramach każdej takiej unikalnej "sesji" będzie dostarczona unikalna instancja QuestionBrowser. Dla użytkownika oznacza to, że na każdej karcie może niezależnie przeglądać listę pytań z zapamiętywaniem aktualnego stanu przeglądania (czyli np. która strona z wynikami, jeśli pytań jest tyle, że trzeba je wyświetlać na wielu stronach do przewijania).
Dla pełnego zrozumiania działania zakresu
ScopeType.CONVERSATION zachęcamy do sięgnięcia do dokumentacji Seam'a.
Listę pytań do przeglądania trzyma pole
private List questionList; oznaczone adnotacją Seam'a @DataModel. Tak oznacza sie listę, która może być użyta przez komponent JSF do wyświetlania zawartości tej listy za pomocą mocno rozbudowanej tableki HTML.Atrybuty
page, max, maxPages, pageSize itd. pozwalają na przechowywanie i sterowanie wyświetlaniem listy pytań z podziałem na strony.Metoda
private void prepareQuestionList() za pośrednictwem QUestionUC pobiera fragment listy pytan z bazy danych i inicjuje ustawienia atrybutów odpowiedzialnych za stronicowanie wyników.
pl\expert\server\web\QuestionBrowser.java
package pl.expert.server.web; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import org.jboss.seam.ScopeType; import org.jboss.seam.annotations.Begin; import org.jboss.seam.annotations.Create; import org.jboss.seam.annotations.Destroy; import org.jboss.seam.annotations.End; import org.jboss.seam.annotations.In; import org.jboss.seam.annotations.Logger; import org.jboss.seam.annotations.Name; import org.jboss.seam.annotations.Out; import org.jboss.seam.annotations.Scope; import org.jboss.seam.annotations.datamodel.DataModel; import org.jboss.seam.faces.FacesMessages; import org.jboss.seam.log.Log; import pl.expert.domain.Question; import pl.expert.server.service.api.ExpertAdviceException; import pl.expert.server.service.api.QuestionUC; @Name("questionBrowser") @Scope(ScopeType.CONVERSATION) public class QuestionBrowser implements Serializable { private static final long serialVersionUID = 1L; @In(value = "questionUC", create=true) private QuestionUC questionUC; private int page = 0; private boolean nextPageAvailable; private boolean prevPageAvailable; private long max; private int maxPages; private int pageSize = 10; @Logger private Log log; @DataModel private List<Question> questionList; @Out(value = "question") private Question question = new Question(); @In private FacesMessages facesMessages; public void setQuestionList(List<Question> questionList) { this.questionList = questionList; } public List<Question> getQuestionList() { return questionList; } public void insertSampleData() { log.debug("starting question insert ..."); int n = (int)questionUC.getAllQUestionsCount(); for (int i = n; i < n+10; i++) { Question q = new Question(); q.setQuestion("Pytanie " + i); try { questionUC.addQuestion(q); } catch (ExpertAdviceException e) { e.printStackTrace(); facesMessages.add("Wystąpił problem ze wstawianiem przykładowych danych"); } } log.debug("finished question insert"); prepareQuestionList(); } @Begin(join=true) @Create public void find() { page = 0; setMax(determineMax()); maxPages = determineMaxPages(); prepareQuestionList(); } @Begin(join=true) public void refresh() { page = 0; questionList = null; find(); } private int determineMaxPages() { double res = (double) getMax() / pageSize; return (int) Math.ceil(res); } private long determineMax() { return questionUC.getAllQUestionsCount(); } public void nextPage() { page++; prepareQuestionList(); } public void prevPage() { page--; prepareQuestionList(); } public void init() { prepareQuestionList(); } private void prepareQuestionList() { List<Question> results; try { results = questionUC .getAllQuestions(getPage() * pageSize, pageSize); nextPageAvailable = (getPage() + 1) * pageSize < getMax(); prevPageAvailable = getPage() > 0; setQuestionList(results); log.debug("prepareQuestionList(): " + questionList); } catch (ExpertAdviceException e) { e.printStackTrace(); facesMessages.add("Błąd podczas wczytywania listy pytań: {1}", e); } } public boolean isNextPageAvailable() { return nextPageAvailable; } public boolean isPrevPageAvailable() { return prevPageAvailable; } public int getPageSize() { return pageSize; } public void setPageSize(int pageSize) { this.pageSize = pageSize; } public void setMax(long max) { this.max = max; } public long getMax() { return max; } public int getPage() { return page; } public void setPage(int page) { this.page = page; } public void moveToPage(String pageStr) { log.info("moveToPage(): #0", pageStr); int page = Integer.parseInt(pageStr); setPage(page); prepareQuestionList(); } public List<Integer> getMaxPages() { List<Integer> l = new ArrayList<Integer>(maxPages); for (int i = 1; i <= maxPages; i++) { l.add(i); } return l; } public int getPagesCount() { return maxPages; } @Destroy @End public void destroy() { } public String startAddAnswer(Question q) { question = q; return "addAnswer"; } public String addAnswer() { try { questionUC.answerQuestion(question.getId(), question.getAnswer(), null); facesMessages.add("Nastąpił poprawny zapis"); } catch (ExpertAdviceException e) { e.printStackTrace(); facesMessages.add("Wystąpił błąd: {1}", e); } return "questionList"; } public String startAddQuestion() { question = new Question(); return "addQuestion"; } public String addQuestion() { try { question = questionUC.addQuestion(question); questionList.add(question); max++; facesMessages.add("Nastąpił poprawny zapis"); } catch (ExpertAdviceException e) { e.printStackTrace(); facesMessages.add("Wystąpił błąd: {1}", e); } return "questionList"; } public String startEditQuestion(Question q) { question = q; return "editQuestion"; } public String saveQuestion() { questionUC.updateQuestion(question); facesMessages.add("Nastąpił poprawny zapis"); return "questionList"; } public void removeQuestion(Question q) { try { questionUC.removeQuestion(q.getId()); questionList.remove(q); max--; facesMessages.add("Nastąpił poprawny zapis"); } catch (ExpertAdviceException e) { e.printStackTrace(); facesMessages.add("Wystąpił błąd: {1}", e); } } }
Stworzenie interfejsu użytkownika
Korzystamy z technologii facelets, JSF i Richfaces. QuestionList.xhtmlExpertAdvice\war\src\main\webapp\QuestionList.xhtml
<ui:composition xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://java.sun.com/jsf/facelets" xmlns:h="http://java.sun.com/jsf/html" xmlns:f="http://java.sun.com/jsf/core" xmlns:s="http://jboss.com/products/seam/taglib" xmlns:a4j="http://richfaces.org/a4j" xmlns:rich="http://richfaces.org/rich" template="/WEB-INF/template.xhtml"> <ui:define name="body"> <div> <h1>Pytania do eksperta</h1> <s:button value="Wypełnij przykładowymi danymi" action="#{questionBrowser.insertSampleData}" /> </div> <div><h:form> <s:button value="Dodaj pytanie" action="#{questionBrowser.startAddQuestion}"/><h:outputText value=" "/> <h:selectOneMenu value="#{questionBrowser.pageSize}" id="pageSize" > <f:selectItem itemLabel="5" itemValue="5" /> <f:selectItem itemLabel="10" itemValue="10" /> <f:selectItem itemLabel="20" itemValue="20" /> <a4j:support event="onchange" reRender="questionTable" action="#{questionBrowser.refresh}"/> </h:selectOneMenu> <s:div id="questionTable"> <rich:dataTable value="#{questionBrowser.questionList}" var="q" rows="#{questionBrowser.pageSize}" reRender="ds" id="simpletable" sortMode="multi"> <f:facet name="header"> <rich:columnGroup> <rich:column> <h:outputText value="Id" /> </rich:column> <rich:column> <h:outputText value="Pytanie" /> </rich:column> <rich:column> <h:outputText value="Odpowiedź" /> </rich:column> <rich:column> <h:outputText value="Akcja" /> </rich:column> </rich:columnGroup> </f:facet> <rich:column> <h:outputText value="#{q.id}" /> </rich:column> <rich:column filterBy="#{q.question}" filterEvent="onkeyup" sortBy="#{q.question}"> <h:outputText value="#{q.questionPreview}" escape="false"/> <rich:toolTip rendered="#{not empty q.question}"> <h:outputText value="#{q.question}" escape="false"/> </rich:toolTip> </rich:column> <rich:column filterBy="#{q.answer}" filterEvent="onkeyup" sortBy="#{q.answer}"> <h:outputText value="#{q.answerPreview}" escape="false"/> <rich:toolTip rendered="#{not empty q.answer}"> <h:outputText value="#{q.answer}" escape="false"/> </rich:toolTip> </rich:column> <rich:column> <a4j:commandButton value="Daj odpowiedź" action="#{questionBrowser.startAddAnswer(q)}" rendered="#{empty q.answer}" style="width: 150px;"/> <a4j:commandButton value="Edytuj odpowiedź" action="#{questionBrowser.startAddAnswer(q)}" rendered="#{not empty q.answer}" style="width: 150px;"/> <a4j:commandButton value="Usuń" action="#{questionBrowser.removeQuestion(q)}" reRender="questionTable"/> <a4j:commandButton value="Edytuj" action="#{questionBrowser.startEditQuestion(q)}"/> </rich:column> <f:facet name="footer"> <rich:datascroller id="ds" renderIfSinglePage="false"></rich:datascroller> </f:facet> </rich:dataTable> <div style="clear: both" /> <s:button value="Dodaj pytanie" action="#{questionBrowser.startAddQuestion}"/><h:outputText value=" "/> <a4j:commandLink action="#{questionBrowser.prevPage}" value="Poprzedni" rendered="#{questionBrowser.prevPageAvailable}" reRender="questionTable, errors" /> <h:outputText value="Poprzedni" rendered="#{not questionBrowser.prevPageAvailable}"/> <h:outputText value=" "/> <a4j:commandLink action="#{questionBrowser.nextPage}" value="Nastepny" rendered="#{questionBrowser.nextPageAvailable}" reRender="questionTable, errors" /> <h:outputText value="Nastepny" rendered="#{not questionBrowser.nextPageAvailable}"/> Strona #{questionBrowser.page + 1} / #{questionBrowser.pagesCount} <br/> Znaleziono #{questionBrowser.max} resultatów. </s:div> <div style="clear: both" /> <a4j:commandButton style="float:left" value="Odśwież" action="#{questionBrowser.refresh}" reRender="questionTable, errors" /> </h:form></div> </ui:define> <ui:define name="sidebar"> </ui:define> </ui:composition>
Kompilacja i zbudowanie projektu
mvn clean package. To jest maven. Więcej komentarzy nie trzeba.
Instalacja zbudowanej aplikacji w środowisku Tomcat'a
Wystarczy skopiować wytworzoną paczkę WARexpertAdvice.war do katalogu TOMCAT_HOME/webapp lub użyć do tego zadania ANT<target name="tomcat.deploy"> <copy file="war/target/expertAdvice.war" todir="${tomcat.home}/webapps"></copy> </target>
Wywołanie stron aplikacji w przeglądarce WWW
Przy założeniu, że mamy skonfigurowanego Tomcat'a na localhost port 8080:http://localhost:8080/expertAdvice/
Pełna paczka ze źródłami dostępna jest do ściągnięcia tutaj ExpertAdvice.zip
Wnioski
Stworzenie tej aplikacji zajęło nam trochę czasu jeśli robiliśmy to ręcznie. Autor artykułu zakłada, że w niedalekiej przyszłości plugin'y do maven'a na tyle się poprawią, że umożliwią wygodne generowanie takiej aplikacji pod kątem wykorzystania maven'a do zarządzania zależnościami, procesem kompilacji, testowania itd.Z drugiej strony poniesiony koszt całkowicie nam się rekompensuje z tytułu zautomatyzowania bardzo wielu czynności, które w innym wypadku musielibyśmy wykonywać ręcznie lub pisać samodzielnie różne skrypty dbające o to. Również dobry podział na moduły i odseparowanie interfejsów od implementacji przynosi duże korzyści w miarę rozbudowy i rozwoju wytwarzanej aplikacji. Dodatkowo Seam w połączeniu z facelets i Richfaces ułatwia nam zadanie pisania warstwy webowej i interfejsu dając nam za darmo wiele gotowych mechanizmów i kontrolek interfejsu użytkownika plus AJAX.
Linki do dokumentacji i przykładów w sieci
| Przewodnik dla Seam | .../tutorial.html | |
| Jak użyć Maven'a do budowania projektu bazującego na Seam | .../seam-ejbs-and-ear-packaging-in-maven.html | |
| Richfaces | .../html_single/ |




(0 głos)