/* * Copyright (c) 2016 NTT DATA Corporation * * 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 jp.terasoluna.fw.batch.executor; import static java.util.Arrays.asList; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsCollectionContaining.hasItems; import static org.hamcrest.core.IsNot.not; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.*; import static org.mockito.Matchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static uk.org.lidalia.slf4jtest.LoggingEvent.info; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import jp.terasoluna.fw.batch.executor.vo.BatchJobData; import uk.org.lidalia.slf4jtest.TestLogger; import uk.org.lidalia.slf4jtest.TestLoggerFactory; /** * {@code CacheableApplicationContextResolverImpl}のテストケース */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:beansDef/AdminContext_CacheableApplicationContextResolverImplTest.xml") public class CacheableApplicationContextResolverImplTest { @Autowired private CacheableApplicationContextResolverImpl applicationContextResolver; @Autowired private CacheManager cacheManager; /** * ロガー */ private static final TestLogger logger = TestLoggerFactory.getTestLogger( CacheableApplicationContextResolverImpl.class); /** * テスト前処理 * @throws Exception 予期しない例外 */ @Before public void setUp() throws Exception { cacheManager.getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY) .clear(); logger.clear(); } /** * testSetCacheManager01 【正常系】 * * <pre> * 事前条件 * ・特になし * 確認項目 * ・内部フィールドにcacheManagerが設定されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testSetCacheManager01() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); CacheManager cacheManager = mock(CacheManager.class); // テスト実行 target.setCacheManager(cacheManager); assertThat(target.cacheManager, is(cacheManager)); } /** * testResolveApplicationContext01 【正常系】 * * <pre> * 事前条件 * ・Spring cache abstractionによるキャッシュ機構が使用可能なインスタンスを使用し、 * 事前に内部キャッシュがクリアされていること。 * 確認項目 * ・1度目に業務コンテキストが生成の後返却され、 * 2度目にキャッシュされたコンテキストが返却されること。 * この後ジョブ業務コードの異なる別の業務コンテキストをキャッシュさせた場合、 * ジョブ業務コードをキーとしてそれぞれ異なるキャッシュインスタンスが取得できること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testResolveApplicationContext01() throws Exception { BatchJobData batchJobData = new BatchJobData(); batchJobData.setJobAppCd("B000001"); // テスト実行(1回目) ApplicationContext blogicContextFirst = applicationContextResolver .resolveApplicationContext(batchJobData); assertThat(logger.getLoggingEvents(), is(asList(info( "[IAL025019] BLogic context will be cached. jobAppCd:B000001")))); logger.clear(); // テスト実行(2回目) ApplicationContext blogicContextSecond = applicationContextResolver .resolveApplicationContext(batchJobData); // 業務コンテキスト生成ログが出力されないこと。 assertFalse(logger.getLoggingEvents().contains(info( "[IAL025019] BLogic context will be cached. jobAppCd:B000001"))); logger.clear(); // キャッシュされたコンテキストとインスタンスが同一であること。 assertThat(blogicContextSecond, is(blogicContextFirst)); batchJobData.setJobAppCd("B000002"); // テスト実行(別の業務コンテキストを生成 1回目) ApplicationContext anotherContextFirst = applicationContextResolver .resolveApplicationContext(batchJobData); assertThat(logger.getLoggingEvents(), is(asList(info( "[IAL025019] BLogic context will be cached. jobAppCd:B000002")))); logger.clear(); // テスト実行(別の業務コンテキストを生成 2回目) ApplicationContext anotherContextSecond = applicationContextResolver .resolveApplicationContext(batchJobData); // 業務コンテキスト生成ログが出力されないこと。 assertFalse(logger.getLoggingEvents().contains(info( "[IAL025019] BLogic context will be cached. jobAppCd:B000002"))); // キャッシュされたコンテキストとインスタンスが同一であること。 assertThat(anotherContextSecond, is(anotherContextFirst)); // キャッシュされたB000001とB000002のコンテキストのインスタンスが異なっていること。 assertThat(blogicContextSecond, not(anotherContextSecond)); } /** * testResolveApplicationContext02 【正常系】 * * <pre> * 事前条件 * ・Spring cache abstractionによるキャッシュ機構が使用可能なインスタンスを使用し、 * 事前に内部キャッシュがクリアされていること。 * 確認項目 * ・複数スレッドが同時にコールした場合に、ジョブ業務コード単位に同期化され、 * ジョブ業務コードにつき1つのApplicationContextが生成されていること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testResolveApplicationContext02() throws Exception { // 事前準備 int multiplicity = 5; CountDownLatch latch = new CountDownLatch(multiplicity); ExecutorService es = Executors.newFixedThreadPool(multiplicity); // 5並列で、3種類のジョブ業務コードを実行する List<SingleTask> taskList = new ArrayList<>(); taskList.add(new SingleTask(this.applicationContextResolver, latch, "B000001")); taskList.add(new SingleTask(this.applicationContextResolver, latch, "B000001")); taskList.add(new SingleTask(this.applicationContextResolver, latch, "B000002")); taskList.add(new SingleTask(this.applicationContextResolver, latch, "B000002")); taskList.add(new SingleTask(this.applicationContextResolver, latch, "B000003")); try { // 試験実施 logger.clearAll(); List<Future<ApplicationContext>> taskFutures = es.invokeAll(taskList); List<ApplicationContext> result = new ArrayList<>(); for (Future<ApplicationContext> future : taskFutures) { result.add(future.get(60L, TimeUnit.SECONDS)); } // 検証 // 5並列の結果の確認 assertEquals(5, result.size()); // ジョブ業務コードごとにコンテキストが払い出されていることの確認 int cachedSize = Map.class.cast(cacheManager.getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY) .getNativeCache()).size(); // ジョブ業務コードは3種類なのでキャッシュは3つ assertEquals(3, cachedSize); // B000001のctxは一致する assertSame(result.get(0), result.get(1)); // B000002のctxは一致する assertSame(result.get(2), result.get(3)); // B000003のctxは他と一致しない assertNotSame(result.get(0), result.get(4)); assertNotSame(result.get(2), result.get(4)); // ログ確認 assertThat(logger.getAllLoggingEvents(), hasItems( info("[IAL025019] BLogic context will be cached. jobAppCd:B000001"), info("[IAL025019] BLogic context will be cached. jobAppCd:B000002"), info("[IAL025019] BLogic context will be cached. jobAppCd:B000003"))); } finally { es.shutdown(); } } /** * 並列処理用のタスククラス */ public static class SingleTask implements Callable<ApplicationContext> { public ApplicationContextResolver applicationContextResolver; public CountDownLatch latch; public String jobAppCd; public SingleTask(ApplicationContextResolver applicationContextResolver, CountDownLatch latch, String jobAppCd) { this.applicationContextResolver = applicationContextResolver; this.latch = latch; this.jobAppCd = jobAppCd; } @Override public ApplicationContext call() { latch.countDown(); BatchJobData batchJobData = new BatchJobData(); batchJobData.setJobAppCd(jobAppCd); ApplicationContext ctx = applicationContextResolver .resolveApplicationContext(batchJobData); return ctx; } } /** * testCloseApplicationContext01 【正常系】 * * <pre> * 事前条件 * ・Spring cache abstractionによるキャッシュ機構を使用している。 *  (CacheManagerフィールドを持ち、businessContextのキャッシュが可能な状態) * 確認項目 * ・ApplicationContextがクローズされないこと。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testCloseApplicationContext01() throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("beansDef/B000001.xml"); // テスト実行 applicationContextResolver.closeApplicationContext(ctx); // コンテキストがクローズされていない⇒コンテキスト内部のBeanが取り出し可能であること。 assertThat(ctx.getBean("B000001BLogic"), is(notNullValue())); } /** * testAfterPropertiesSet01 【正常系】 * * <pre> * 事前条件 * ・特になし。 * 確認項目 * ・共通コンテキストのパスが未設定の場合、親DIコンテナが生成されないこと。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testAfterPropertiesSet01() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); // テスト実行 try { target.afterPropertiesSet(); fail(); } catch (BeanCreationException e) { assertThat(e.getMessage(), is( "[EAL025061] Cannot create CacheableApplicationContextResolverImpl, because either cacheManager is not injected or Cache instance is not found by key:businessContext")); } } /** * testDestroy01 【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ対象の業務コンテキストがフィールドに存在している。 * 確認項目 * ・業務コンテキストがクローズされていること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testDestroy01() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); final ApplicationContext b000001 = new ClassPathXmlApplicationContext("beansDef/B000001.xml"); final ApplicationContext b000002 = new ClassPathXmlApplicationContext("beansDef/B000002.xml"); CacheManager cacheManager = mock(CacheManager.class); doReturn(new ArrayList<String>() { private static final long serialVersionUID = -6836804456272606019L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); Cache cache = mock(Cache.class); doReturn(new HashMap<String, ApplicationContext>() { private static final long serialVersionUID = -8224529649381655075L; { put("B000001", b000001); put("B000002", b000002); } }).when(cache).getNativeCache(); doReturn(cache).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); target.cacheManager = cacheManager; // テスト実行 target.destroy(); verify(cache).clear(); // 業務コンテキストの全てがクローズ済みであること。 try { b000001.getBean("B000001BLogic"); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), is( "BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext")); } try { b000002.getBean("B000002BLogic"); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), is( "BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext")); } } /** * testDestroyCachedContext01 【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されているが、キャッシュは空。 * 確認項目 * ・何も行われないこと。(キャッシュのクリアはよばれること。) * </pre> * * @throws Exception 予期しない例外 */ @Test public void testDestroyCachedContext01() throws Exception { CacheableApplicationContextResolverImpl target = spy( new CacheableApplicationContextResolverImpl()); Cache cache = mock(Cache.class); doReturn(new HashMap<String, ApplicationContext>()).when(cache) .getNativeCache(); CacheManager cacheManager = mock(CacheManager.class); doReturn(cache).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); doReturn(new ArrayList<String>() { private static final long serialVersionUID = 2574172591143649628L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); target.setCacheManager(cacheManager); // テスト実行 target.destroyCachedContext(); verify(target, never()).closeApplicationContext(any( ApplicationContext.class)); verify(cache).clear(); } /** * testDestroyCachedContext02 【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されており、 *  ApplicationContextとそれ以外のオブジェクトが含まれているとき、 *  ApplicationContextのみクローズされること。 * 確認項目 * ・キャッシュされているApplicationContextのみがクローズされること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testDestroyCachedContext02() throws Exception { final ApplicationContext b000001 = new ClassPathXmlApplicationContext("beansDef/B000001.xml"); final ApplicationContext b000002 = new ClassPathXmlApplicationContext("beansDef/B000002.xml"); CacheableApplicationContextResolverImpl target = spy( new CacheableApplicationContextResolverImpl()); Cache cache = mock(Cache.class); doReturn(new HashMap<String, Object>() { private static final long serialVersionUID = 2574172591143649628L; { put("B000001", b000001); put("B000002", b000002); put("another", "anotherObject"); } }).when(cache).getNativeCache(); CacheManager cacheManager = mock(CacheManager.class); doReturn(cache).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); doReturn(new ArrayList<String>() { private static final long serialVersionUID = 4460457695974083597L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); target.setCacheManager(cacheManager); // テスト実行 target.destroyCachedContext(); // 業務コンテキストの全てがクローズ済みであること。 try { b000001.getBean("B000001BLogic"); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), is( "BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext")); } try { b000002.getBean("B000002BLogic"); fail(); } catch (IllegalStateException e) { assertThat(e.getMessage(), is( "BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext")); } verify(cache).clear(); } /** * testIsCacheEnabled01【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されていない。 * 確認項目 * ・falseが返却されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testIsCacheEnabled01() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); // テスト実行 assertThat(target.isCacheEnabled(), is(false)); } /** * testIsCacheEnabled02【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されているが、blogicContextというキャッシュ名を含んでいない。 * 確認項目 * ・falseが返却されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testIsCacheEnabled02() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); CacheManager cacheManager = mock(CacheManager.class); doReturn(new ArrayList<String>()).when(cacheManager).getCacheNames(); target.setCacheManager(cacheManager); // テスト実行 assertThat(target.isCacheEnabled(), is(false)); } /** * testIsCacheEnabled03【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されているが、キャッシュオブジェクトがnull。 * 確認項目 * ・falseが返却されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testIsCacheEnabled03() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); CacheManager cacheManager = mock(CacheManager.class); doReturn(new ArrayList<String>() { private static final long serialVersionUID = -5780661534958374434L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); doReturn(null).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); target.setCacheManager(cacheManager); // テスト実行 assertThat(target.isCacheEnabled(), is(false)); } /** * testIsCacheEnabled04【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されているが、キャッシュオブジェクトがMapではない。 * 確認項目 * ・falseが返却されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testIsCacheEnabled04() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); CacheManager cacheManager = mock(CacheManager.class); doReturn(new ArrayList<String>() { private static final long serialVersionUID = -5780661534958374434L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); Cache cache = mock(Cache.class); doReturn("not Map").when(cache).getNativeCache(); doReturn(cache).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); target.setCacheManager(cacheManager); // テスト実行 assertThat(target.isCacheEnabled(), is(false)); } /** * testIsCacheEnabled05【正常系】 * * <pre> * 事前条件 * ・事前にキャッシュ機能(CacheManager)が設定されており、キャッシュオブジェクトとしてMapを設定している。 * 確認項目 * ・trueが返却されること。 * </pre> * * @throws Exception 予期しない例外 */ @Test public void testIsCacheEnabled05() throws Exception { CacheableApplicationContextResolverImpl target = new CacheableApplicationContextResolverImpl(); CacheManager cacheManager = mock(CacheManager.class); doReturn(new ArrayList<String>() { private static final long serialVersionUID = -5780661534958374434L; { add(CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); } }).when(cacheManager).getCacheNames(); Cache cache = mock(Cache.class); doReturn(new HashMap<String, Object>()).when(cache).getNativeCache(); doReturn(cache).when(cacheManager).getCache( CacheableApplicationContextResolverImpl.BLOGIC_CONTEXT_CACHE_KEY); target.setCacheManager(cacheManager); // テスト実行 assertThat(target.isCacheEnabled(), is(true)); } }