środa, 25 lutego 2009

Jazoon Rookie

Jesteś młody, żądny przygód? Masz mniej niż 26 lat? Masz coś ciekawego do powiedzenia o Javie? Doskonale się składa. Zawody Jazoon Rookie są właśnie dla Ciebie.
Jesteś młody, żądny przygód? Masz mniej niż 26 lat? Nie masz nic ciekawego do powiedzenia o Javie? Jeszcze lepiej. Zawody Jazoon Rookie są właśnie dla Ciebie.

Jazoon Rookie to zawody, w których zmierzy się trzech prelegentów wraz ze swoimi 20 minutowymi prelekcjami. Aby stać się jednym z nich, wystarczy przygotować abstrakt 20 minutowej prezentacji i zgłosić się do 15 marca 2008. Termin ten był już 2 razy przekładany, więc zgłoszeń nie ma chyba zbyt dużo. Warto więc spróbować.

Spośród zgłoszeń zostanie wybranych dziesięciu uczestników. Nagrają oni fragment swoich prelekcji. Spośród tych nagrań zostanie wyłoniona trójka finalistów. Każdy z nich zaprezentuje się na konferencji Jazoon'09. Do tego otrzyma darmową wejściówkę na konferencje, transport i zakwaterowanie.

Szczegółowe informacje znajdziesz na stronie Jazoon Rookie.

Zgłoś się koniecznie. Najwyższy czas, żeby ktoś z Polski pokazał, kto rządzi w Javie.

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ąć?

piątek, 26 grudnia 2008

Jazoon09 już w czerwcu, już w Zurychu

Na moim lokalnym, WJUGowym podwórku 2 razy do roku odbwają interesujące konferencje. Wiosenna Jawarsovia i jesienna Warsjawa. Na naszym mniej lokalnym krajowym podwórzu mamy do tego Java Developers' Day oraz nowość, międzynarodowy GeeCON (7-8 Maj). Mnie jak dotąd żadnej pozawarszawskiej konferencji nie udało się odwiedzić. Szkoda, wszak podróże kształcą.

Okazji do zmiany tego stanu rzeczy będzie w nadchodzącym roku co niemiara. Oprócz polskich wydarzeń rokrocznie na jawowym kalendarzu pojawia się kilka europejskich i światowych pozycji. Jedną z nich będzie Jazoon. Trzydniowa, czerwcowa konferencja w Zurychu. Byłem już w Zurychu i było całkiem fajnie. Nie od dziś wiadomo, że "jestem umysł ścisły. Mnie się podobają melodie, które już raz słyszałem." więc i miejsca które już widziałem powinny być warte uwagi. Zatem mam nadzieję pojawić się tamże pomiędzy 22 a 25 czerwca 2009 przy okazji konferencji Jazoon.

Niestety program Jazoon09 jest jeszcze wielką niewiadomą. Znanych jest tylko kilku pierwszych prezenterów: Neal Ford, Danny Coward, Roberto Chinnici, Jérôme Dochez. Pozostali powinni się zgłosić do 15 stycznia, więc pewnie niedługo poinformuję siebie i was bardziej szczegółowo.

Nie do końca bezinteresownie, bo mam za to szansę na darmową wejściówkę. Impreza niestety nie jest bezpłatna, uczestnictwo kosztuje 1450-1810 franków szwajcarskich.

Wybierasz się na Jazoon09? Może byłeś na jakiejś innej konferencji? Podziel się opinią w komentarzu!

poniedziałek, 8 grudnia 2008

Bezpieczeństwo szybko, łatwo i przyjemnie czyli wstęp do Spring Security

W znakomitej większości aplikacji internetowych mamy jakiegoś użytkownika, mamy jakieś konto... Gdzieś się rejestrujemy, gdzieś się logujemy i hulaj dusza po serwisie. Podczas projektowania tychże aplikacji, pojawia się dylemat: czy skorzystać z gotowego rozwiązania, czy zaimplementować uwierzytelnianie i autoryzację samemu? Co będzie łatwiejsze, co będzie bezpieczniejsze, co będzie lepsze? Jeśli gotowe rozwiązanie, to jakie? i na co mi taka armata na muchę?
Trudne pytania. Ja na nie odpowiedź mam jednak gotową: Spring Security.

Niezależny od wykorzystywanego szkieletu aplikacji, posiadający ogromne możliwości a przy tym prosty w użyciu.

