lunes, 9 de julio de 2007

Entender las conversaciones: iniciar una conversación

Los contextos en Seam facillitan al desarrollador controlar (inicialización, finalización recursos implicados) el ciclo de vida de la información(componentes) implicada en la realización de una tarea.
Los contextos tradicionales eran los de aplication, session y request. JBoss-Seam principalmente introduce el contexto de conversation para representar el concepto de task(tarea que realiza el usuario [reserva hotel, edición registro ...]).
Hay que distinguir entre conversation y long running conversation, únicamente la 2ª mantiene el estado entre diferentes peticiones a lo largo de la realización de la tarea (task).

De la misma forma que utilizamos el contexto de session o aplicacion sin tener que crearlo, el contexto de conversation existe sin necesidad de crearlo, lo único que como desarrolladores debe preocuparnos es la inyección en dicho contexto de los componentes que nos interesen. Al anotar un componente como @Conversation y anotar un metodo con la etiqueta @Begin, lo único que estamos haciendo es enviar a dicho contexto el componente y su estado correspondiente.


Es decir: Una conversación NO ES un componente:
@Scope(ScopeType.CONVERSATION)
@Name("carrito")
public class carritoCompra{}

Por ello es fundamental entender la forma en como iniciamos una conversación en Seam.
en el ejemplo de seam-booking se realiza de la siguiente forma:
desde la pagina main.seam llamamos al componente de tipo @Conversation y su metodo hotelBooking.selectHotel(hot) anotado con la etiqueta @Begin, dejando al pages.xml el control de la navegación entre páginas (es decir en caso de estar registrado reenvía al usuario a la pagina hotel.seam)


<page view-id="/main.xhtml"
login-required="true">
<navigation from-action="#{hotelBooking.selectHotel(hot)}">
<redirect view-id="/hotel.xhtml"/>
</navigation>
<navigation from-action="#{bookingList.cancel}">
<redirect/>
</navigation>
</page>







Por otro lado podemos examinar el ejemplo de seam-issues para apreciar que el componente que inicia las conversaciones no tiene porque mantener su estado, que se anota con Stateless (sin estado), siendo los componentes implicados en la conversación y Stateful (con estado) los que mantienen el estado de la conversación:


@Stateless
@Name("projectSelector")
public class ProjectSelectorBean implements ProjectSelector {

@In
private transient Map messages;

@Logger
private Log log;


@In(create=true)
private transient ProjectEditor projectEditor;


@In(create=true)
private transient ProjectFinder projectFinder;

@Begin()
public String select() {
projectEditor.setInstance( projectFinder.getSelection() );
return "editProject";
}
}



@Name("projectEditor")
@Stateful
public class ProjectEditorBean implements ProjectEditor {
...
public void setInstance(Project instance) {
isNew = false;
this.project = instance;
}

...

}


viernes, 6 de julio de 2007

internacionalización en Seam: Acceder al componente messages para cambiar sus valores

@Name("ejemplo")
class EjemploBean implements Ejemplo{


@In
private transient Map messages;

public String getButtonLabel() {
return messages.get("View");
}
}

mantener varias conversaciones siguiendo el ejemplo de seam-issues

¿quién inicia las conversaciones en este proyecto?
pues es un bean stateless

@Stateless
@Name("projectSelector")
public class ProjectSelectorBean implements ProjectSelector {

@In
private transient Map messages;

@Logger
private Log log;


@In(create=true)
private transient ProjectEditor projectEditor;

@In()
Conversation conversation;

@In(create=true)
private transient ProjectFinder projectFinder;

@Begin()
public String select() {
projectEditor.setInstance( projectFinder.getSelection() );
return "editProject";
}

public String getButtonLabel() {
return messages.get("View");
}

public boolean isCreateEnabled() {
return true;
}

}

@Name("projectEditor")
@Stateful
public class ProjectEditorBean implements ProjectEditor {
...
}

miércoles, 4 de julio de 2007

Herencia en clases persistentes- ejemplo DVD store

La herencia en este caso se implementa con un campo en la base de datos que hace de discriminador @DiscriminatorValue, de esta forma las dos entidades hijas se encuentran en la misma tabla de la base datos.
Atención lapropiedad que hace de discriminator value no se declara en el código java, solo a nivel de anotación de clase

Clase padre: User

@Entity
@Table(name="USERS")
public abstract class User
implements Serializable
{
long id;

String userName;
String password;

String firstName;
String lastName;

@Id @GeneratedValue
@Column(name="USERID")
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}

@Column(name="USERNAME",unique=true,nullable=false,length=50)
@NotNull
@Length(min=4,max=16)
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}

@Column(name="PASSWORD",nullable=false,length=50)
@NotNull
@Length(min=6,max=50)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

@Column(name="FIRSTNAME",length=50)
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name="LASTNAME",length=50)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}

@Transient
public boolean isAdmin() {
return false;
}
}


Clase hija: Admin
@Entity
@DiscriminatorValue("admin")
public class Admin
extends User
implements Serializable
{
@Transient
public boolean isAdmin() {
return true;
}

}



