Entity listeners (BeforeUpdate, BeforeDelete)

This commit is contained in:
Konstantin Krivopustov 2008-12-17 10:38:59 +00:00
parent 07e5a3068c
commit 1722fd029c
15 changed files with 556 additions and 2 deletions

View File

@ -10,12 +10,20 @@
package com.haulmont.cuba.core;
import com.haulmont.cuba.core.impl.ManagedPersistenceProvider;
import com.haulmont.cuba.core.entity.BaseEntity;
import javax.persistence.Entity;
import java.lang.annotation.Annotation;
import java.util.BitSet;
import java.util.List;
import java.util.ArrayList;
import org.jboss.remoting.samples.chat.exceptions.InvalidArgumentException;
import org.apache.commons.lang.StringUtils;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.enhance.StateManager;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.FieldMetaData;
public abstract class PersistenceProvider
{
@ -69,6 +77,22 @@ public abstract class PersistenceProvider
return entityClass.getSimpleName();
}
public static String[] getDirtyFields(BaseEntity entity) {
if (!(entity instanceof PersistenceCapable))
return new String[0];
List<String> list = new ArrayList<String>();
OpenJPAStateManager stateManager = (OpenJPAStateManager) ((PersistenceCapable) entity).pcGetStateManager();
BitSet dirtySet = stateManager.getDirty();
for (int i = 0; i < dirtySet.size()-1; i++) {
if (dirtySet.get(i)) {
FieldMetaData field = stateManager.getMetaData().getField(i);
list.add(field.getName());
}
}
return list.toArray(new String[list.size()]);
}
protected abstract EntityManagerFactoryAdapter __getEntityManagerFactory();
protected abstract EntityManagerAdapter __getEntityManager();

View File

@ -20,6 +20,8 @@ public interface QueryAdapter
Object getSingleResult();
int executeUpdate();
QueryAdapter setMaxResults(int maxResult);
QueryAdapter setFirstResult(int startPosition);

View File

@ -0,0 +1,23 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 17.12.2008 11:00:54
*
* $Id$
*/
package com.haulmont.cuba.core.entity.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Listeners
{
String[] value() default {};
}

View File