No ale dość tych peanów. Zobaczmy jak to wygląda w akcji. Kilka tygodni temu opisywałem sposób na uprowadzenie sesji HTTP. Zobaczmy, jak na przykładzie przedstawionej tam aplikacji JSF wprowadzić do projektu Spring Security.

Najpierw trzeba dodać garść bibliotek:
aopalliance-1.0.jar, spring-context-2.0.8.jar, spring-security-core-tiger-2.0.4.jar, aspectjrt-1.5.4.jar, spring-core-2.0.8.jar, spring-web-2.0.8.jar, spring-aop-2.0.8.jar, spring-dao-2.0.8.jar, spring-beans-2.0.8.jar, spring-security-core-2.0.4.jar

Wszystkie są do znalezienia w paczce Spring Security 2.0.4.

Jeżeli chcemy, aby obiekty napisanej przez nas klasy reprezentowały użytkowników aplikacji, klasa ta musi implementować interfejs org.springframework.security.userdetails.UserDetails która zawiera kilka metod, dzięki którym Spring Security orientuje się kto jest kto i co może zrobić w naszym systemie.

 
public interface UserDetails extends Serializable {
/**
* Returns the authorities granted to the user. Cannot return <code>null</code>.
*
* @return the authorities, sorted by natural key (never <code>null</code>)
*/
GrantedAuthority[] getAuthorities();

/**
* Returns the password used to authenticate the user. Cannot return <code>null</code>.
*
* @return the password (never <code>null</code>)
*/
String getPassword();

/**
* Returns the username used to authenticate the user. Cannot return <code>null</code>.
*
* @return the username (never <code>null</code>)
*/
String getUsername();

/**
* Indicates whether the user's account has expired. An expired account cannot be authenticated.
*
* @return <code>true</code> if the user's account is valid (ie non-expired), <code>false</code> if no longer valid
* (ie expired)
*/
boolean isAccountNonExpired();

/**
* Indicates whether the user is locked or unlocked. A locked user cannot be authenticated.
*
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
boolean isAccountNonLocked();

/**
* Indicates whether the user's credentials (password) has expired. Expired credentials prevent
* authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie non-expired), <code>false</code> if no longer
* valid (ie expired)
*/
boolean isCredentialsNonExpired();

/**
* Indicates whether the user is enabled or disabled. A disabled user cannot be authenticated.
*
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
boolean isEnabled();
}


Na uwagę zasługuje metoda GrantedAuthority[] getAuthorities(). Zwraca ona tablicę obiektów, reprezentujących role.

Zmieniam zatem klasę pl.matt.model.User mojej aplikacji tak, aby implementowała ona wymieniony wyżej interfejs:

 
public class User implements UserDetails {
/**
*
*/
private static final long serialVersionUID = 853438034988558585L;
private String login;
private String password;
private int balance;
private GrantedAuthority[] authorities = new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_USER") };

public String getLogin() {
return login;
}

public void setLogin(String login) {
this.login = login;
}

public String getPassword() {
return password;
}

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

public int getBalance() {
return balance;
}

public void setBalance(int balance) {
this.balance = balance;
}

@Override
public GrantedAuthority[] getAuthorities() {
return authorities;
}

@Override
public String getUsername() {
return login;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}

}


Do działania modelu bezpieczeństwa niezbędna będzie jeszcze jedna klasa usługowa, implementująca interfejs org.springframework.security.userdetails.UserDetailsService.

Zawiera on tylko jedną metodę:
 
UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException;


która jak sama nazwa wskazuje, ładuje użytkownika o zadanej nazwie.

Implementacja tego interfejsu w mojej aplikacji to klasa UserDetailsServiceImpl

 
public class UserDetailsServiceImpl implements UserDetailsService {

private UserManager userManager = new UserManager();

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
User user = userManager.loadUser(username);
if (user != null) {
return user;
}
throw new UsernameNotFoundException(username);
}

}


klasę UserManager pozostawiam bez zmian. Z tym, że poprzednio była ona ziarnem zarządzanym JSF. Teraz nie ma już takiej potrzeby.

Spring Security przychodzi z kilkoma implementacjami interfejsu UserDetailsServiceImpl, między innymi z implementacją korzystającą z użytkowników zapisanych w bazie danych lub przechowującą użytkowników w pamięci. Na potrzeby mojej aplikacji żadna z nich nie będzie przydatna.

Oprócz dwóch powyższych klas stworzyłem również klasę UserLoginBean umożliwiającą wyświetlenie zalogowanego użytkownika na stronie JSF.
 
