PL-9638 BeforeDetachEntityListener is not fired if an unfetched attribute was accessed in AfterCompleteTransactionListener

This commit is contained in:
Konstantin Krivopustov 2017-09-19 12:49:42 +04:00
parent 60ff3f4845
commit ed2d479cd6
3 changed files with 219 additions and 6 deletions

View File

@ -151,13 +151,13 @@ public class PersistenceImplSupport implements ApplicationContextAware {
protected ContainerResourceHolder getInstanceContainerResourceHolder(String storeName) {
ContainerResourceHolder holder =
(ContainerResourceHolder) TransactionSynchronizationManager.getResource(RESOURCE_HOLDER_KEY);
if (holder != null)
return holder;
if (holder == null) {
holder = new ContainerResourceHolder(storeName);
TransactionSynchronizationManager.bindResource(RESOURCE_HOLDER_KEY, holder);
}
holder = new ContainerResourceHolder(storeName);
TransactionSynchronizationManager.bindResource(RESOURCE_HOLDER_KEY, holder);
holder.setSynchronizedWithTransaction(true);
if (TransactionSynchronizationManager.isSynchronizationActive()) {
if (TransactionSynchronizationManager.isSynchronizationActive() && !holder.isSynchronizedWithTransaction()) {
holder.setSynchronizedWithTransaction(true);
TransactionSynchronizationManager.registerSynchronization(
new ContainerResourceSynchronization(holder, RESOURCE_HOLDER_KEY));
}
@ -364,6 +364,9 @@ public class PersistenceImplSupport implements ApplicationContextAware {
BaseEntityInternalAccess.setManaged(baseGenericIdEntity, false);
BaseEntityInternalAccess.setDetached(baseGenericIdEntity, true);
}
if (instance instanceof FetchGroupTracker) {
((FetchGroupTracker) instance)._persistence_setSession(null);
}
}
for (AfterCompleteTransactionListener listener : afterCompleteTxListeners) {

View File

@ -48,6 +48,15 @@ public class TestAfterCompleteTxListener implements AfterCompleteTransactionList
case "testRollback":
testRollback(committed, detachedEntities);
break;
case "accessName":
testAccessName(committed, detachedEntities);
break;
case "accessGroup":
testAccessGroup(committed, detachedEntities);
break;
case "accessUserRoles":
testAccessUserRoles(committed, detachedEntities);
break;
}
}
}
@ -92,4 +101,40 @@ public class TestAfterCompleteTxListener implements AfterCompleteTransactionList
}
}
}
private void testAccessName(boolean committed, Collection<Entity> detachedEntities) {
for (Entity entity : detachedEntities) {
if (entity instanceof User) {
try {
System.out.println("User name: " + ((User) entity).getName());
} catch (IllegalStateException e) {
System.out.println("An exception has been thrown: " + e);
}
}
}
}
private void testAccessGroup(boolean committed, Collection<Entity> detachedEntities) {
for (Entity entity : detachedEntities) {
if (entity instanceof User) {
try {
System.out.println("User group: " + ((User) entity).getGroup());
} catch (IllegalStateException e) {
System.out.println("An exception has been thrown: " + e);
}
}
}
}
private void testAccessUserRoles(boolean committed, Collection<Entity> detachedEntities) {
for (Entity entity : detachedEntities) {
if (entity instanceof User) {
try {
System.out.println("User roles size: " + ((User) entity).getUserRoles().size());
} catch (IllegalStateException e) {
System.out.println("An exception has been thrown: " + e);
}
}
}
}
}

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package spec.tx_listeners
import com.haulmont.cuba.core.global.AppBeans
import com.haulmont.cuba.core.global.EntityStates
import com.haulmont.cuba.core.global.View
import com.haulmont.cuba.security.entity.Group
import com.haulmont.cuba.security.entity.Role
import com.haulmont.cuba.security.entity.User
import com.haulmont.cuba.security.entity.UserRole
import com.haulmont.cuba.testsupport.TestContainer
import com.haulmont.cuba.testsupport.TestSupport
import com.haulmont.cuba.tx_listener.TestAfterCompleteTxListener
import org.junit.ClassRule
import spock.lang.Shared
import spock.lang.Specification
class AfterCompleteTransactionListenerTest extends Specification {
@Shared @ClassRule
public TestContainer cont = TestContainer.Common.INSTANCE
def "reference CAN be fetched in afterComplete if entity is not partial"() {
def entityStates = AppBeans.get(EntityStates)
TestAfterCompleteTxListener.test = 'accessGroup'
when:
def user = cont.persistence().callInTransaction { em ->
em.find(User, TestSupport.ADMIN_USER_ID)
}
then:
entityStates.isLoaded(user, 'login')
entityStates.isLoaded(user, 'name')
entityStates.isLoaded(user, 'group')
user.group != null
cleanup:
TestAfterCompleteTxListener.test = null
}
def "collection CAN be fetched in afterComplete if entity is not partial"() {
def entityStates = AppBeans.get(EntityStates)
TestAfterCompleteTxListener.test = 'accessUserRoles'
when:
def user = cont.persistence().callInTransaction { em ->
em.find(User, TestSupport.ADMIN_USER_ID)
}
then:
entityStates.isLoaded(user, 'login')
entityStates.isLoaded(user, 'name')
entityStates.isLoaded(user, 'userRoles')
user.userRoles.size() == 1
cleanup:
TestAfterCompleteTxListener.test = null
}
def "local attribute CANNOT be fetched in afterComplete if entity is partial"() {
def entityStates = AppBeans.get(EntityStates)
TestAfterCompleteTxListener.test = 'accessName'
def view = new View(User)
.addProperty('login')
.addProperty('group', new View(Group).addProperty('name'))
view.setLoadPartialEntities(true)
when:
def user = cont.persistence().callInTransaction { em ->
em.find(User, TestSupport.ADMIN_USER_ID, view)
}
then:
entityStates.isLoaded(user, 'login')
entityStates.isLoaded(user, 'group')
entityStates.isLoaded(user.group, 'name')
!entityStates.isLoaded(user, 'name')
cleanup:
TestAfterCompleteTxListener.test = null
}
def "reference CANNOT be fetched in afterComplete if entity is partial"() {
def entityStates = AppBeans.get(EntityStates)
TestAfterCompleteTxListener.test = 'accessGroup'
def view = new View(User)
.addProperty('login')
.addProperty('userRoles', new View(UserRole)
.addProperty('role', new View(Role)
.addProperty('name')))
view.setLoadPartialEntities(true)
when:
def user = cont.persistence().callInTransaction { em ->
em.find(User, TestSupport.ADMIN_USER_ID, view)
}
then:
entityStates.isLoaded(user, 'login')
entityStates.isLoaded(user, 'userRoles')
!entityStates.isLoaded(user, 'group')
cleanup:
TestAfterCompleteTxListener.test = null
}
def "collection CANNOT be fetched in afterComplete if entity is partial"() {
def entityStates = AppBeans.get(EntityStates)
TestAfterCompleteTxListener.test = 'accessUserRoles'
def view = new View(User)
.addProperty('login')
view.setLoadPartialEntities(true)
when:
def user = cont.persistence().callInTransaction { em ->
em.find(User, TestSupport.ADMIN_USER_ID, view)
}
then:
entityStates.isLoaded(user, 'login')
!entityStates.isLoaded(user, 'userRoles')
cleanup:
TestAfterCompleteTxListener.test = null
}
}