@ -13,6 +13,7 @@ package com.haulmont.cuba.core.impl;
import com.haulmont.cuba.core.QueryAdapter;
import javax.persistence.TemporalType;
import javax.persistence.FlushModeType;
import java.util.List;
import java.util.Date;
@ -24,6 +25,7 @@ public class QueryAdapterImpl implements QueryAdapter
public QueryAdapterImpl(OpenJPAQuery query) {
this.query = query;
this.query.setFlushMode(FlushModeType.COMMIT);
}
public List getResultList() {
@ -34,6 +36,10 @@ public class QueryAdapterImpl implements QueryAdapter
return query.getSingleResult();
}
public int executeUpdate() {
return query.executeUpdate();
}
public QueryAdapter setMaxResults(int maxResult) {
query.setMaxResults(maxResult);
return this;

View File

@ -0,0 +1,161 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 16.12.2008 19:33:33
*
* $Id$
*/
package com.haulmont.cuba.core.impl.listener;
import com.haulmont.cuba.core.entity.BaseEntity;
import com.haulmont.cuba.core.entity.annotation.Listeners;
import com.haulmont.cuba.core.listener.BeforeUpdateEntityListener;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
import com.haulmont.cuba.core.PersistenceProvider;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class EntityListenerManager
{
private static class Key
{
private final Class entityClass;
private final EntityListenerType type;
public Key(Class entityClass, EntityListenerType type) {
this.entityClass = entityClass;
this.type = type;
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!entityClass.equals(key.entityClass)) return false;
if (type != key.type) return false;
return true;
}
public int hashCode() {
int result;
result = entityClass.hashCode();
result = 31 * result + type.hashCode();
return result;
}
}
private static EntityListenerManager instance;
private Log log = LogFactory.getLog(EntityListenerManager.class);
private Map<Key, Object> cache = new ConcurrentHashMap<Key, Object>();
private Object nullListener = new Object();
public static EntityListenerManager getInstance() {
if (instance == null) {
instance = new EntityListenerManager();
}
return instance;
}
public void fireListener(BaseEntity entity, EntityListenerType type) {
Object entityListener = getListener(entity.getClass(), type);
if (entityListener != null) {
switch (type) {
case BEFORE_UPDATE: {
logExecution(type, entity);
((BeforeUpdateEntityListener) entityListener).onBeforeUpdate(entity);
break;
}
case BEFORE_DELETE: {
logExecution(type, entity);
((BeforeDeleteEntityListener) entityListener).onBeforeDelete(entity);
break;
}
default:
throw new UnsupportedOperationException("Unsupported EntityListenerType: " + type);
}
}
}
private void logExecution(EntityListenerType type, BaseEntity entity) {
if (log.isDebugEnabled()) {
StringBuilder sb = new StringBuilder();
sb.append("Executing ").append(type).append(" entity listener for ")
.append(entity.getClass().getName()).append(" id=").append(entity.getId());
String[] strings = PersistenceProvider.getDirtyFields(entity);
if (strings.length > 0) {
sb.append(", changedProperties: ");
for (int i = 0; i < strings.length; i++) {
String s = strings[i];
sb.append(s);
if (i < strings.length - 1)
sb.append(",");
}
}
log.debug(sb.toString());
}
}
private Object getListener(Class<? extends BaseEntity> entityClass, EntityListenerType type) {
Key key = new Key(entityClass, type);
if (!cache.containsKey(key)) {
Object listener = findListener(entityClass, type);
cache.put(key, listener != null ? listener : nullListener);
return listener;
}
else {
Object listener = cache.get(key);
return listener != nullListener ? listener : null;
}
}
private Object findListener(Class<? extends BaseEntity> entityClass, EntityListenerType type) {
log.trace("get listener " + type + " for class " + entityClass.getName());
String[] classNames = getDeclaredListeners(entityClass);
if (classNames == null) {
log.trace("no annotations, exiting");
return null;
}
for (String className : classNames) {
try {
Class aClass = Thread.currentThread().getContextClassLoader().loadClass(className);
log.trace("listener class found: " + aClass);
Class[] interfaces = aClass.getInterfaces();
for (Class intf : interfaces) {
if (intf.equals(type.getListenerInterface())) {
log.trace("listener implements " + type.getListenerInterface());
return aClass.newInstance();
}
}
} catch (ClassNotFoundException e) {
throw new RuntimeException("Unable to find an Entity Listener class", e);
} catch (IllegalAccessException e) {
throw new RuntimeException("Unable to instantiate an Entity Listener", e);
} catch (InstantiationException e) {
throw new RuntimeException("Unable to instantiate an Entity Listener", e);
}
}
log.trace("no implementors of interface " + type.getListenerInterface() + " found");
return null;
}
private String[] getDeclaredListeners(Class<? extends BaseEntity> entityClass) {
Listeners annotation = entityClass.getAnnotation(Listeners.class);
return annotation == null ? null : annotation.value();
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 16.12.2008 19:32:37
*
* $Id$
*/
package com.haulmont.cuba.core.impl.listener;
import com.haulmont.cuba.core.listener.BeforeUpdateEntityListener;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
public enum EntityListenerType
{
BEFORE_UPDATE(BeforeUpdateEntityListener.class),
BEFORE_DELETE(BeforeDeleteEntityListener.class);
private final Class listenerInterface;
private EntityListenerType(Class listenerInterface) {
this.listenerInterface = listenerInterface;
}
public Class getListenerInterface() {
return listenerInterface;
}
}

View File

@ -11,14 +11,19 @@
package com.haulmont.cuba.core.impl.persistence;
import com.haulmont.cuba.core.SecurityProvider;
import com.haulmont.cuba.core.global.TimeProvider;
import com.haulmont.cuba.core.PersistenceProvider;
import com.haulmont.cuba.core.impl.listener.EntityListenerManager;
import com.haulmont.cuba.core.impl.listener.EntityListenerType;
import com.haulmont.cuba.core.entity.BaseEntity;
import com.haulmont.cuba.core.entity.Updatable;
import com.haulmont.cuba.core.entity.DeleteDeferred;
import com.haulmont.cuba.core.global.TimeProvider;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.event.AbstractLifecycleListener;
import org.apache.openjpa.event.LifecycleEvent;
import java.util.Date;
import java.util.Arrays;
public class EntityLifecycleListener extends AbstractLifecycleListener
{
@ -30,8 +35,34 @@ public class EntityLifecycleListener extends AbstractLifecycleListener
public void beforeStore(LifecycleEvent event) {
PersistenceCapable pc = (PersistenceCapable) event.getSource();
if (!pc.pcIsNew() && (event.getSource() instanceof Updatable)) {
if (!pc.pcIsNew() && (pc instanceof Updatable)) {
__beforeUpdate((Updatable) event.getSource());
if ((pc instanceof DeleteDeferred) && justDeleted((DeleteDeferred) pc)) {
EntityListenerManager.getInstance().fireListener(
((BaseEntity) event.getSource()), EntityListenerType.BEFORE_DELETE);
}
else {
EntityListenerManager.getInstance().fireListener(
((BaseEntity) event.getSource()), EntityListenerType.BEFORE_UPDATE);
}
}
}
private boolean justDeleted(DeleteDeferred dd) {
if (!dd.isDeleted()) {
return false;
}
else {
String[] fields = PersistenceProvider.getDirtyFields((BaseEntity) dd);
Arrays.sort(fields);
return Arrays.binarySearch(fields, "deleteTs") >= 0;
}
}
public void afterStore(LifecycleEvent event) {
PersistenceCapable pc = (PersistenceCapable) event.getSource();
if (!pc.pcIsNew() && (event.getSource() instanceof Updatable)) {
// System.out.println("afterStore: " + pc);
}
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 17.12.2008 11:31:43
*
* $Id$
*/
package com.haulmont.cuba.core.listener;
import com.haulmont.cuba.core.entity.BaseEntity;
/**
* Defines the contract for handling of entities before they have been marked as deleted in DB.<br>
*/
public interface BeforeDeleteEntityListener<T extends BaseEntity>
{
/**
* Executes before the object has been marked as deleted in DB.<br>
* @param entity deleted entity
*/
void onBeforeDelete(T entity);
}

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 16.12.2008 19:01:40
*
* $Id$
*/
package com.haulmont.cuba.core.listener;
import com.haulmont.cuba.core.entity.BaseEntity;
/**
* Defines the contract for handling of entities before they have been updated in DB.<br>
*/
public interface BeforeUpdateEntityListener<T extends BaseEntity>
{
/**
* Executes before the object has been updated in DB.<br>
* @param entity updated entity
* @param changedProperties names of properties changed since last load.<br>
* If the entity was detached, then changedProperties contains all properties of the entity.<br>
* The array is sorted, so Arrays.binarySearch() can be applied
*/
void onBeforeUpdate(T entity);
}

View File

@ -11,6 +11,7 @@
package com.haulmont.cuba.security.entity;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.cuba.core.entity.annotation.Listeners;
import javax.persistence.*;
import java.util.Set;
@ -18,6 +19,7 @@ import java.io.Serializable;
@Entity(name = "sec$Profile")
@Table(name = "SEC_PROFILE")
@Listeners({"com.haulmont.cuba.security.listener.ProfileEntityListener"})
public class Profile extends StandardEntity
{
private static final long serialVersionUID = -9008053062363137148L;

View File

@ -11,6 +11,7 @@
package com.haulmont.cuba.security.entity;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.cuba.core.entity.annotation.Listeners;
import javax.persistence.Entity;
import javax.persistence.Table;
@ -18,6 +19,7 @@ import javax.persistence.Column;
@Entity(name = "sec$Role")
@Table(name = "SEC_ROLE")
@Listeners({"com.haulmont.cuba.security.listener.RoleEntityListener"})
public class Role extends StandardEntity
{
private static final long serialVersionUID = -4889116218059626402L;

View File

@ -0,0 +1,28 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 17.12.2008 12:04:22
*
* $Id$
*/
package com.haulmont.cuba.security.listener;
import com.haulmont.cuba.core.EntityManagerAdapter;
import com.haulmont.cuba.core.PersistenceProvider;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
import com.haulmont.cuba.security.entity.Profile;
import com.haulmont.cuba.security.entity.ProfileRole;
public class ProfileEntityListener implements BeforeDeleteEntityListener<Profile>
{
public void onBeforeDelete(Profile profile) {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
for (ProfileRole profileRole : profile.getProfileRoles()) {
em.remove(profileRole);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 17.12.2008 13:07:13
*
* $Id$
*/
package com.haulmont.cuba.security.listener;
import com.haulmont.cuba.core.EntityManagerAdapter;
import com.haulmont.cuba.core.PersistenceProvider;
import com.haulmont.cuba.core.QueryAdapter;
import com.haulmont.cuba.core.listener.BeforeDeleteEntityListener;
import com.haulmont.cuba.security.entity.ProfileRole;
import com.haulmont.cuba.security.entity.Role;
import java.util.List;
public class RoleEntityListener implements BeforeDeleteEntityListener<Role>
{
public void onBeforeDelete(Role entity) {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
QueryAdapter query = em.createQuery("select pr from sec$ProfileRole pr where pr.role = ?1");
query.setParameter(1, entity);
List<ProfileRole> list = query.getResultList();
for (ProfileRole profileRole : list) {
em.remove(profileRole);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 16.12.2008 17:03:51
*
* $Id$
*/
package com.haulmont.cuba.core;
import com.haulmont.cuba.core.entity.Server;
import java.util.UUID;
public class EntityListenerTest extends CubaTestCase
{
public void test() {
UUID id;
TransactionAdapter tx = Locator.createTransaction();
try {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
assertNotNull(em);
Server server = new Server();
id = server.getId();
server.setName("localhost");
server.setAddress("127.0.0.1");
server.setRunning(true);
em.persist(server);
Server server1 = new Server();
server1.setName("localhost");
server1.setAddress("127.0.0.1");
server1.setRunning(true);
em.persist(server1);
tx.commitRetaining();
em = PersistenceProvider.getEntityManager();
server = em.find(Server.class, id);
server.setAddress("192.168.1.1");
tx.commitRetaining();
} catch (Exception e) {
tx.end();
}
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright (c) 2008 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
* Author: Konstantin Krivopustov
* Created: 17.12.2008 11:53:01
*
* $Id$
*/
package com.haulmont.cuba.security;
import com.haulmont.cuba.core.*;
import com.haulmont.cuba.security.entity.Profile;
import com.haulmont.cuba.security.entity.User;
import com.haulmont.cuba.security.entity.Role;
import com.haulmont.cuba.security.entity.ProfileRole;
import java.util.UUID;
public class RelationsTest extends CubaTestCase
{
public void testProfile() {
UUID profileId = createProfile();
TransactionAdapter tx = Locator.createTransaction();
try {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
Profile profile = em.find(Profile.class, profileId);
em.remove(profile);
tx.commit();
} finally {
tx.end();
}
}
public void testRole() {
UUID roleId = createRole();
TransactionAdapter tx = Locator.createTransaction();
try {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
Role role = em.find(Role.class, roleId);
em.remove(role);
tx.commit();
} finally {
tx.end();
}
}
public UUID createProfile() {
TransactionAdapter tx = Locator.createTransaction();
try {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
User user = em.find(User.class, UUID.fromString("60885987-1b61-4247-94c7-dff348347f93"));
Role role = em.find(Role.class, UUID.fromString("0c018061-b26f-4de2-a5be-dff348347f93"));
Profile profile = new Profile();
profile.setUser(user);
profile.setName("RelationTest");
em.persist(profile);
ProfileRole profileRole = new ProfileRole();
profileRole.setProfile(profile);
profileRole.setRole(role);
em.persist(profileRole);
tx.commit();
return profile.getId();
} finally {
tx.end();
}
}
public UUID createRole() {
TransactionAdapter tx = Locator.createTransaction();
try {
EntityManagerAdapter em = PersistenceProvider.getEntityManager();
Profile profile = em.find(Profile.class, UUID.fromString("bf83541f-f610-46f4-a268-dff348347f93"));
Role role = new Role();
role.setName("RelationTest");
em.persist(role);
ProfileRole profileRole = new ProfileRole();
profileRole.setProfile(profile);
profileRole.setRole(role);
em.persist(profileRole);
tx.commit();
return role.getId();
} finally {
tx.end();
}
}
}