public class UserLoginBean {

private User loggedUser;

public User getLoggedUser() {
if (loggedUser == null) {
HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
UsernamePasswordAuthenticationToken principal = (UsernamePasswordAuthenticationToken) request.getUserPrincipal();
loggedUser = (User) principal.getPrincipal();
}
return loggedUser;
}

}


UserLoginBean jest ziarnem zarządzanym JSF o zasięgu żądania. Dzięki temu na stronach JSF uzyskuję dostęp do zalogowanego użytkownika poprzez wyrażenie EL #{userLoginBean.loggedUser}
Oto cały 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>userLoginBean</managed-bean-name>
<managed-bean-class>pl.matt.UserLoginBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>
<navigation-rule>
<from-view-id>/pages/login.xhtml</from-view-id>
<navigation-case>
<from-outcome>success</from-outcome>
<to-view-id>/secure/home.xhtml</to-view-id>
</navigation-case>
<navigation-case>
<from-outcome>faliture</from-outcome>
<to-view-id>/pages/login.xhtml</to-view-id>
</navigation-case>
</navigation-rule>
<application>
<view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
</application>
</faces-config>


No i to by było na tyle... gdyby nie potrzeba konfiguracji Spring Security. Dodaję zatem do pliku web.xml po jednym jeden filtrze, słuchaczu i parametrze konfiguracyjnym

 

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

<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>


Wartość parametru contextConfigLocation wskazuje położenie pliku ze szczegułami konfiguracji Spring Security.
Tworzę zatem i plik /WEB-INF/applicationContext-security.xml


<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.1.xsd">

<global-method-security secured-annotations="enabled">
</global-method-security>

<http auto-config="true">
<intercept-url pattern="/secure/**" access="ROLE_USER" />
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<form-login login-page="/pages/login.jsf" />
</http>

<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<custom-authentication-provider />
</beans:bean>

<beans:bean id="userDetailsService" class="pl.matt.security.UserDetailsServiceImpl" />

</beans:beans>


W zasadzie na pierwszy rzut oka wiadomo o co chodzi.

 
<http auto-config="true">
<intercept-url pattern="/secure/**" access="ROLE_USER" />
<intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<form-login login-page="/pages/login.jsf" />
</http>


<http auto-config="true"> włącza predefiniowane ustawienia konfiguracyjne, z których zmieniam położenie formularza do logowania na /pages/login.jsf, oraz określam które urle będą dostępne dla użytkowników o określonych uprawnieniach. W moim przypadku strony zaczynające się od /secure/ będą widoczne tylko dla użytkowników o roli ROLE_USER.


W duecie daoAuthenticationProvider i userDetailsService
 
<beans:bean id="daoAuthenticationProvider"
class="org.springframework.security.providers.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService" />
<custom-authentication-provider />
</beans:bean>

<beans:bean id="userDetailsService" class="pl.matt.security.UserDetailsServiceImpl"

który generalnie wskazuje, której usługi będziemy używać do autentykacji użytkowników (można używać wielu usług jednocześnie) na uwagę zasługuje znacznik <custom-authentication-provider />. Imformuje on Spring Security, że nie będziemy używać domyślnej usługi przechowującej użytkowników w pamięci, ale naszej implementacji.

Zobaczmy jeszcze stronę logowania login.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">Login form</ui:define>
<ui:define name="pageHeader">Proszę się zalogować</ui:define>
<ui:define name="body">
<form action="../j_spring_security_check">
<table>
<tr>
<td>User:</td>
<td><input type='text' name='j_username' /></td>
</tr>
<tr>
<td>Password:</td>
<td><input type='password' name='j_password' /></td>
</tr>
<tr>
<td><input type="checkbox" name="_spring_security_remember_me" /></td>
<td>Don't ask for my password for two weeks</td>
</tr>

<tr>
<td colspan='2'><input name="submit" type="submit" /></td>
</tr>
</table>
</form>
</ui:define>
</ui:composition>
</html>


Specjalna wartość parametru action formularza, oraz jego nazwy pól pozwalają się zorientować Spring Security, że podjęto próbę logowania.

Całą aplikację można pobrać tutaj.
Dla chętnych pozostawiam sprawdzenie odporności tej wersji na atak uprowadzenia sesji i inne ataki.

To by zatem było na tyle. Niewielkim nakładem pracy dodałem bardzo potężne narzędzie do zarządzania bezpieczeństwem aplikacji. Co prawda wykorzystałem tylko mały fragment jego możliwości, ale muszę jeszcze mieć o czym pisać na blogu...

czwartek, 27 listopada 2008

