Pokazywanie postów oznaczonych etykietą EJB 3. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą EJB 3. Pokaż wszystkie posty

niedziela, 22 marca 2009

Ziarna sesyjne - czar prysł.

Dopiero co zachwycałem się nad lokalnym i zdalnym sposobem wywoływania usług ziaren EJB. Przyszła wiosna, śnieg stopniał i czar prysł. Otóż pomiędzy wywołaniem danej metody poprzez interfejs oznaczony adnotacją @Local a wywołaniem za pomocą interfejsu @Remote mogą być subtelne różnice. Bierze się to stąd, że parametry metod wywoływanych lokalnie (@Local) przekazywane są poprzez referencje, parametry metod zdalnych (@Remote) przez wartość.

Zobaczmy o co chodzi na podstawie prostego przykładu. Stworzę z tej okazji w NetBeans IDE 6.5.1 projekt ziarna EJB oraz projekt klienta EJB.

Zaczynam od aplikacji EJB:
Z menu File wybieram pozycję New Project:


następnie wybieram kategorię Java EE i projekt EJB Module
klikam Next, po czym na zakładce Server and Settings dodaję nową konfigurację serwera, klikając przycisk Add...

Przykład będę uruchamiał na serwerze JBoss 4.2.2.GA


więc wskazuję jego położenie na dysku:


klikam Next> i Finish. Projekt gotowy.

W projekcie stworzę proste ziarno, które będzie udostępniało metodę dodającą podany jako parametr element do podanej jako parametr kolekcji:


public Collection add(Collection collection, Object element) {
collection.add(element);
return collection;
}


Ziarno będzie implementowało interfejs lokalny oraz zdalny. Ziarno oraz interfejsy przedstawiają się następująco:

Kod ziarna StatelessBean:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Local;
import javax.ejb.Remote;


@javax.ejb.Stateless
@Remote(StatelessRemote.class)
@Local(StatelessLocal.class)
public class StatelessBean implements StatelessRemote, StatelessLocal {

public Collection add(Collection collection, Object element) {
collection.add(element);
return collection;
}

}


interfejs zdalny StatelessRemote:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Remote;

public interface StatelessRemote {

Collection add(Collection collection, Object element);

}


i identyczny interfejs lokalny StatelessLocal:

package pl.matt.session;

import java.util.Collection;
import javax.ejb.Remote;

public interface StatelessLocal {

Collection add(Collection collection, Object element);

}


Całość uzupełnia ziarno testujące dodawanie elementów do kolekcji, które próbuje zarówno zdalnie jak i lokalnie dodać 3 napisy do listy:

interfejs TestRemote:

package pl.matt.session;

public interface TestRemote {
void test();
}


oraz jego implementacja:


package pl.matt.session;

import java.util.ArrayList;
import java.util.List;
import javax.ejb.Remote;
import javax.naming.Context;
import javax.naming.InitialContext;

@javax.ejb.Stateless
@Remote(TestRemote.class)
public class TestBean implements TestRemote {

public void test() {
try {
Context context = new InitialContext();
StatelessLocal statelessLocal = (StatelessLocal) context.lookup("StatelessBean/local");
StatelessRemote statelessRemote = (StatelessRemote) context.lookup("StatelessBean/remote");
List<String> localList = new ArrayList();
statelessLocal.add(localList, "1");
statelessLocal.add(localList, "2");
statelessLocal.add(localList, "3");
System.out.println("local list: " + localList);

List<String> remoteList = new ArrayList();
statelessRemote.add(remoteList, "1");
statelessRemote.add(remoteList, "2");
statelessRemote.add(remoteList, "3");
System.out.println("remote list: " + remoteList);
} catch (Exception e) {
e.printStackTrace();
}
}
}


Do wywołania metody ziarna TestBean potrzebuję klienta EJB. Tworzę go wybierając ponownie pozycję New Project... z menu File. Następnie z kategorii Java EE wybieram projekt Enterprise Application Client:

