diff --git a/modules/core/src/com/haulmont/cuba/security/app/EntityLog.java b/modules/core/src/com/haulmont/cuba/security/app/EntityLog.java index 675a796f2e..2fb7c0cc84 100644 --- a/modules/core/src/com/haulmont/cuba/security/app/EntityLog.java +++ b/modules/core/src/com/haulmont/cuba/security/app/EntityLog.java @@ -38,6 +38,8 @@ import org.eclipse.persistence.internal.sessions.ObjectChangeSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -112,7 +114,9 @@ public class EntityLog implements EntityLogAPI { for (EntityLogItem item : items) { List sameEntityList = items.stream() - .filter(entityLogItem -> entityLogItem.getObjectEntityId().equals(item.getObjectEntityId())) + .filter(entityLogItem -> entityLogItem.getDbGeneratedIdEntity() != null ? + entityLogItem.getDbGeneratedIdEntity().equals(item.getDbGeneratedIdEntity()) : + entityLogItem.getObjectEntityId().equals(item.getObjectEntityId())) .collect(Collectors.toList()); EntityLogItem itemToSave = sameEntityList.get(0); computeChanges(itemToSave, sameEntityList); @@ -192,16 +196,31 @@ public class EntityLog implements EntityLogAPI { protected void saveItem(EntityLogItem item) { String storeName = metadataTools.getStoreName(metadata.getClassNN(item.getEntity())); - if (Stores.isMain(storeName)) { - EntityManager em = persistence.getEntityManager(); - em.persist(item); - } else { - // Create a new transaction in main DB if we are saving an entity from additional data store - try (Transaction tx = persistence.createTransaction()) { + if (item.getDbGeneratedIdEntity() == null) { + if (Stores.isMain(storeName)) { EntityManager em = persistence.getEntityManager(); em.persist(item); - tx.commit(); + } else { + // Create a new transaction in main DB if we are saving an entity from additional data store + try (Transaction tx = persistence.createTransaction()) { + EntityManager em = persistence.getEntityManager(); + em.persist(item); + tx.commit(); + } } + } else { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { + @Override + public void afterCommit() { + Number id = item.getDbGeneratedIdEntity().getId().getNN(); + item.setObjectEntityId(id); + try (Transaction tx = persistence.createTransaction()) { + EntityManager em = persistence.getEntityManager(); + em.persist(item); + tx.commit(); + } + } + }); } } @@ -387,7 +406,11 @@ public class EntityLog implements EntityLogAPI { item.setUser(findUser(em)); item.setType(EntityLogItem.Type.CREATE); item.setEntity(entityName); - item.setObjectEntityId(referenceToEntitySupport.getReferenceId(entity)); + if (entity instanceof BaseDbGeneratedIdEntity) { + item.setDbGeneratedIdEntity((BaseDbGeneratedIdEntity) entity); + } else { + item.setObjectEntityId(referenceToEntitySupport.getReferenceId(entity)); + } item.setAttributes(createLogAttributes(entity, attributes, null)); enqueueItem(item); diff --git a/modules/core/test/spec/cuba/core/entity_log/EntityLogTest.groovy b/modules/core/test/spec/cuba/core/entity_log/EntityLogTest.groovy index f1b1ba5584..ca77e1218b 100644 --- a/modules/core/test/spec/cuba/core/entity_log/EntityLogTest.groovy +++ b/modules/core/test/spec/cuba/core/entity_log/EntityLogTest.groovy @@ -29,6 +29,8 @@ import com.haulmont.cuba.security.entity.Group import com.haulmont.cuba.security.entity.LoggedAttribute import com.haulmont.cuba.security.entity.LoggedEntity import com.haulmont.cuba.security.entity.User +import com.haulmont.cuba.testmodel.primary_keys.IdentityEntity +import com.haulmont.cuba.testmodel.primary_keys.IntIdentityEntity import com.haulmont.cuba.testsupport.TestContainer import com.haulmont.cuba.testsupport.TestSupport import org.junit.ClassRule @@ -79,6 +81,26 @@ class EntityLogTest extends Specification { la.setEntity(le) la.setName('type') em.persist(la) + + le = new LoggedEntity() + le.setName('test$IntIdentityEntity') + le.setAuto(true) + em.persist(le) + + la = new LoggedAttribute() + la.setEntity(le) + la.setName('name') + em.persist(la) + + le = new LoggedEntity() + le.setName('test$IdentityEntity') + le.setAuto(true) + em.persist(le) + + la = new LoggedAttribute() + la.setEntity(le) + la.setName('name') + em.persist(la) } entityLog = AppBeans.get(EntityLogAPI.class) entityLog.invalidateCache() @@ -102,16 +124,21 @@ class EntityLogTest extends Specification { runner.update("delete from SEC_LOGGED_ENTITY") } - private List getEntityLogItems(def userId) { + private List getEntityLogItems(String entityName, def entityId) { Transaction tx List items tx = cont.persistence().createTransaction() try { EntityManager em = cont.persistence().getEntityManager() + String entityIdField + if (entityId instanceof Integer) entityIdField = 'intEntityId' + else if (entityId instanceof Long) entityIdField = 'longEntityId' + else entityIdField = 'entityId' + TypedQuery query = em.createQuery( - 'select i from sec$EntityLog i where i.entity = ?1 and i.entityRef.entityId = ?2 order by i.eventTs desc', EntityLogItem.class) - query.setParameter(1, 'sec$User') - query.setParameter(2, userId) + "select i from sec\$EntityLog i where i.entity = ?1 and i.entityRef.$entityIdField = ?2 order by i.eventTs desc", EntityLogItem.class) + query.setParameter(1, entityName) + query.setParameter(2, entityId) items = query.getResultList() tx.commit() @@ -147,8 +174,8 @@ class EntityLogTest extends Specification { then: - getEntityLogItems(user1Id).size() == 1 - getEntityLogItems(user2Id).size() == 1 + getEntityLogItems('sec$User', user1Id).size() == 1 + getEntityLogItems('sec$User', user2Id).size() == 1 when: @@ -164,8 +191,8 @@ class EntityLogTest extends Specification { then: - getEntityLogItems(user1Id).size() == 2 - getEntityLogItems(user2Id).size() == 2 + getEntityLogItems('sec$User', user1Id).size() == 2 + getEntityLogItems('sec$User', user2Id).size() == 2 } def "correct old value in case of flush in the middle"() { @@ -198,8 +225,8 @@ class EntityLogTest extends Specification { then: - getEntityLogItems(user1Id).size() == 2 - def item = getEntityLogItems(user1Id)[0] // latest + getEntityLogItems('sec$User', user1Id).size() == 2 + def item = getEntityLogItems('sec$User', user1Id)[0] // latest item.attributes.find({ it.name == 'email' }).value == 'email111' item.attributes.find({ it.name == 'email' }).oldValue == 'email1' @@ -207,4 +234,80 @@ class EntityLogTest extends Specification { item.attributes.find({ it.name == 'name' }).value == 'name11' item.attributes.find({ it.name == 'name' }).oldValue == 'name1' } + + def "works for BaseIdentityIdEntity"() { + + when: + + IdentityEntity entity = cont.persistence().callInTransaction { em -> + def e = new IdentityEntity(name: 'test1') + em.persist(e) + e + } + + then: + + noExceptionThrown() + + def item1 = getEntityLogItems('test$IdentityEntity', entity.id.get())[0] + item1.attributes.find({ it.name == 'name' }).value == 'test1' + item1.attributes.find({ it.name == 'name' }).oldValue == null + + when: + + cont.persistence().runInTransaction { em -> + def e = em.find(IdentityEntity, entity.id) + e.name = 'test2' + } + + then: + + def item2 = getEntityLogItems('test$IdentityEntity', entity.id.get())[0] + item2.attributes.find({ it.name == 'name' }).value == 'test2' + item2.attributes.find({ it.name == 'name' }).oldValue == 'test1' + + cleanup: + + if (entity != null && entity.getId().get() != null) { + new QueryRunner(cont.persistence().dataSource).update("delete from TEST_IDENTITY where id = ${entity.getId().get()}") + } + } + + def "works for BaseIntIdentityIdEntity"() { + + when: + + IntIdentityEntity entity = cont.persistence().callInTransaction { em -> + def e = new IntIdentityEntity(name: 'test1') + em.persist(e) + e + } + + then: + + noExceptionThrown() + + def item1 = getEntityLogItems('test$IntIdentityEntity', entity.id.get())[0] + item1.attributes.find({ it.name == 'name' }).value == 'test1' + item1.attributes.find({ it.name == 'name' }).oldValue == null + + when: + + cont.persistence().runInTransaction { em -> + def e = em.find(IntIdentityEntity, entity.id) + e.name = 'test2' + } + + then: + + def item2 = getEntityLogItems('test$IntIdentityEntity', entity.id.get())[0] + item2.attributes.find({ it.name == 'name' }).value == 'test2' + item2.attributes.find({ it.name == 'name' }).oldValue == 'test1' + + cleanup: + + if (entity != null && entity.getId().get() != null) { + new QueryRunner(cont.persistence().dataSource).update("delete from TEST_INT_IDENTITY where id = ${entity.getId().get()}") + } + } } diff --git a/modules/global/src/com/haulmont/cuba/security/entity/EntityLogItem.java b/modules/global/src/com/haulmont/cuba/security/entity/EntityLogItem.java index bf6bbae3aa..b5576c62c5 100644 --- a/modules/global/src/com/haulmont/cuba/security/entity/EntityLogItem.java +++ b/modules/global/src/com/haulmont/cuba/security/entity/EntityLogItem.java @@ -18,6 +18,7 @@ package com.haulmont.cuba.security.entity; import com.haulmont.chile.core.annotations.MetaProperty; import com.haulmont.chile.core.datatypes.impl.EnumClass; +import com.haulmont.cuba.core.entity.BaseDbGeneratedIdEntity; import com.haulmont.cuba.core.entity.BaseUuidEntity; import com.haulmont.cuba.core.entity.Creatable; import com.haulmont.cuba.core.entity.ReferenceToEntity; @@ -31,7 +32,6 @@ import javax.annotation.PostConstruct; import javax.persistence.*; import java.util.Date; import java.util.Set; -import java.util.UUID; /** * Record containing information about entity lifecycle event. @@ -99,6 +99,9 @@ public class EntityLogItem extends BaseUuidEntity implements Creatable { @EmbeddedParameters(nullAllowed = false) private ReferenceToEntity entityRef; + @Transient + private transient BaseDbGeneratedIdEntity dbGeneratedIdEntity; + @Transient @MetaProperty private Set attributes; @@ -188,6 +191,14 @@ public class EntityLogItem extends BaseUuidEntity implements Creatable { this.entityRef = entityRef; } + public BaseDbGeneratedIdEntity getDbGeneratedIdEntity() { + return dbGeneratedIdEntity; + } + + public void setDbGeneratedIdEntity(BaseDbGeneratedIdEntity dbGeneratedIdEntity) { + this.dbGeneratedIdEntity = dbGeneratedIdEntity; + } + public void setObjectEntityId(Object entity) { if (entityRef == null) { entityRef = AppBeans.get(Metadata.class).create(ReferenceToEntity.class);