Clase hija: Customer
@Entity
@Name("customer")
@DiscriminatorValue("customer")
public class Customer
extends User
implements Serializable
{
public static String[] cctypes = {"MasterCard", "Visa", "Discover", "Amex", "Dell Preferred"};

String address1;
String address2;
String city;
String state;
String zip;

String email;
String phone;

Integer creditCardType = 1;
String creditCard = "000-0000-0000";
int ccMonth = 1;
int ccYear = 2005;


public Customer() {
}


@Column(name="ADDRESS1",length=50)
@NotNull
public String getAddress1() {
return address1;
}
public void setAddress1(String address1) {
this.address1 = address1;
}

@Column(name="ADDRESS2",length=50)
@NotNull
public String getAddress2() {
return address2;
}
public void setAddress2(String address2) {
this.address2 = address2;
}

@Column(name="CITY",length=50)
@NotNull
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}


@Column(name="STATE",length=2)
@NotNull
@Length(min=2,max=2)
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}

@Column(name="ZIP", length=10)
@Length(min=5, max=10)
@Pattern(regex="[0-9]{5}(-[0-9]{4})?", message="not a valid zipcode") // {validator.zip}
@NotNull
public String getZip() {
return zip;
}
public void setZip(String zip) {
this.zip = zip;
}

@Column(name="EMAIL",length=50)
@Email
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}

@Column(name="PHONE",length=50)
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}


@Column(name="CREDITCARDTYPE")
public Integer getCreditCardType() {
return creditCardType;
}
public void setCreditCardType(Integer type) {
this.creditCardType = type;
}

@Transient public String getCreditCardTypeString() {
if (creditCardType<1>cctypes.length) {
return "";
}
return cctypes[creditCardType-1];
}

@Column(name="CC_NUM", length=50)
public String getCreditCard() {
return creditCard;
}
public void setCreditCard(String creditCard) {
this.creditCard = creditCard;
}

@Column(name="CC_MONTH", length=50)
public int getCreditCardMonth() {
return ccMonth;
}
public void setCreditCardMonth(int ccMonth) {
this.ccMonth = ccMonth;
}

@Column(name="CC_YEAR", length=50)
public int getCreditCardYear() {
return ccYear;
}
public void setCreditCardYear(int ccYear) {
this.ccYear = ccYear;
}

@Transient
public String getCreditCardExpiration() {
return "" + ccMonth + "/" + ccYear;
}

@Override
public String toString() {
return "Customer#" + getId() + "(" + userName + ")";
}

}

Crear estadisticas en Seam con programación orientada a aspectos

En el ejemplo del blog podemos aprender como grabar estadisticas con un enfoque AOP, es decir manteniendo esta finalidad totalmente separada de la finalidad del blog que sería mostrar las entradas de forma cronológica o filtrada por categorías

En JBoss Seam hay varias formas de aplicar AOP (Aspect Oriented Programming) a nuestro código OOP (Object Oriented Programming): por medio de anotaciones, a través de jBPM-pageflow, a través del archivo pages.xml, components.xml ...

En este caso, el ejemplo del blog, se utiliza el archivo pages.xml para definir una acción (grabar estadística) relacionada con otro aspecto de la aplicación (seam blog)

en el pages.xml se incluyen las siguientes lineas:

<page view-id="*">
<action execute="#{blog.hitCount.hit}"/>
</page>


Estas lineas indican que al entrar en cualquier página de la aplicación (view-id="*") se ejecuta la acción blog.hitCount.hit
Dentro de las facilidades que nos proporciona el pages.xml se encuentra el condicional if dentro de la etiqueta action, con el cual podemos supeditar la acción al estado de cualquier componente de la aplicacion por ejemplo action execute="#{gestor.editarEntrada}" if="#{usuario.admin}"

A continuación se muestran las entidades participantes en esta acción, que al ser entidades persistentes y al utilizar en este ejemplo el org.jboss.seam.jsf.TransactionalSeamPhaseListener, en el renderizado de cada petición de página se produce una transacción y la consecuente actualización de datos en las entidades persistentes, con lo cual no tenemos que llamar al metodo persist() para que nuestros datos se actualicen en la base de datos

@Entity
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Blog {
....
public HitCount getHitCount()
{
return hitCount;
}

}


@Entity
public class HitCount
{
@Id
@Column(name="blog_name")
private String blogName;

@OneToOne(optional=false)
@JoinColumn(insertable=false, updatable=false)
private Blog blog;

private int pageviews;

public int getPageviews()
{
return pageviews;
}
public void hit()
{
System.out.print(FacesContext.getCurrentInstance().getViewRoot().getViewId());
pageviews++;
}
}



Configuración del faces-config para la gestión de persistencia

<faces-config>
<!-- Facelets support -->
<application>
<message-bundle>messages</message-bundle>
<view-handler>org.jboss.seam.ui.facelet.SeamFaceletViewHandler</view-handler>
</application>
<!-- Select one of the two standard persistence lifecycle models for the Seam application -->
<lifecycle>
<phase-listener>org.jboss.seam.jsf.TransactionalSeamPhaseListener</phase-listener>
</lifecycle>
</faces-config>

lunes, 2 de julio de 2007

enlazar dos pageflow (flujos de negocio)

En el ejemplo de numberguess encontramos que existen dos procesos (pageflow) enlazados, estos son:
  • pageflow.jpdl.xml
    • esta definición de proceso controla el problema de la adivinación de un número en una serie de posibilidades
  • cheat.jpdl.xml
    • esta controla el proceso de desvelar el número a adivinar y volver al flujo anterior