Do tak stworzonego projektu dodaję jako zależność stworzony uprzednio projekt EJB


Dzięki temu, będę w nim mógł uzyskać dostęp do interfejsu testującego. Całość kodu klienta zawarta jest w metodzie main():


package pl.matt;

import javax.naming.Context;
import javax.naming.InitialContext;
import pl.matt.session.TestRemote;

public class Main {

public static void main(String[] args) {
try {
System.out.println("start");
Context context = new InitialContext();
TestRemote test = (TestRemote) context.lookup("TestBean/remote");
test.test();
System.out.println("finished");
} catch (Exception e) {
e.printStackTrace();
}

}
}


Pozostało uruchomienie i przekonanie się, jak to wszystko będzie działać. Klikam prawym przyciskiem myszy na projekcie EJB, wybieram opcję Run. Następnie to samo robię na projekcie klienta EJB. JBoss się uruchamia, po chwili widzę w konsoli:


22:27:55,969 INFO [STDOUT] local list: [1, 2, 3]
22:27:55,974 INFO [STDOUT] remote list: []


2 razy wywołałem tę samą metodę, raz przez interfejs @Remote, raz przez @Local i wynik jej działania jest inny. Do kolekcji, przekazanej przez wartość elementy nie zostały dodane (tzn. zostały, ale do kopii tej kolekcji, przekazanej do ziarna EJB).

Trzeba uważać.

czwartek, 5 lutego 2009

aplikacja JSF i EJB 3.0 w JBoss Tools

W dzisiejszym odcinku, stworzymy aplikację webową (a jakże) korzystającą z EJB 3.0 i JSF. Ktoś może się zapytać po co? Przecież mamy znakomity szkielet na literę S... Owszem mamy, ale może nie znamy, albo może nie potrzebujemy jego możliwości... Różnie to w życiu bywa. Poza tym modułowa budowa, którą narzuca nam podział aplikacji na webową i biznesową wydaje się całkiem rozsądnym rozwiązaniem.
Zobaczmy zatem, co i jak... Do pracy nad aplikacją użyję środowiska Eclipse 3.3 Europa z zainstalowaną wtyczką JBoss Tools 2.1.2.GA (ostatnia finalna wersja). Jako środowisko uruchomieniowe posłuży mi kontener JBoss AS 4.2.0.GA. Bazą danych będzie MySQL 5.0.
Strukturę aplikacji tworzy archiwum EAR (Enterprise Application Archive) zawierające aplikację EJB (archiwum ejb-jar) oraz aplikację webową (archiwum war).


Ten na pozór skomplikowany podział może okazać się bardzo przydatny w większych, żeby nie powiedzieć korporacyjnych rozwiązaniach. Z warstwy usług zaimplementowanej w aplikacji EJB korzystać może nie tylko nasza aplikacja JSF, ale także inne, niekoniecznie nawet webowe aplikacje.

Stworzona aplikacja, będzie typową aplikacją CRUD, umożliwiającą tworzenie, odczyt, aktualizację i kasowanie pracowników. Potrzebować zatem będziemy 3 projekty. Projekt ejb, jsf oraz zawierający je projekt aplikacji ear.
Zacznijmy od EJB. Klikam File / New / Other...


i z listy wybieram EJB Project.


Podaję nazwę projektu ejbProject i klikam Next > Na następnej zakładce zaznaczam EJB module Java i Java Persistence


klikam 2 razy Next >, odznaczam create orm.xml (będę używał adnotacji) i klikam Finish


Tworzę w projekcie encję Employee

package pl.matt.model;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;

@NamedQueries({
@NamedQuery(name = "Employee.findAllOrderByName", query = "FROM Employee e ORDER BY e.lastName, e.firstName")
})
@Entity
public class Employee {

private int id;
private String password;
private String username;
private String firstName;
private String lastName;

@Id
@GeneratedValue
public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

@Column(nullable = false)
public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

@Column(nullable = false)
public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

@Column(nullable = false)
public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

@Column(nullable = false)
public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

}



