/*
 * Copyright 2002-2008 the original author or authors.
 *
 * 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 anyframe.core.hibernate.concurrency;

import java.util.HashSet;
import java.util.Set;

import org.hibernate.StaleObjectStateException;

import anyframe.common.util.DateUtil;
import anyframe.core.hibernate.AbstractConfigurationalTest;
import anyframe.sample.model.bidirection.concurrency.optimistic.Country;
import anyframe.sample.model.bidirection.concurrency.optimistic.Movie;

/**
 * TestCase Name : HibernateOptimisticLockingTest <br>
 * <br>
 * [Description] : Optimistic Locking 테스트를 수행해본다.<br>
 * [Main Flow]
 * <ul>
 * <li>#-1 Positive Case : VERSION 필드를 가진 Country. 첫번째 트랜잭션 내에서 테스트를 위한 신규 데이터를
 * 입력한 후, 두번째 트랜잭션 내에서 동일한 식별자를 이용하여 Country 정보를 두번 조회한다. 두번째 트랜잭션이 종료된 후에 앞서
 * 조회한 Country의 countryName을 다른 것으로 셋팅해둔다. 세번째 트랜잭션 내에서는 두번째 조회한 Country 정보에 다른
 * countryName을 셋팅하여 DB에 반영한다. <br/> 네번째 트랜잭션에서 첫번째 조회한 Country 정보에 대해 update()
 * 메소드를 호출해본다.</li>
 * <li>#-2 Negative Case : VERSION 필드를 가지지 않은 Movie. 첫번째 트랜잭션 내에서 테스트를 위한 신규
 * 데이터를 입력한 후, 두번째 트랜잭션 내에서 동일한 식별자를 이용하여 Movie 정보를 두번 조회한다. 두번째 트랜잭션이 종료된 후에 앞서
 * 조회한 Movie의 title을 다른 것으로 셋팅해둔다. 세번째 트랜잭션 내에서는 두번째 조회한 Movie 정보에 다른 title을
 * 셋팅하여 DB에 반영한다. <br/> 네번째 트랜잭션에서 첫번째 조회한 Movie 정보에 대해 update() 메소드를 호출해본다.</li>
 * </ul>
 * 
 * @author SoYon Lim
 */
public class HibernateOptimisticLockingTest extends AbstractConfigurationalTest {
	protected String getHibernateConfigLocation() {
		return "anyframe/core/hibernate/concurrency/hibernate-optimistic.cfg.xml";
	}

	/**
	 * [Flow #-1] Positive Case : 첫번째 트랜잭션 내에서 테스트를 위한 신규 데이터를 입력한 후, 두번째 트랜잭션
	 * 내에서 동일한 식별자를 이용하여 Country 정보를 두번 조회한다. 두번째 트랜잭션이 종료된 후에 앞서 조회한 Country의
	 * countryName을 다른 것으로 셋팅해둔다. 세번째 트랜잭션 내에서는 두번째 조회한 Country 정보에 다른
	 * countryName을 셋팅하여 DB에 반영한다. <br/> 네번째 트랜잭션에서 첫번째 조회한 Country 정보에 대해
	 * update() 메소드를 호출해본다. 이때, 세번째 트랜잭션에서의 수정으로 인해 COUNTRY_VERSION이 이미 변경되었기
	 * 때문에 StaleObjectStateException 발생이 예상된다.
	 * 
	 * @throws Exception
	 */
	public void testUpdateCountryWithOptimisticLocking() throws Exception {
		// 1. insert a new country, movies information
		newSession();
		addCountryMovieAtOnce();
		closeSession();

		// 2. select a country
		newSession();
		Country fstCountry = (Country) session.get(Country.class,
				"COUNTRY-0001");
		assertEquals("fail to check a version of country.", 0, fstCountry
				.getVersion());
		Country scdCountry = (Country) session.get(Country.class,
				"COUNTRY-0001");
		closeSession();

		// 3. set country name
		fstCountry.setCountryName("First : Republic of Korea.");

		// 4. select a country again with same id and update country name
		newSession();
		scdCountry.setCountryName("Second : Republic of Korea.");
		closeSession();

		// 5. try to update with detached object
		newSession();
		try {
			session.update(fstCountry);
			closeSession();
		} catch (Exception e) {
			e.printStackTrace();
			assertTrue("fail to throw StaleObjectStateException.",
					e instanceof StaleObjectStateException);
		}
	}

	/**
	 * [Flow #-2] Negative Case : 첫번째 트랜잭션 내에서 테스트를 위한 신규 데이터를 입력한 후, 두번째 트랜잭션
	 * 내에서 동일한 식별자를 이용하여 Movie 정보를 두번 조회한다. 두번째 트랜잭션이 종료된 후에 앞서 조회한 Movie의
	 * title을 다른 것으로 셋팅해둔다. 세번째 트랜잭션 내에서는 두번째 조회한 Movie 정보에 다른 title을 셋팅하여 DB에
	 * 반영한다. <br/> 네번째 트랜잭션에서 첫번째 조회한 Movie 정보에 대해 update() 메소드를 호출해본다. 이때, 네번째
	 * 트랜잭션에서의 수정이 정상적으로 처리되면서, 세번째 트랜잭션에서의 수정이 무효화되게 된다.
	 * 
	 * @throws Exception
	 */
	public void testUpdateMovieWithoutOptimisticLocking() throws Exception {
		// 1. insert a new country, movies information
		newSession();
		addCountryMovieAtOnce();
		closeSession();

		// 2. select a country
		newSession();
		Movie fstMovie = (Movie) session.get(Movie.class, "MV-00001");
		Movie scdMovie = (Movie) session.get(Movie.class, "MV-00001");
		closeSession();

		// 3. set country name
		fstMovie.setTitle("First : My Sassy Girl");

		// 4. select a country again with same id and update country name
		newSession();
		scdMovie.setTitle("Second : My Sassy Girl");
		closeSession();

		// 5. try to update with detached object
		newSession();
		session.update(fstMovie);
		closeSession();
	}

	private String addCountryMovieAtOnce() throws Exception {
		// 1. insert a country information with movies
		Movie movie1 = new Movie();
		movie1.setMovieId("MV-00001");
		movie1.setDirector("J.Y.Gwak");
		movie1.setReleaseDate(DateUtil.string2Date("2001-07-27", "yyyy-MM-dd"));
		movie1.setTitle("My Sassy Girl");

		Movie movie2 = new Movie();
		movie2.setMovieId("MV-00002");
		movie2.setDirector("Hojun Kim");
		movie2.setReleaseDate(DateUtil.string2Date("2004-04-02", "yyyy-MM-dd"));
		movie2.setTitle("My Little Bride");

		Country country1 = new Country();
		String countryCode = "COUNTRY-0001";
		country1.setCountryCode(countryCode);
		country1.setCountryId("KR");
		country1.setCountryName("Korea");

		Set movies = new HashSet();
		movie1.setCountry(country1);
		movies.add(movie1);
		movie2.setCountry(country1);
		movies.add(movie2);
		country1.setMovies(movies);

		session.save(country1);

		return countryCode;
	}

}

