hibernate-orm/reference/es/modules/events.xml

234 lines
10 KiB
XML

<chapter id="events">
<title>Interceptores y eventos</title>
<para>
Frecuentemente es &#x00fa;til para la aplicaci&#x00f3;n reaccionar a ciertos eventos que ocurran dentro de Hibernate.
Esto permite la implementaci&#x00f3;n de ciertos tipos de funcionalidade gen&#x00e9;rica, y extensi&#x00f3;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&#x00f3;n a la aplicaci&#x00f3;n
permitiendo a &#x00e9;sta &#x00fa;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&#x00f3;n de auditor&#x00ed;a. Por ejemplo, el siguiente <literal>Interceptor</literal> establece
autom&#x00e1;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&#x00ed;a ser especificado cuando se crea la sesi&#x00f3;n:
</para>
<programlisting><![CDATA[Session session = sf.openSession( new AuditInterceptor() );]]></programlisting>
<para>
Puedes adem&#x00e1;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&#x00e9;n la
arquitectura de <emphasis>eventos</emphasis> de Hibernate3. El sistema de eventos puede ser usado
en adici&#x00f3;n o como un remplazo a los interceptores.
</para>
<para>
Esencialmente todos los m&#x00e9;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&#x00f3;n XML o el paquete <literal>org.hibernate.event</literal>
para la lista completa de tipos de evento definidos). Cuando se hace una petici&#x00f3;n de uno de estos
m&#x00e9;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&#x00e1;brica, estos oyentes implementan
el mismo procesamiento en los que siempre resultan aquellos m&#x00e9;todos. Sin embargo, eres libre de
implementar una personalizaci&#x00f3;n de una de las interfaces oyentes (es decir, el
<literal>LoadEvent</literal> es procesado por la implementaci&#x00f3;n registrada de la interface
<literal>LoadEventListener</literal>), en cuyo caso su implementaci&#x00f3;n ser&#x00ed;a responsable
de procesar cualquier petici&#x00f3;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&#x00fa;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&#x00e1;brica al ser &#x00e9;stos declarados non-final para este prop&#x00f3;sito). Los
oyentes personalizados pueden ser registrados program&#x00e1;ticamente a trav&#x00e9;s del objeto
<literal>Configuration</literal>, o especificados en el XML de configuraci&#x00f3;n de Hibernate
(la declaraci&#x00f3;n declarativa a trav&#x00e9;s del fichero de propiedades no est&#x00e1; soportada).
He aqu&#x00ed; 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&#x00e1;s una entrada de configuraci&#x00f3;n dici&#x00e9;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&#x00e1;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&#x00fa;ltiples elementos <literal>&lt;listener/&gt;</literal>, cada referencia resultar&#x00e1; 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&#x00f3;n program&#x00e1;tica.
</para>
<para>
&#x00bf;Por qu&#x00e9; implementar una interface y definir el tipo espc&#x00ed;fico durante la configuraci&#x00f3;n?
Bueno, una implementaci&#x00f3;n de oyente podr&#x00ed;a implementar m&#x00fa;ltiples interfaces de oyente
de eventos. Teniendo el tipo definido adicionalmente durante la registraci&#x00f3;n lo hace m&#x00e1;s
f&#x00e1;cil para activar o desactivar oyentes personalizados durante la configuraci&#x00f3;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&#x00f3;n. Ahora, Hibernate3 permite que ciertas acciones sean permitidas v&#x00ed;a JACC, y autorizadas v&#x00ed;a
JAAS. Esta en una funcionalidad opcional constru&#x00ed;da encima de la arquitectura de eventos.
</para>
<para>
Primero, debes configurar los oyentes de eventos apropiados, para habilitar el uso de
autorizaci&#x00f3;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&#x00fa;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>