warstwę dostępu do danych (DAO):


package pl.matt.dao;

import java.util.List;

import pl.matt.model.Employee;

public interface EmployeeDao {

/**
* @return
*/
public List<Employee> getAllOrderByName();
/**
* @param employee
* @return
*/
public Employee create(Employee employee);

/**
* @param employee
*/
public void update(Employee employee);

/**
* @param employeeId
* @return
*/
public Employee load(int employeeId);

/**
* @param employeeId
* @return
*/
public void delete(Employee employee);

}



package pl.matt.dao.impl;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import pl.matt.dao.EmployeeDao;
import pl.matt.model.Employee;

@Stateless
public class JpaEmployeeDao implements EmployeeDao {

@PersistenceContext
private EntityManager entityManager;

@SuppressWarnings("unchecked")
public List<Employee> getAllOrderByName() {
return entityManager.createNamedQuery("Employee.findAllOrderByName").getResultList();
}

public Employee create(Employee employee) {
entityManager.persist(employee);
return employee;
}

public void update(Employee employee) {
entityManager.merge(employee);
}

public Employee load(int employeeId) {
return entityManager.find(Employee.class, employeeId);
}

public void delete(Employee employee) {
entityManager.remove(employee);

}

}


i może trochę na wyrost warstwę usług biznesowych

package pl.matt.service;

import java.util.List;

import pl.matt.model.Employee;

public interface EmployeeService {
/**
* @return
*/
public List<Employee> getAllOrderByName();
/**
* @param employee
* @return
*/
public Employee create(Employee employee);

/**
* @param employee
*/
public void update(Employee employee);

/**
* @param employeeId
* @return
*/
public Employee load(int employeeId);

/**
* @param employeeId
*/
public void delete(int employeeId);
}



package pl.matt.service.impl;

import java.util.List;

import javax.ejb.EJB;
import javax.ejb.Stateless;

import pl.matt.dao.EmployeeDao;
import pl.matt.model.Employee;
import pl.matt.service.EmployeeService;

@Stateless
public class EmployeeServiceImpl implements EmployeeService {

@EJB
private EmployeeDao employeeDao;

public Employee create(Employee employee) {
return employeeDao.create(employee);
}

public List<Employee> getAllOrderByName() {
return employeeDao.getAllOrderByName();
}

public void update(Employee employee) {
employeeDao.update(employee);

}

public Employee load(int employeeId) {
return employeeDao.load(employeeId);
}

public void delete(int employeeId) {
Employee employee = load(employeeId);
if (employee != null) {
employeeDao.delete(employee);
}
}

}


Ponieważ korzystam z bazy danych za pośrednictwem JPA, nie obejdzie się bez pliku 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" version="1.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">


<persistence-unit name="seam_war_project" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>java:/empAppDatasource</jta-data-source>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.show_sql" value="true"/>
</properties>
</persistence-unit>

</persistence>


potrzebuję też źródła danych (Data Source) empAppDatasource. Tworzę ją w pliku empApp-ds.xml

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

<!DOCTYPE datasources
PUBLIC "-//JBoss//DTD JBOSS JCA Config 1.5//EN"
"http://www.jboss.org/j2ee/dtd/jboss-ds_1_5.dtd">

<datasources>

<local-tx-datasource>
<jndi-name>empAppDatasource</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/jpabasics?characterEncoding=latin2</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>root</password>
</local-tx-datasource>

</datasources>

który umieszczam w katalogu server/default/deploy/ JBossa.

Przejdźmy do aplikacji JSF. Tworzę ją wybierając z menu Eclipse File / New / Other... / JSF Project



Jako środowisko JSF ustawiam JSF 1.2 z Faceletami, podaję nazwę projektu jsfModule i klikam Finish.

W projekcie tworzę prosty szablon stron WebContent/templates/common.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 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">

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title><ui:insert name="pageTitle">Page Title</ui:insert></title>
<style type="text/css">
body {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 14px;
}

