234 lines
10 KiB
XML
234 lines
10 KiB
XML
|
<chapter id="events">
|
||
|
<title>Interceptores y eventos</title>
|
||
|
|
||
|
<para>
|
||
|
Frecuentemente es útil para la aplicación reaccionar a ciertos eventos que ocurran dentro de Hibernate.
|
||
|
Esto permite la implementación de ciertos tipos de funcionalidade genérica, y extensión de la
|
||
|
funcionalidad de Hibernate.
|
||
|
</para>
|
||
|
|
||
|
<sect1 id="objectstate-interceptors" revision="1">
|
||
|
<title>Interceptores</title>
|
||
|
|
||
|
<para>
|
||
|
La interface <literal>Interceptor</literal> provee callbacks desde la sesión a la aplicación
|
||
|
permitiendo a ésta última inspeccionar y/o manipular las propiedades de un objeto persistente
|
||
|
antes que sea salvado, actualizado, borrado o cargado. Un uso posible de esto es seguir la pista
|
||
|
de información de auditoría. Por ejemplo, el siguiente <literal>Interceptor</literal> establece
|
||
|
automáticamente el <literal>createTimestamp</literal> cuando un <literal>Auditable</literal> es
|
||
|
creado y actualiza la propiedad <literal>lastUpdateTimestamp</literal> cuando un
|
||
|
<literal>Auditable</literal> es acutalizado.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[package org.hibernate.test;
|
||
|
|
||
|
import java.io.Serializable;
|
||
|
import java.util.Date;
|
||
|
import java.util.Iterator;
|
||
|
|
||
|
import org.hibernate.Interceptor;
|
||
|
import org.hibernate.type.Type;
|
||
|
|
||
|
public class AuditInterceptor implements Interceptor, Serializable {
|
||
|
|
||
|
private int updates;
|
||
|
private int creates;
|
||
|
|
||
|
public void onDelete(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] state,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
// do nothing
|
||
|
}
|
||
|
|
||
|
public boolean onFlushDirty(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] currentState,
|
||
|
Object[] previousState,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
|
||
|
if ( entity instanceof Auditable ) {
|
||
|
updates++;
|
||
|
for ( int i=0; i < propertyNames.length; i++ ) {
|
||
|
if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) {
|
||
|
currentState[i] = new Date();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean onLoad(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] state,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public boolean onSave(Object entity,
|
||
|
Serializable id,
|
||
|
Object[] state,
|
||
|
String[] propertyNames,
|
||
|
Type[] types) {
|
||
|
|
||
|
if ( entity instanceof Auditable ) {
|
||
|
creates++;
|
||
|
for ( int i=0; i<propertyNames.length; i++ ) {
|
||
|
if ( "createTimestamp".equals( propertyNames[i] ) ) {
|
||
|
state[i] = new Date();
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public void postFlush(Iterator entities) {
|
||
|
System.out.println("Creations: " + creates + ", Updates: " + updates);
|
||
|
}
|
||
|
|
||
|
public void preFlush(Iterator entities) {
|
||
|
updates=0;
|
||
|
creates=0;
|
||
|
}
|
||
|
|
||
|
...
|
||
|
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
El interceptor podría ser especificado cuando se crea la sesión:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Puedes además establecer un interceptor a un nivel global, usando la <literal>Configuration</literal>:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[new Configuration().setInterceptor( new AuditInterceptor() );]]></programlisting>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-events" revision="2">
|
||
|
<title>Sistema de eventos</title>
|
||
|
|
||
|
<para>
|
||
|
Si tienes que reaccionar a eventos particulares en tu capa de persistencia, puedes también la
|
||
|
arquitectura de <emphasis>eventos</emphasis> de Hibernate3. El sistema de eventos puede ser usado
|
||
|
en adición o como un remplazo a los interceptores.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Esencialmente todos los métodos de la interface <literal>Session</literal> se correlacionan
|
||
|
con un evento. Tienes un <literal>LoadEvent</literal>, un <literal>FlushEvent</literal>, etc
|
||
|
(consulta el DTD del fichero de configuración XML o el paquete <literal>org.hibernate.event</literal>
|
||
|
para la lista completa de tipos de evento definidos). Cuando se hace una petición de uno de estos
|
||
|
métodos, la <literal>Session</literal> de Hibernate genera un evento apropiado y se lo pasa
|
||
|
al oyente (listener) de eventos configurado para ese tipo. De fábrica, estos oyentes implementan
|
||
|
el mismo procesamiento en los que siempre resultan aquellos métodos. Sin embargo, eres libre de
|
||
|
implementar una personalización de una de las interfaces oyentes (es decir, el
|
||
|
<literal>LoadEvent</literal> es procesado por la implementación registrada de la interface
|
||
|
<literal>LoadEventListener</literal>), en cuyo caso su implementación sería responsable
|
||
|
de procesar cualquier petición <literal>load()</literal> hecha a la <literal>Session</literal>.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Los oyentes deben ser considerados efectivamente singletons; quiere decir, que son compartidos
|
||
|
entre las peticiones, y por lo tanto no guardan ningún estado en variables de instancia.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Un oyente personalizado debe implementar la interface apropiada para el evento que quiere procesar y/o
|
||
|
extender una de las clases base de conveniencia (o incluso los oyentes de eventos por defecto
|
||
|
usados por Hibernate de fábrica al ser éstos declarados non-final para este propósito). Los
|
||
|
oyentes personalizados pueden ser registrados programáticamente a través del objeto
|
||
|
<literal>Configuration</literal>, o especificados en el XML de configuración de Hibernate
|
||
|
(la declaración declarativa a través del fichero de propiedades no está soportada).
|
||
|
He aquí un ejemplo de un oyente personalizado de eventos load:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[public class MyLoadListener extends DefaultLoadEventListener {
|
||
|
// this is the single method defined by the LoadEventListener interface
|
||
|
public Object onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
|
||
|
throws HibernateException {
|
||
|
if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
|
||
|
throw MySecurityException("Unauthorized access");
|
||
|
}
|
||
|
return super.onLoad(event, loadType);
|
||
|
}
|
||
|
}]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Necesitas además una entrada de configuración diciéndole a Hibernate que use el
|
||
|
oyente en vez del oyente por defecto:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<hibernate-configuration>
|
||
|
<session-factory>
|
||
|
...
|
||
|
<listener type="load" class="MyLoadListener"/>
|
||
|
</session-factory>
|
||
|
</hibernate-configuration>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
En cambio, puedes registrarlo programáticamente:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[Configuration cfg = new Configuration();
|
||
|
cfg.getSessionEventListenerConfig().setLoadEventListener( new MyLoadListener() );]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Los oyentes registrados declarativamente no pueden compartir instancias. Si el mismo nombre de clase es
|
||
|
usado en múltiples elementos <literal><listener/></literal>, cada referencia resultará en una instancia
|
||
|
separada de esa clase. Si necesitas la capacidad de compartir instancias de oyentes entre tipos de oyente
|
||
|
debes usar el enfoque de registración programática.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
¿Por qué implementar una interface y definir el tipo espcífico durante la configuración?
|
||
|
Bueno, una implementación de oyente podría implementar múltiples interfaces de oyente
|
||
|
de eventos. Teniendo el tipo definido adicionalmente durante la registración lo hace más
|
||
|
fácil para activar o desactivar oyentes personalizados durante la configuración.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
<sect1 id="objectstate-decl-security">
|
||
|
<title>Seguridad declarativa de Hibernate</title>
|
||
|
<para>
|
||
|
Usualmente, la seguridad declarativa en aplicaciones Hibernate es manejada en una capa de fachada
|
||
|
de sesión. Ahora, Hibernate3 permite que ciertas acciones sean permitidas vía JACC, y autorizadas vía
|
||
|
JAAS. Esta en una funcionalidad opcional construída encima de la arquitectura de eventos.
|
||
|
</para>
|
||
|
|
||
|
<para>
|
||
|
Primero, debes configurar los oyentes de eventos apropiados, para habilitar el uso de
|
||
|
autorización JAAS.
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/>
|
||
|
<listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/>
|
||
|
<listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/>
|
||
|
<listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Seguido, aún en <literal>hibernate.cfg.xml</literal>, liga los permisos a roles:
|
||
|
</para>
|
||
|
|
||
|
<programlisting><![CDATA[<grant role="admin" entity-name="User" actions="insert,update,read"/>
|
||
|
<grant role="su" entity-name="User" actions="*"/>]]></programlisting>
|
||
|
|
||
|
<para>
|
||
|
Los nombres de role son los roles entendidos por tu proveedor de JACC.
|
||
|
</para>
|
||
|
|
||
|
</sect1>
|
||
|
|
||
|
</chapter>
|
||
|
|