Глава 14. Перехватчики и события

Оглавление

Содержание

  1. 14.1. Перехватчики
  2. 14.2. Система событий
  3. 14.3. Декларативная безопасность Hibernate

Для приложения полезно реагировать на определённые события, которые происходят внутри Hibernate. Это позволяет реализовать рядовую функциональность и расширение функциональности Hibernate.

14.1. Перехватчики

Интерфейс Interceptor обеспечивает обратные вызовы от сессии к приложению, позволяя приложению проверять и/или управлять свойствами постоянного объекта до его сохранения, обновления, удаления или загрузки. Одним из возможных способов использования этого является отслеживание информации аудита. Например, следующий Interceptor автоматически устанавливает createTimestamp при создании Auditable и обновляет свойство lastUpdateTimestamp при обновлении Auditable.

Вы можете либо реализовать Interceptor напрямую, либо расширить EmptyInterceptor.

package org.hibernate.test;
import java.io.Serializable; import java.util.Date; import java.util.Iterator;
import org.hibernate.EmptyInterceptor; import org.hibernate.Transaction; import org.hibernate.type.Type;
public class AuditInterceptor extends EmptyInterceptor {
private int updates; private int creates; private int loads;
public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // делаем что-нибудь }
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) { if ( entity instanceof Auditable ) { loads++; } 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 afterTransactionCompletion(Transaction tx) { if ( tx.wasCommitted() ) { System.out.println("Creations: " + creates + ", Updates: " + updates + "Loads: " + loads); } updates=0; creates=0; loads=0; } }

Существует два типа inteceptors: области видимости Session и области видимости SessionFactory.

Перехватчик с Session областью видимости указывается при открытии сессии.

Session session = sf.withOptions( new AuditInterceptor() ).openSession();

Перехватчик с областью видимости SessionFactory регистрируется в объекте Configuration до создания SessionFactory. Пока сессия не открыта, явно указывайте используемый перехватчик, он будет применяться ко всем сессиям, открытым из этого SessionFactory. Перехватчики SessionFactory должны быть потокобезопасными. Убедитесь, что вы не сохраняете состояния, зависящие от сессии, поскольку несколько сессий могут использовать этот перехватчик одновременно.

new Configuration().setInterceptor( new AuditInterceptor() );

14.2. Система событий

Если вам нужно реагировать на определённые события на вашем слое персистентности, вы также можете использовать архитектуру событий Hibernate. Система событий может использоваться дополнительно или в качестве замены перехватчиков.

Многие методы интерфейса Session коррелируют с типом события. Полный диапазон определенных типов событий объявляется как значения enum в org.hibernate.event.spi.EventType. Когда выполняется запрос одного из этих методов, Hibernate Session генерирует соответствующее событие и передает его слушателям событий этого типа. «Из коробки» эти слушатели реализуют ту же обработку, в которой эти методы всегда приводили. Тем не менее, вы можете реализовать настройку одного из интерфейсов слушателя (т. е. LoadEvent обрабатывается зарегистрированной реализацией интерфейса LoadEventListener), и в этом случае их реализация будет отвечать за обработку любых запросов load(), сделанных из Session.

Заметка

Информацию о регистрации пользовательских слушателей событий см. в Руководстве разработчика Hibernate.

Слушатели должны рассматриваться как «безсостоятельные»: они разделяются между запросами и не должны сохранять какое-либо состояние в переменных экземпляра.

Пользовательский слушатель реализует соответствующий интерфейс для события, которое он хочет обработать и/или расширить один из базовых классов (или даже слушателей событий по умолчанию, которые Hibernate предлагает из коробки, поскольку они объявлены без final). Ниже приведен пример пользовательского слушателя событий загрузки:

public class MyLoadListener implements LoadEventListener {
    // это единственный метод, определяемый интерфейсом LoadEventListener
    public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType)
            throws HibernateException {
        if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) {
            throw MySecurityException("Unauthorized access");
        }
    }
}

14.3. Декларативная безопасность Hibernate

Обычно декларативная безопасность в приложениях Hibernate управляется в слое фасада сессии. Hibernate позволяет разрешить определённые действия через JACC и авторизовать через JAAS. Это дополнительная функциональность, построенная поверх архитектуры событий.

Во-первых, вы должны настроить соответствующие слушатели событий, чтобы разрешить использование авторизации JACC. Опять же, см. Руководство разработчика Hibernate для деталей. Ниже приведён пример соответствующей реализации org.hibernate.integrator.spi.Integrator для этой цели.

import org.hibernate.event.service.spi.DuplicationStrategy;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.secure.internal.JACCPreDeleteEventListener;
import org.hibernate.secure.internal.JACCPreInsertEventListener;
import org.hibernate.secure.internal.JACCPreLoadEventListener;
import org.hibernate.secure.internal.JACCPreUpdateEventListener;
import org.hibernate.secure.internal.JACCSecurityListener;
public class JaccEventListenerIntegrator implements Integrator {
private static final DuplicationStrategy JACC_DUPLICATION_STRATEGY = new DuplicationStrategy() { @Override public boolean areMatch(Object listener, Object original) { return listener.getClass().equals( original.getClass() ) && JACCSecurityListener.class.isInstance( original ); }
@Override public Action getAction() { return Action.KEEP_ORIGINAL; } };
@Override @SuppressWarnings( {"unchecked"}) public void integrate( Configuration configuration, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { boolean isSecurityEnabled = configuration.getProperties().containsKey( AvailableSettings.JACC_ENABLED ); if ( !isSecurityEnabled ) { return; }
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService( EventListenerRegistry.class ); eventListenerRegistry.addDuplicationStrategy( JACC_DUPLICATION_STRATEGY );
final String jaccContextId = configuration.getProperty( Environment.JACC_CONTEXTID ); eventListenerRegistry.prependListeners( EventType.PRE_DELETE, new JACCPreDeleteEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_INSERT, new JACCPreInsertEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_UPDATE, new JACCPreUpdateEventListener(jaccContextId) ); eventListenerRegistry.prependListeners( EventType.PRE_LOAD, new JACCPreLoadEventListener(jaccContextId) ); } }

Вы также должны решить, как настроить поставщика JACC. Один из вариантов — указать Hibernate, какие разрешения к каким ролям привязать и настроить его для JACC-провайдера. Это должно быть сделано в файле hibernate.cfg.xml.

<grant role="admin" entity-name="User" actions="insert,update,read"/>
<grant role="su" entity-name="User" actions="*"/>