.header {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 18px;
}

.bottom {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 9px;
text-align: center;
vertical-align: middle;
color: #8E969D;
}

td.column1 {
width: 15%;
}

td.column2 {
width: 25%;
}

td.column3 {
width: 60%;
}
</style>
</head>

<body bgcolor="#ffffff">
<table style="border: 1px solid #CAD6E0" align="center" cellpadding="0"
cellspacing="0" border="0" width="400">
<tbody>

<tr>
<td class="header" height="42" align="center" valign="middle"
width="100%" bgcolor="#E4EBEB"><ui:insert name="pageHeader">Page Header</ui:insert>
</td>
</tr>
<tr>
<td height="1" width="100%" bgcolor="#CAD6E0"></td>
</tr>

<tr>
<td width="100%" colspan="2">
<table width="100%" style="height: 150px" align="left"
cellpadding="0" cellspacing="0" border="0">
<tbody>
<tr>
<td align="left" width="800px" valign="middle"><ui:insert
name="body">Page Body</ui:insert></td>
</tr>
</tbody>
</table>
</td>
</tr>

<tr>
<td colspan="2" valign="bottom" height="1" width="100%"
bgcolor="#CAD6E0"></td>
</tr>
</tbody>
</table>
</body>

</html>


plik wyświetlający listę pracowników WebContent/pages/employeeList.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 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:c="http://java.sun.com/jstl/core">

<f:loadBundle basename="resources" var="msg" />

<ui:composition template="/templates/common.xhtml">

<ui:define name="pageTitle">Lista pracowników</ui:define>

<ui:define name="pageHeader">Lista pracowników</ui:define>

<ui:define name="body">
<div style="text-align: center"><h:form>
<h:commandLink action="ADD_EMP" value="Dodaj pracownika" />
<br />
<br />
<ui:repeat value="#{employeeBean.employees}" var="emp">
<h:outputLink value="editEmployee.jsf?employeeId=#{emp.id}">
<h:outputText value="#{emp.firstName} #{emp.lastName}" />
</h:outputLink>
<h:outputText value=" " />
<h:commandLink action="#{employeeBean.delete}" value="[usuń]">
<f:param name="toDelete" value="#{emp.id}" />
</h:commandLink>
<br />
</ui:repeat>

</h:form></div>

</ui:define>
</ui:composition>
</html>


oraz plik umożliwiający edycję pojedynczego pracownika WebContent/pages/editEmployee.xhtml

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html 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:c="http://java.sun.com/jstl/core">

<f:loadBundle basename="resources" var="msg" />

<ui:composition template="/templates/common.xhtml">

<ui:define name="pageTitle">Edycja</ui:define>

<ui:define name="pageHeader">Edycja pracownika</ui:define>

<ui:define name="body">
<h:outputLink value="employeeList.jsf">Powrót</h:outputLink>
<h:form id="employeeForm">
<h:inputHidden id="employeeId" value="#{employeeBean.employee.id}" />
<h:panelGrid columns="3" width="800"
columnClasses="column1,column2,column3">
<h:outputText value="Imię" />
<h:inputText value="#{employeeBean.employee.firstName}"
id="firstName" required="true" />
<h:message for="firstName" style="color:red;" />
<h:outputText value="Nazwisko" />
<h:inputText value="#{employeeBean.employee.lastName}" id="lastName"
required="true" />
<h:message for="lastName" style="color:red;" />
<h:outputText value="Login" />
<h:inputText value="#{employeeBean.employee.username}" id="username"
required="true" />
<h:message for="username" style="color:red;" />
<h:outputText value="haslo" />
<h:inputSecret id="password"
value="#{employeeBean.employee.password}" required="true" redisplay="true" />
<h:message for="password" style="color:red;" />
<h:commandButton action="#{employeeBean.save}" value="zapisz" />
</h:panelGrid>
<h:outputLink value="employeeList.jsf">Powrót</h:outputLink>
</h:form>
</ui:define>
</ui:composition>
</html>