Się działo się... Warsjawa Eclipse DemoCamp 2008

Było minęło... ale, że było fajnie, opiszę pokrótce jak to miało miejsce.

Warsjawa Eclipse DemoCamp 2008, odbyła się w ubiegłą sobotę, 22 listopada na terenie wydziału MIMUW Uniwersytetu Warszawskiego. Swoją obecnością zaszczyciło nas ponad 150 słuchaczy, ale zacznijmy od początku (relacja z czuba i na żywo)...

a na początku był Waldemar Kot. Waldi po nieudanych inwestycjach w przemysł księgarski



powrócił w klimaty bliższe naszemu sercu. Jego prezentację dotyczącą mechanizmu Publish-Subscribe wysłuchała pełna sala.



Było jak zwykle u Waldiego. Ciekawie, fachowo i konkretnie. Po prostu rewelacja. Choć w sobotę rano nikt jeszcze w to nie wierzył, poziom widowiska nie opadł do sameog końca. Kolejna prelekcja "Taking SQL IDEs from the Stone Age to the 21st century" Wassima Mełhema tylko podsyciła apetyt.

Szczęście w nieszczęściu, że zaraz potem był czas na przerwę. Obiadową. Na każdego czekał kotlecik oraz zupka. Mniam...



To co zapowiadała pierwsza część spotkania w drugiej stało się faktem. Worek z bramkami się rozwiązał. Co prawda Eclipse RPC nie do końca zmieścił się w przeglądarce Jacek Pospychały, ale to był dopiero początek emocji. Telekomunikacja w Javie pełna była pełna ostrej walki bark w bark Tomasz Zieleniewskiego oraz Waldemara Kota. Na szczęście sędziowie byli stanowczy i konsekwentni. O żadnych zadymach nie mogło być zatem mowy. W dogrywce 2 krótsze prezentacje - Dojo Toolkit Łukasza Lenarta oraz Spring Dynamic Modules Agaty i Jacka Laskowskich i każdy mógł być zadowolony ze zwycięskiego remisu.

Tenże uczciliśmy w Lolku. Co prawda nikt się nie spodziewał tylu zwycięzców i miejsc brakło, ale cóż. Taka okazja nie trafia się na co dzień...



Było super. Pozostaje mi tylko mieć nadzieję, że to nie pierwszy i nie ostatni raz.

poniedziałek, 17 listopada 2008

Warsjawa Eclipse DemoCamp 2008

Już w tą sobotę, 22 listopada w Warszawie w budynku MIMUW przy ul. Banacha 2 w sali 5440 w godz. 9:00-17:00 odbędzie się konferencja Warsjawa Eclipse DemoCamp 2008 organizowana przez Warszawski JUG. Konferencja dotyczy tego, co w jawie piszczy, a dokładniej:

09:00 - 09:45 Rejestracja (w trakcie: kawa/herbata, stół szwedzki)
09:45 - 11:15 Comet, Bayeux i mechanizm Publish-Subscribe poprzez HTTP - Waldemar "waldi" Kot (Warszawa JUG)
11:30 - 13:00 Taking SQL IDEs from the Stone Age to the 21st century - Wassim Melhem (Eclipse Foundation)
13:00 - 14:00 Przerwa obiadowa (zarejestrowani bezpłatnie!)
14:00 - 14:45 Czy Eclipse RCP mieści się w przeglądarce? - Jacek Pospychała (IBM Eclipse Support Center)
14:50 - 15:35 Telekomunikacja w Javie - kilka słów o konwergencji i usługach w telekomunikacji - Tomasz Zieleniewski (TouK)
15:40 - 16:10 Dojo Toolkit - Łukasz Lenart (Warszawa JUG)
16:15 - 16:45 OSGi + Spring Framework = Spring Dynamic Modules - Jacek Laskowski (Warszawa JUG)
16:45 - 17:00 Losowanie nagrody głównej - Kapituła Warsjavy
17:00 Spotkania integracyjnego czas zacząć! - Kapituła Warsjavy

Zauważyliście pewnie kilka frykasów: stół szwedzki, obiadek, imprezka, tajemnicza nagroda główna... To dzięki naszym wspaniałym sponsorom:
7N, Javatech, e-point, Touk, Eclipse, JetBrains.

Konferencja jest całkowicie bezpłatna, pod warunkiem rejestracji. Osobiście będę sprawdzał listę obecności ;).

Więcej informacji na stronie warsjawa.pl

W imieniu organizatorów serdecznie zapraszam.