diff --git a/uPortal-groups/uPortal-groups-core/src/main/java/org/apereo/portal/groups/RDBMEntityGroupStore.java b/uPortal-groups/uPortal-groups-core/src/main/java/org/apereo/portal/groups/RDBMEntityGroupStore.java index 45539202c96..8377fa0dd21 100644 --- a/uPortal-groups/uPortal-groups-core/src/main/java/org/apereo/portal/groups/RDBMEntityGroupStore.java +++ b/uPortal-groups/uPortal-groups-core/src/main/java/org/apereo/portal/groups/RDBMEntityGroupStore.java @@ -21,6 +21,7 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; import net.sf.ehcache.CacheManager; @@ -68,7 +69,8 @@ public class RDBMEntityGroupStore implements IEntityGroupStore, IGroupConstants private static final String MEMBER_IS_ENTITY = "F"; private static final String MEMBER_IS_GROUP = "T"; - private static final String CACHE_NAME = "org.apereo.portal.groups.RDBMEntityGroupStore.search"; + private static final String SEARCH_CACHE_NAME = + "org.apereo.portal.groups.RDBMEntityGroupStore.search"; private static final String PARENT_GROUP_BY_ENTITY_CACHE_NAME = "org.apereo.portal.groups.RDBMEntityGroupStore.parentGroupEntity"; private static final String PARENT_GROUP_BY_ENTTITY_GROUP_CACHE_NAME = @@ -180,7 +182,7 @@ private void initialize() { } // Cache for group search - groupSearchCache = getGroupSearchCache(CACHE_NAME); + groupSearchCache = getGroupSearchCache(SEARCH_CACHE_NAME); parentGroupEntityCache = getGroupSearchCache(PARENT_GROUP_BY_ENTITY_CACHE_NAME); parentGroupEntityGroupCache = getGroupSearchCache(PARENT_GROUP_BY_ENTTITY_GROUP_CACHE_NAME); } @@ -340,20 +342,22 @@ public IEntityGroup find(String groupID) throws GroupsException { * @param ent the entity in question * @return java.util.Iterator */ - public java.util.Iterator findParentGroups(IEntity ent) throws GroupsException { + public java.util.Iterator findParentGroups(IEntity ent) throws GroupsException { // https://github.com/uPortal-Project/uPortal/issues/1903 #2 String memberKey = ent.getKey(); - Element el = parentGroupEntityCache.get(memberKey); - if (el != null) { - java.util.Iterator it = (java.util.Iterator) el.getObjectValue(); - assert it != null; - return it; - } - Integer type = EntityTypesLocator.getEntityTypes().getEntityIDFromType(ent.getLeafType()); - java.util.Iterator it = findParentGroupsForEntity(memberKey, type.intValue()); - parentGroupEntityCache.put(new Element(memberKey, it)); - return it; + String cacheKey = memberKey + ":" + type.intValue(); + List list; + Element el = parentGroupEntityCache.get(cacheKey); + if (el == null) { + list = findParentGroupsForEntity(memberKey, type.intValue()); + list = Collections.unmodifiableList(list); + parentGroupEntityCache.put(new Element(cacheKey, list)); + } else { + list = (List) el.getObjectValue(); + assert list != null; + } + return list.iterator(); } /** @@ -362,21 +366,24 @@ public java.util.Iterator findParentGroups(IEntity ent) throws GroupsException { * @param group IEntityGroup * @return java.util.Iterator */ - public java.util.Iterator findParentGroups(IEntityGroup group) throws GroupsException { + public java.util.Iterator findParentGroups(IEntityGroup group) + throws GroupsException { // https://github.com/uPortal-Project/uPortal/issues/1903 #8 String memberKey = group.getLocalKey(); - Element el = parentGroupEntityGroupCache.get(memberKey); - if (el != null) { - java.util.Iterator it = (java.util.Iterator) el.getObjectValue(); - assert it != null; - return it; - } - String serviceName = group.getServiceName().toString(); Integer type = EntityTypesLocator.getEntityTypes().getEntityIDFromType(group.getLeafType()); - java.util.Iterator it = findParentGroupsForGroup(serviceName, memberKey, type.intValue()); - parentGroupEntityGroupCache.put(new Element(memberKey, it)); - return it; + String cacheKey = memberKey + ":" + type.intValue() + ":" + serviceName; + Element el = parentGroupEntityGroupCache.get(cacheKey); + List list; + if (el == null) { + list = findParentGroupsForGroup(serviceName, memberKey, type.intValue()); + list = Collections.unmodifiableList(list); + parentGroupEntityGroupCache.put(new Element(cacheKey, list)); + } else { + list = (List) el.getObjectValue(); + assert list != null; + } + return list.iterator(); } /** @@ -401,12 +408,12 @@ public Iterator findParentGroups(IGroupMember gm) throws GroupsException { * * @param memberKey * @param type - * @return java.util.Iterator + * @return list of groups (IEntityGroup) */ - private java.util.Iterator findParentGroupsForEntity(String memberKey, int type) + private List findParentGroupsForEntity(String memberKey, int type) throws GroupsException { Connection conn = null; - Collection groups = new ArrayList(); + List groups = new ArrayList<>(); IEntityGroup eg = null; try { @@ -444,7 +451,7 @@ private java.util.Iterator findParentGroupsForEntity(String memberKey, int type) RDBMServices.releaseConnection(conn); } - return groups.iterator(); + return groups; } /** @@ -453,12 +460,12 @@ private java.util.Iterator findParentGroupsForEntity(String memberKey, int type) * @param serviceName * @param memberKey * @param type - * @return java.util.Iterator + * @return list of groups (IEntityGroup) */ - private java.util.Iterator findParentGroupsForGroup( + private List findParentGroupsForGroup( String serviceName, String memberKey, int type) throws GroupsException { Connection conn = null; - Collection groups = new ArrayList(); + List groups = new ArrayList<>(); IEntityGroup eg = null; try { @@ -499,7 +506,7 @@ private java.util.Iterator findParentGroupsForGroup( RDBMServices.releaseConnection(conn); } - return groups.iterator(); + return groups; } /** diff --git a/uPortal-groups/uPortal-groups-core/src/test/java/org/apereo/portal/groups/RDBMSEntityGroupStoreTest.java b/uPortal-groups/uPortal-groups-core/src/test/java/org/apereo/portal/groups/RDBMSEntityGroupStoreTest.java index 4896e731115..cefedfe0eef 100644 --- a/uPortal-groups/uPortal-groups-core/src/test/java/org/apereo/portal/groups/RDBMSEntityGroupStoreTest.java +++ b/uPortal-groups/uPortal-groups-core/src/test/java/org/apereo/portal/groups/RDBMSEntityGroupStoreTest.java @@ -1,9 +1,16 @@ package org.apereo.portal.groups; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -11,8 +18,9 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; import javax.naming.Name; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; @@ -21,181 +29,263 @@ import org.apereo.portal.jdbc.RDBMServices; import org.apereo.portal.spring.locator.ApplicationContextLocator; import org.apereo.portal.spring.locator.EntityTypesLocator; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.context.ApplicationContext; @RunWith(MockitoJUnitRunner.class) public class RDBMSEntityGroupStoreTest { - private static final String CACHE_NAME = "org.apereo.portal.groups.RDBMEntityGroupStore.search"; + private static final String SEARCH_CACHE_NAME = + "org.apereo.portal.groups.RDBMEntityGroupStore.search"; private static final String PARENT_GROUP_BY_ENTITY_CACHE_NAME = "org.apereo.portal.groups.RDBMEntityGroupStore.parentGroupEntity"; - private static final String PARENT_GROUP_BY_ENTTITY_GROUP_CACHE_NAME = + private static final String PARENT_GROUP_BY_ENTITY_GROUP_CACHE_NAME = "org.apereo.portal.groups.RDBMEntityGroupStore.parentGroupEntityGroup"; + @Mock ApplicationContext context; + @Mock Connection conn; + @Mock PreparedStatement ps; + @Mock EntityTypes entityTypes; + @Mock ResultSet rs; + @Mock IEntity entity; + @Mock IEntityGroup entityGroup; + @Mock Name name; + + Cache groupSearchCache; + Cache parentGroupEntityCache; + Cache parentGroupEntityGroupCache; + CacheManager cacheManager; + RDBMEntityGroupStore store; + + private static final String entityKey = "entity-key"; + private static final String entityLocalKey = "entity-local-key"; + private static final String serviceName = "service-name"; + + @Before + public void setUp() throws Exception { + setUpCaching(); + setUpRdbms(); + when(entity.getKey()).thenReturn(entityKey); + when(entityGroup.getLocalKey()).thenReturn(entityLocalKey); + when(entityGroup.getServiceName()).thenReturn(name); + when(name.toString()).thenReturn(serviceName); + when(entityTypes.getEntityTypeFromID(anyInt())).thenReturn(null); + } + + @After + public void tearDown() throws Exception { + cacheManager.shutdown(); + cacheManager = null; + } + + // These tests reflect the complexity of the RDBMEntityGroupStore class + // The RDBMEntityGroupStore class should really be two separate classes: + // a repository for reading from the database + // a service for processing the results. + // + // In addition, RDBMEntityGroupStore should be migrated to Spring annotations + // and bean management, which would eliminate the need for CacheManager + // and ApplicationContext + @Test - public void testFindParentGroupsByIEntity() throws Exception { - // This test confirms that calling findParentGroups(IEntity) with the same key - // multiple times will check the cache each time but will only write - // to the cache one time. - // - // This test reflects the complexity of the RDBMEntityGroupStore class - // The RDBMEntityGroupStore class should really be two separate classes: - // a repository for reading from the database - // a service for processing the results. - // - // In addition, RDBMEntityGroupStore should be migrated to Spring annotations - // and bean management, which would eliminate the need for CacheManager - // and ApplicationContext - String cacheKey = "cache-key"; - try (MockedStatic applicationContextLocator = - mockStatic(ApplicationContextLocator.class); - MockedStatic rdbmServices = mockStatic(RDBMServices.class); - MockedStatic entityTypesLocator = - mockStatic(EntityTypesLocator.class)) { - // define all mocks - ApplicationContext context = mock(ApplicationContext.class); - Cache groupSearchCache = mock(Cache.class); - Cache parentGroupEntityCache = mock(Cache.class); - Cache parentGroupEntityGroupCache = mock(Cache.class); - CacheManager cacheManager = mock(CacheManager.class); - Connection conn = mock(Connection.class); - PreparedStatement ps = mock(PreparedStatement.class); - EntityTypes entityTypes = mock(EntityTypes.class); - ResultSet rs = mock(ResultSet.class); - IEntity entity = mock(IEntity.class); - - // define behavior for mocked classes - // during initialization - applicationContextLocator - .when(() -> ApplicationContextLocator.getApplicationContext()) - .thenReturn(context); - when(context.getBean("cacheManager", CacheManager.class)).thenReturn(cacheManager); - when(cacheManager.getCache(CACHE_NAME)).thenReturn(groupSearchCache); - when(cacheManager.getCache(PARENT_GROUP_BY_ENTITY_CACHE_NAME)) - .thenReturn(parentGroupEntityCache); - when(cacheManager.getCache(PARENT_GROUP_BY_ENTTITY_GROUP_CACHE_NAME)) - .thenReturn(parentGroupEntityGroupCache); - // during findParentGroups - when(entity.getKey()).thenReturn(cacheKey); - entityTypesLocator - .when(() -> EntityTypesLocator.getEntityTypes()) - .thenReturn(entityTypes); - when(entityTypes.getEntityTypeFromID(anyInt())).thenReturn(null); - // during findParentGroupsForEntity - rdbmServices.when(() -> RDBMServices.getConnection()).thenReturn(conn); - when(conn.prepareStatement(anyString())).thenReturn(ps); - when(ps.executeQuery()).thenReturn(rs); - when(rs.next()).thenReturn(true, false); - // set to null in order to avoid more mocks in - // RDBMEntityGroupStore.instanceFromResultSet since we don't care about the results here - when(rs.getString(1)).thenReturn(null); - when(rs.getString(2)).thenReturn("ignore-me"); - // value is ignored by entityTypes.getEntityTypeFromID() (above) - when(rs.getInt(3)).thenReturn(1); - when(rs.getString(4)).thenReturn("ignore-me"); - when(rs.getString(5)).thenReturn("ignore-me"); - - Collection collection = new ArrayList(); - Element el = new Element(cacheKey, collection.iterator()); - when(parentGroupEntityCache.get(cacheKey)).thenReturn(null, el, el, el); + public void testFindParentGroupsByIEntityForCacheMiss() throws Exception { + String cacheKey = entityKey + ":0"; + try (MockStaticUtil util = new MockStaticUtil()) { + store = new RDBMEntityGroupStore(); + store.findParentGroups(entity); + verify(parentGroupEntityCache, times(1)).get(cacheKey); + verify(parentGroupEntityCache, times(1)).put(new Element(cacheKey, null)); + verify(ps).executeQuery(); + } + } + @Test + public void testFindParentGroupsByIEntityForCacheHit() throws Exception { + String cacheKey = entityKey + ":0"; + List expectedGroups = Collections.singletonList(entityGroup); + try (MockStaticUtil util = new MockStaticUtil()) { + parentGroupEntityCache.put(new Element(cacheKey, expectedGroups)); + verify(parentGroupEntityCache, times(1)).put(any()); + store = new RDBMEntityGroupStore(); + Iterator iterator = store.findParentGroups(entity); + verify(parentGroupEntityCache, times(1)).get(cacheKey); + // check this again to make sure it wasn't called again + verify(parentGroupEntityCache, times(1)).put(any()); + verify(ps, never()).executeQuery(); + verifyResults(iterator, expectedGroups); + } + } + + @Test + public void testFindParentGroupsByIEntityCalledMultipleTimes() throws Exception { + // This test confirms that calling findParentGroups(IEntity) with the same key multiple + // times will check the + // cache each time but will only write to the cache one time. + String cacheKey = entityKey + ":0"; + try (MockStaticUtil util = new MockStaticUtil()) { // for the purposes of this test, we don't care what is actually returned // from the database; we're just confirming that the cache is being // accessed the correct number of times - RDBMEntityGroupStore store = new RDBMEntityGroupStore(); + store = new RDBMEntityGroupStore(); store.findParentGroups(entity); store.findParentGroups(entity); store.findParentGroups(entity); store.findParentGroups(entity); verify(parentGroupEntityCache, times(4)).get(cacheKey); - verify(parentGroupEntityCache, times(1)).put(el); + verify(parentGroupEntityCache, times(1)).put(new Element(cacheKey, null)); + } + } + + @Test + public void + testFindParentGroupsByIEntityForCacheHitReturnsIteratorThatDoesNotSupportRemoveMethod() + throws Exception { + String cacheKey = entityKey + ":0"; + try (MockStaticUtil util = new MockStaticUtil()) { + store = new RDBMEntityGroupStore(); + parentGroupEntityCache.put( + new Element(cacheKey, Collections.singletonList(entityGroup))); + Iterator iterator = store.findParentGroups(entity); + assertNotNull(iterator); + assertNotNull(iterator.next()); + assertIteratorDoesNotSupportRemove(iterator); + } + } + + @Test + public void + testFindParentGroupsByIEntityForCacheMissReturnsIteratorThatDoesNotSupportRemoveMethod() + throws Exception { + try (MockStaticUtil util = new MockStaticUtil()) { + store = new RDBMEntityGroupStore(); + Iterator iterator = store.findParentGroups(entity); + assertNotNull(iterator); + assertNotNull(iterator.next()); + assertIteratorDoesNotSupportRemove(iterator); + } + } + + @Test + public void testFindParentGroupsByIEntityGroupForCacheMiss() throws Exception { + String cacheKey = entityLocalKey + ":0:" + serviceName; + try (MockStaticUtil util = new MockStaticUtil()) { + store = new RDBMEntityGroupStore(); + store.findParentGroups(entityGroup); + verify(parentGroupEntityGroupCache, times(1)).get(cacheKey); + verify(parentGroupEntityGroupCache, times(1)).put(new Element(cacheKey, null)); + verify(ps).executeQuery(); + } + } + + @Test + public void testFindParentGroupsByIEntityGroupForCacheHit() throws Exception { + String cacheKey = entityLocalKey + ":0:" + serviceName; + List expectedGroups = Collections.singletonList(entityGroup); + try (MockStaticUtil util = new MockStaticUtil()) { + parentGroupEntityGroupCache.put(new Element(cacheKey, expectedGroups)); + verify(parentGroupEntityGroupCache, times(1)).put(new Element(cacheKey, null)); + store = new RDBMEntityGroupStore(); + Iterator iterator = store.findParentGroups(entityGroup); + verify(parentGroupEntityGroupCache, times(1)).get(cacheKey); + // check this again to make sure it wasn't called again + verify(parentGroupEntityGroupCache, times(1)).put(new Element(cacheKey, null)); + verify(ps, never()).executeQuery(); + verifyResults(iterator, expectedGroups); } } @Test - public void testFindParentGroupsByIEntityGroup() throws Exception { + public void testFindParentGroupsByIEntityGroupCalledMultipleTimes() throws Exception { // This test confirms that calling findParentGroups(IEntityGroup) with the same key // multiple times will check the cache each time but will only write // to the cache one time. - // - // This test reflects the complexity of the RDBMEntityGroupStore class - // The RDBMEntityGroupStore class should really be two separate classes: - // a repository for reading from the database - // a service for processing the results. - // - // In addition, RDBMEntityGroupStore should be migrated to Spring annotations - // and bean management, which would eliminate the need for CacheManager - // and ApplicationContext - String cacheKey = "cache-key"; - String serviceName = "service-name"; - try (MockedStatic applicationContextLocator = - mockStatic(ApplicationContextLocator.class); - MockedStatic rdbmServices = mockStatic(RDBMServices.class); - MockedStatic entityTypesLocator = - mockStatic(EntityTypesLocator.class)) { - // define all mocks - ApplicationContext context = mock(ApplicationContext.class); - Cache groupSearchCache = mock(Cache.class); - Cache parentGroupEntityCache = mock(Cache.class); - Cache parentGroupEntityGroupCache = mock(Cache.class); - CacheManager cacheManager = mock(CacheManager.class); - Name name = mock(Name.class); - Connection conn = mock(Connection.class); - PreparedStatement ps = mock(PreparedStatement.class); - EntityTypes entityTypes = mock(EntityTypes.class); - ResultSet rs = mock(ResultSet.class); - IEntityGroup entityGroup = mock(IEntityGroup.class); - - // define behavior for mocked classes - // during initialization - applicationContextLocator - .when(() -> ApplicationContextLocator.getApplicationContext()) - .thenReturn(context); - when(context.getBean("cacheManager", CacheManager.class)).thenReturn(cacheManager); - when(cacheManager.getCache(CACHE_NAME)).thenReturn(groupSearchCache); - when(cacheManager.getCache(PARENT_GROUP_BY_ENTITY_CACHE_NAME)) - .thenReturn(parentGroupEntityCache); - when(cacheManager.getCache(PARENT_GROUP_BY_ENTTITY_GROUP_CACHE_NAME)) - .thenReturn(parentGroupEntityGroupCache); - // during findParentGroups - when(entityGroup.getLocalKey()).thenReturn(cacheKey); - when(name.toString()).thenReturn(serviceName); - when(entityGroup.getServiceName()).thenReturn(name); - entityTypesLocator - .when(() -> EntityTypesLocator.getEntityTypes()) - .thenReturn(entityTypes); - when(entityTypes.getEntityTypeFromID(anyInt())).thenReturn(null); - // during findParentGroupsForEntity - rdbmServices.when(() -> RDBMServices.getConnection()).thenReturn(conn); - when(conn.prepareStatement(anyString())).thenReturn(ps); - when(ps.executeQuery()).thenReturn(rs); - when(rs.next()).thenReturn(true, false); - // set to null in order to avoid more mocks in - // RDBMEntityGroupStore.instanceFromResultSet since we don't care about the results here - when(rs.getString(1)).thenReturn(null); - when(rs.getString(2)).thenReturn("ignore-me"); - // value is ignored by entityTypes.getEntityTypeFromID() (above) - when(rs.getInt(3)).thenReturn(1); - when(rs.getString(4)).thenReturn("ignore-me"); - when(rs.getString(5)).thenReturn("ignore-me"); - - Collection collection = new ArrayList(); - Element el = new Element(cacheKey, collection.iterator()); - when(parentGroupEntityGroupCache.get(cacheKey)).thenReturn(null, el, el, el); - + String cacheKey = entityLocalKey + ":0:" + serviceName; + try (MockStaticUtil util = new MockStaticUtil()) { // for the purposes of this test, we don't care what is actually returned // from the database; we're just confirming that the cache is being // accessed the correct number of times - RDBMEntityGroupStore store = new RDBMEntityGroupStore(); + store = new RDBMEntityGroupStore(); store.findParentGroups(entityGroup); store.findParentGroups(entityGroup); store.findParentGroups(entityGroup); store.findParentGroups(entityGroup); verify(parentGroupEntityGroupCache, times(4)).get(cacheKey); - verify(parentGroupEntityGroupCache, times(1)).put(el); + verify(parentGroupEntityGroupCache, times(1)).put(new Element(cacheKey, null)); + } + } + + private void verifyResults(Iterator iterator, List expectedGroups) { + assertNotNull(iterator); + for (IEntityGroup expectedGroup : expectedGroups) { + assertTrue(iterator.hasNext()); + IEntityGroup group = iterator.next(); + assertNotNull(group); + assertEquals(expectedGroup, group); + } + assertFalse(iterator.hasNext()); + } + + private void assertIteratorDoesNotSupportRemove(Iterator iterator) { + try { + iterator.remove(); + fail(); + } catch (UnsupportedOperationException expected) { + } + } + + private void setUpCaching() { + groupSearchCache = spy(createNewCache(SEARCH_CACHE_NAME)); + parentGroupEntityCache = spy(createNewCache(PARENT_GROUP_BY_ENTITY_CACHE_NAME)); + parentGroupEntityGroupCache = spy(createNewCache(PARENT_GROUP_BY_ENTITY_GROUP_CACHE_NAME)); + cacheManager = new CacheManager(); + cacheManager.addCache(groupSearchCache); + cacheManager.addCache(parentGroupEntityCache); + cacheManager.addCache(parentGroupEntityGroupCache); + when(context.getBean("cacheManager", CacheManager.class)).thenReturn(cacheManager); + } + + private Cache createNewCache(String cacheName) { + return new Cache(cacheName, 100, false, false, 300, 300); + } + + private void setUpRdbms() throws Exception { + when(conn.prepareStatement(anyString())).thenReturn(ps); + when(ps.executeQuery()).thenReturn(rs); + when(rs.next()).thenReturn(true, false); + // set to null in order to avoid more mocks in + // RDBMEntityGroupStore.instanceFromResultSet since we don't care about the results here + when(rs.getString(1)).thenReturn("key1"); + when(rs.getString(2)).thenReturn("ignore-me"); + // value is ignored by entityTypes.getEntityTypeFromID() (above) + when(rs.getInt(3)).thenReturn(1); + when(rs.getString(4)).thenReturn("ignore-me"); + when(rs.getString(5)).thenReturn("ignore-me"); + } + + class MockStaticUtil implements AutoCloseable { + MockedStatic applicationContextLocator = + mockStatic(ApplicationContextLocator.class); + MockedStatic rdbmServices = mockStatic(RDBMServices.class); + MockedStatic entityTypesLocator = mockStatic(EntityTypesLocator.class); + + public MockStaticUtil() { + applicationContextLocator + .when(ApplicationContextLocator::getApplicationContext) + .thenReturn(context); + entityTypesLocator.when(EntityTypesLocator::getEntityTypes).thenReturn(entityTypes); + rdbmServices.when(RDBMServices::getConnection).thenReturn(conn); + } + + @Override + public void close() { + applicationContextLocator.close(); + rdbmServices.close(); + entityTypesLocator.close(); } } }