Potrzebne będzie też ziarno zarządzane JSF

package pl.matt.view;

import java.util.List;

import javax.ejb.EJB;
import javax.faces.context.FacesContext;

import pl.matt.model.Employee;
import pl.matt.service.EmployeeService;

public class EmployeeBean {

private List<Employee> employees;
private Employee employee;
private Integer employeeId;

@EJB(name="eeProject/EmployeeServiceImpl/local")
private EmployeeService employeeService;

public List<Employee> getEmployees() {
if (employees == null) {
employees = employeeService.getAllOrderByName();
}
return employees;
}

public Employee getEmployee() {
if (employee == null) {
if (employeeId != null) {
employee = employeeService.load(employeeId);
} else {
employee = new Employee();
}
}
return employee;
}

public void setEmployee(Employee employee) {
this.employee = employee;
}

public String save() {
if (employee.getId() > 0) {
employeeService.update(employee);
} else {
employeeService.create(employee);
}
employees = null;
return "LIST_EMP";
}

public Integer getEmployeeId() {
return employeeId;
}

public void setEmployeeId(Integer employeeId) {
this.employeeId = employeeId;
}

public String delete() {
System.out.println("EmployeeBean.delete()");
Integer id = Integer.valueOf(
FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("toDelete"));
if (id != null) {
employeeService.delete(id);
}
employees = null;
return "LIST_EMP";
}

}



JBoss umieszcza w interfejsie JNDI ziarna EJB korzystając z trochę dziwnej konwencji:
nazwaModułu/klasaZiarna/local|remote stąd konieczność podania parametru name
@EJB(name="eeProject/EmployeeServiceImpl/local")

przy adnotacji @EJB.
Jak będzie wyglądała sytuacja w momencie, kiedy ziarno EJB będzie implementowało kilka interfejsów zdalnych lub lokalnych? Niestety nie mam pojęca.

Do kompletu brakuje jeszcze pliku WebContent/WEB-INF/faces-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="1.2" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">
<managed-bean>
<managed-bean-name>employeeBean</managed-bean-name>
<managed-bean-class>pl.matt.view.EmployeeBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
<managed-property>
<property-name>employeeId</property-name>
<value>#{param.employeeId}</value>
</managed-property>
</managed-bean>
<navigation-rule>
<navigation-case>
<from-outcome>ADD_EMP</from-outcome>
<to-view-id>/pages/editEmployee.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>LIST_EMP</from-outcome>
<to-view-id>/pages/employeeList.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>


oraz WebContent/WEB-INF/web.xml

<?xml version="1.0"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<description>Facelets StarterKit</description>
<display-name>jsfModule</display-name>
<context-param>
<param-name>javax.faces.DEFAULT_SUFFIX</param-name>
<param-value>.xhtml</param-value>
</context-param>
<context-param>
<param-name>facelets.REFRESH_PERIOD</param-name>
<param-value>2</param-value>
</context-param>
<context-param>
<param-name>facelets.DEVELOPMENT</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.validateXml</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>com.sun.faces.verifyObjects</param-name>
<param-value>true</param-value>
</context-param>
<servlet>
<servlet-name>Faces Servlet</servlet-name>
<servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.jsf</url-pattern>
</servlet-mapping>
<login-config>
<auth-method>BASIC</auth-method>
</login-config>
</web-app>


Aplikacja JSF gotowa. Aplikacja EJB gotowa. Czas je wyswatać i pożenić. Pomoże nam w tym projekt EAR. Z menu kontekstowego Eclipse wybieram zatem File / New / Other... / Enterprise Application Project.


Podaję nazwę projektu eeProject klikam 2 razy Next > i wybieram oba stworzone uprzednio moduły. Będą to składowe naszej aplikacji. Dodatkowo zaznaczam opcję Generate Deployment Descriptor i klikam Finish.



W tym projekcie znajduje się tylko plik EarContent/META-INF/application.xml.


<?xml version="1.0" encoding="UTF-8"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:application="http://java.sun.com/xml/ns/javaee/application_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application_5.xsd" id="Application_ID" version="5">
<module>
<ejb>ejbProject.jar</ejb>
</module>
<module>
<web>
<web-uri>jsfModule.war</web-uri>
<context-root>jsfModule</context-root>
</web>
</module>
</application>


Plik ten opisuje moduły naszej aplikacji. Moduł webowy jsfModule i moduł EJB ejbProject.jar. Wdrożenie aplikacji pozostawiam wtyczce JBoss Tools. Startuję więc serwer i... aplikacja działa.



Kod źródłowy powyższej aplikacji dostępny jest tutaj.

Cóż, pora jechać na narty...

poniedziałek, 26 stycznia 2009

gry i zabawy ze szklaną rybką

Zdarzyło się tak, że w nowym 2009 roku wpadła w moje śliskie łapska niezła cegła.
Mastering Enterprise JavaBeans 3.0. Ponad 700 stron lektury. Jak przebrnę przez wszystko, postaram się zamieścić recenzję. Mam nadzieję, że wyrobię się przed rokiem pańskim 2010.
Mastering Enterprise JavaBeans 3.0 traktuje całkiem szczegółowo o EJB 3.0. Czas zatem rozpocząć wycieczkę w tamte rejony. Wycieczki są fajne, wszak podróże kształcą. Jednak wycieczki w pojedynkę bywają nudne. Na szczęście udało mi się namówić do wyprawy serwer aplikacyjny GlassFish. We dwójkę raźniej.

Zacząłem od instalacji mojego towarzysza. Pobrałem serwer GlassFish 2. Co ciekawe, do instalacji nie wystarczy odpalenie pobranego pliku glassfish-installer-v2.1-b60e-linux.jar. Aby w pełni zainstalować serwer, należy jeszcze uruchomić antem znajdujący się w jego katalogu głównym plik setup.xml. Czyli:

  
java -jar glassfish-installer-v2.1-b60e-linux.jar
cd glassfish
ant -f setup.xml


Dopiero teraz możemy się cieszyć w pełni działającym serwerem Java EE.
Żeby go uruchomić, wchodzę do katalogu bin GlassFisha i startuję domenę domain1.
 
./asadmin start-domain domain1
Starting Domain domain1, please wait.
Log redirected to /opt/glassfish/domains/domain1/logs/server.log.
Redirecting output to /opt/glassfish/domains/domain1/logs/server.log
Domain domain1 is ready to receive client requests. Additional services are being started in background.
Domain [domain1] is running [Sun Java System Application Server 9.1_02 (build b04-fcs)] with its configuration and logs at: [/opt/glassfish/domains].
Admin Console is available at [http://localhost:4848].
Use the same port [4848] for "asadmin" commands.
User web applications are available at these URLs:
[http://localhost:8080 https://localhost:8181 ].
Following web-contexts are available:
[/web1 /__wstx-services ].
Standard JMX Clients (like JConsole) can connect to JMXServiceURL:
[service:jmx:rmi:///jndi/rmi://jt-laptop:8686/jmxrmi] for domain management purposes.
Domain listens on at least following ports for connections:
[8080 8181 4848 3700 3820 3920 8686 ].
Domain does not support application server clusters and other standalone instances.

Wchodzę na stronę
http://localhost:8080


Wszystko ładnie działa.

W EJB 3.0, mamy 3.0 rodzaje ziaren. Ziarna sesyjne, ziarna sterowane wiadomością i ziarna encyjne, które są, ale jakby ich nie było... Dziś zajmę się tymi pierwszymi i to tylko częściowo. Ziarna sesyjne bowiem występują w dwóch odmianach, stanowej i bezstanowej. Zarówno jedne, jak i drugie zazwyczaj wykonują tak zwaną logikę biznesową. Robią to niby wydajnie, skalowalnie, och i ach, ale moim skromnym zdaniem siła EJB ukryta jest tutaj zupełnie gdzie indziej. Otóż w sposób poniekąd przezroczysty dla programisty pozwalają na zarówno lokalne jak i zdalne wywoływanie usług. Możemy je wykonywać w ramach jednej maszyny wirtualnej (np. aplikacja webowa) jak i z osobnych (gruby klient). Zobaczmy jak to działa.

Żeby nie było za nudno, stworzę 2 aplikacje EJB i odpalę je w różnych domenach GlassFisha. Następnie "grubym" klientem wykonam metody jednego i drugiego ziarna. Do dzieła.

EJB 3.0 uprościło się znacznie od zamierzchłych czasów wersji 2.x. Żeby stworzyć ziarno sesyjne i jego zdalny interfejs, potrzebuję tylko... interfejs i jego implementację.

  
interfejs Hello.java


public interface Hello {

public String hello();
}

i jego implementacja

@Stateless
@Remote(Hello.class)
public class HelloBean implements Hello {

public String hello() {
return "domain1";
}

}


@Stateless oznacza, że jest to ziarno bezstanowe
@Remote(Hello.class) informuje kontener EJB (czyli w moim przypadku GlassFisha), że jest to imlpementacja zdalnego interfejsu Hello.

No i to już wszystko.

Powyższą aplikację EJB buduję Mavenem 2.
Oto pom.xml:
  
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<packaging>ejb</packaging>

<properties>
<JAVA_HOME>/usr/lib/jvm/java-6-sun</JAVA_HOME>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>ejb3-slsb</finalName>
<directory>/tmp/mvn-target/</directory>
<resources>
<resource>
<targetPath>/META-INF</targetPath>
<filtering>false</filtering>
<directory>${basedir}/src/main/resources</directory>
<includes>
<include>ejb-jar.xml</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-ejb-plugin</artifactId>
<configuration>
<ejbVersion>3.0</ejbVersion>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<executable>${JAVA_HOME}/bin/javac</executable>
<compilerVersion>1.6</compilerVersion>
<source>1.6</source>
<target>1.6</target>
<debug>true</debug>
<optimize>false</optimize>
</configuration>
</plugin>
</plugins>
</build>
</project>

Zwracam uwagę na <packaging>ejb</packaging> dzięki czemu Maven zbuduje aplikację EJB.

Tworzę również drugą aplikację EJB, która odrobinę różni się od pierwszej implementacją interfejsu Hello.class


@Stateless
@Remote(Hello.class)
public class HelloBean implements Hello {

public String hello() {
return "domain2";
}

}

Aplikację w tej wersji, wdrożę na osobnej domenie serwera GlassFish.

GlassFish po instalacji skonfigurowany jest z domeną domain1. Nową, o nazwie domain2 tworzę poleceniem, które wykonuje w katalogu bin GlassFIsha.
  
./asadmin create-domain --adminport 4849 domain2

Please enter the admin user name>admin
Please enter the admin password>
Please enter the admin password again>
Please enter the master password [Enter to accept the default]:>
Please enter the master password again [Enter to accept the default]:>
Using port 4849 for Admin.
Default port 8080 for HTTP Instance is in use. Using 47039
Default port 7676 for JMS is in use. Using 59523
Default port 3700 for IIOP is in use. Using 38742
Default port 8181 for HTTP_SSL is in use. Using 34802
Default port 3820 for IIOP_SSL is in use. Using 36960
Default port 3920 for IIOP_MUTUALAUTH is in use. Using 48963
Default port 8686 for JMX_ADMIN is in use. Using 55733
Domain being created with profile:developer, as specified by variable AS_ADMIN_PROFILE in configuration file.
The file in given locale [pl_PL] at: [/opt/glassfish/lib/install/templates/locales/pl_PL/index.html] could not be found. Using default (en_US) index.html instead.
Security Store uses: JKS
Domain domain2 created.

Podaję kilka haseł i tyle.
Port konsoli administracyjnej to 4849. Port IIOP, którego używa EJB to 38742.

Czas wdrożyć aplikacje w obu domenach. Wchodzę więc do panelu administracyjnego, dla domeny domain1 będzie to adres:
http://localhost:4848/

dla domeny dmain2
http://localhost:4849/

Loguję się jako administrator. Domyślnie jest to użytkonwik o nazwie admin i haśle adminadmin.



wchodzę w pozycję bocznego menu Applications/EJB Modules i klikam ikonkę deploy.

Jako Packaged file to be uploaded to the server wskazuję plik jar ejb3-slsb.jar. Plik ten utworzył Maven w katalogu /tmp/mvn-target/
Teraz już tylko OK. Aplikacje w różnych wersjach osadzam w obu domenach.

Czas na klienta EJB, który wywoła metody z ziaren zdnajdujących się w różnych domenach. To już zwykła aplikacja w postaci jednej klasy.

public class HelloClients {

public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
Hello hello = (Hello) ctx.lookup("pl.matt.interfaces.Hello");

System.out.println(hello.hello());
}
}

Referencję do ziarna EJB otrzymujemy poprzez wywołanie metody lookup na obiekcie Context. Domyślnie konstruktor new InitialContext() będzie korzystał z serwera o adresie localhost i porcie 3700 (w tym przypadku będzie to domena domain1). Aby to zmienić i skorzystać z ziarna w domenie domain2, trzeba skorzystać z konstruktora new InitialContext(Properties properties).

public class HelloClients {

public static void main(String[] args) throws Exception {
Context ctx = new InitialContext();
Hello hello = (Hello) ctx.lookup("pl.matt.interfaces.Hello");

System.out.println(hello.hello());

Properties properties = new Properties();
properties.setProperty("org.omg.CORBA.ORBInitialHost", "localhost");
properties.setProperty("org.omg.CORBA.ORBInitialPort", "38742");

Context context = new InitialContext(properties);
hello = (Hello) context.lookup("pl.matt.interfaces.Hello");
System.out.println(hello.hello());

}

}

podając odpowiednie ustawienia właściwości org.omg.CORBA.ORBInitialHost i org.omg.CORBA.ORBInitialPort uzyskuję dostęp do ziarna EJB z domeny domain2.

Aplikacja wymaga w swojej ścieżce klas plików appserv-rt.jar oraz j2ee.jar, które znajdują się w podkatalogu lib GlassFisha.

Do kompletu brakuje jeszcze pliku pom.xml aplikacji klienckiej:

<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>pl.matt</groupId>
<artifactId>ejb3-client</artifactId>
<version>1.0</version>
<packaging>jar</packaging>

<properties>
<JAVA_HOME>/usr/lib/jvm/java-6-sun</JAVA_HOME>
</properties>

<dependencies>
<dependency>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-ejb_3.0_spec</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<finalName>ejb3-client</finalName>
<directory>/tmp/mvn-target/</directory>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<verbose>true</verbose>
<fork>true</fork>
<executable>${JAVA_HOME}/bin/javac</executable>
<compilerVersion>1.6</compilerVersion>
<source>1.6</source>
<target>1.6</target>
<debug>true</debug>
<optimize>false</optimize>
</configuration>
</plugin>
</plugins>
</build>
</project>

zależność

<dependency>
<groupId>pl.matt</groupId>
<artifactId>ejb3-slsb</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>

wprowadza widoczność zdalnego interfejsu Hello w aplikacji klienckiej.

Czas aplikację uruchomić.
Na konsoli wypisuje się odpowiednio:

domain1
domain2

Czyli odpalone zostało ziarno EJB w różnych domenach. Pełen sukces.



Natknąłem się na informacje, że ziarna EJB 3 można uruchamiać klientami napisanymi w technologii EJB 2. Udało się wam to osiągnąć?