package com.shin1ogawa.entity;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.Assert.assertThat;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.jdo.JDOException;
import javax.jdo.JDOHelper;
import javax.jdo.JDOOptimisticVerificationException;
import javax.jdo.ObjectState;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import javax.jdo.Transaction;
import javax.jdo.annotations.VersionStrategy;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.junit.Before;
import org.junit.Test;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Text;
import com.shin1ogawa.AbstractRelasionShipTest;

public class ParentATest extends AbstractRelasionShipTest {
	private ChildA1 childA1_1, childA1_2;
	private ChildA2 childA2_1, childA2_2;
	private OtherA otherA_1, otherA_2;
	private OtherB otherB_1, otherB_2;

	/**
	 * ParentAが参照するEntityを作成する。
	 * <p>
	 * このメソッドではchildA1_1, childA1_2, childA2_1, childA2_2は永続化しない点に注意が必要。
	 * </p>
	 * 
	 * @see com.shin1ogawa.AbstractRelasionShipTest#setUp()
	 */
	@Before
	public void setUp() {
		super.setUp();
		PersistenceManager pm = getFactory().getPersistenceManager();
		try {
			childA1_1 = new ChildA1().setValue("ChildA1_1");
			childA1_2 = new ChildA1().setValue("ChildA1_2");
			childA2_1 = new ChildA2().setValue("ChildA2_1");
			childA2_2 = new ChildA2().setValue("ChildA2_2");
			otherA_1 = new OtherA().setValue("OtherA_1").save(pm);
			otherA_2 = new OtherA().setValue("OtherA_2").save(pm);
			otherB_1 = new OtherB().setValue("OtherB_1").save(pm, 1);
			otherB_2 = new OtherB().setValue("OtherB_2").save(pm, 2);
		} finally {
			pm.close();
		}
	}

	@Test
	public void 普通に新規追加する() {
		ParentA entity = createParentA1();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1(), notNullValue());
		assertThat(entity2.getChildA2list().size(), is(2));
		assertThat(entity2.getOtherAKey(), notNullValue());
		assertThat(entity2.getOtherBKeys().size(), is(2));
		assertThat(childA1_1.getKey(), notNullValue());
		assertThat(childA1_1.getKey().getId(), not((long) 0));
	}

	@Test
	public void ロールバックの確認() {
		ParentA entity = createParentA1();
		String oldValue = entity.getText().getValue();

		PersistenceManager pm = getFactory().getPersistenceManager();
		entity = pm.getObjectById(ParentA.class, entity.getKey());
		entity.setText(new Text("hoge"));
		Transaction transaction = pm.currentTransaction();
		transaction.begin();
		pm.makePersistent(entity);
		transaction.rollback();
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getText().getValue(), is(equalTo(oldValue)));
		assertThat(entity2.getChildA1(), notNullValue());
		assertThat(entity2.getChildA2list().size(), is(2));
		assertThat(entity2.getOtherAKey(), notNullValue());
		assertThat(entity2.getOtherBKeys().size(), is(2));
		assertThat(childA1_1.getKey(), notNullValue());
		assertThat(childA1_1.getKey().getId(), not((long) 0));
	}

	@Test
	// @Ignore("SDK1.2.1では低レベルAPIでのTransactionはサポートされていない。")
	public void 低レベルAPIでロールバックの確認() throws EntityNotFoundException {
		ParentA entity = createParentA1();
		String oldValue = entity.getText().getValue();

		DatastoreService service = DatastoreServiceFactory
				.getDatastoreService();
		com.google.appengine.api.datastore.Transaction transaction = service
				.beginTransaction();

		Entity e = service.get(entity.getKey());
		e.setProperty("text", new Text("hoge"));
		service.put(e);
		transaction.rollback();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getText().getValue(), is(equalTo(oldValue)));
	}

	/**
	 * ルートEntityを取得して、そこから取得した子Entityに対して何か操作をし、ルートEntityをmakePersist()する。
	 */
	@Test
	public void 親子関係の子を更新する_OK() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity = pm.getObjectById(ParentA.class, entity.getKey());
			entity.getChildA1().setValue("childA1_1更新済み");
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1().getValue(), is("childA1_1更新済み"));
	}

	/**
	 * ルートEntityを取得して、そこから取得した子Entityに対して何か操作をし、ルートEntityをmakePersist()する。
	 * <p>
	 * 更新用のPersistenceManagerで取得し直さない場合。
	 * </p>
	 * <p>
	 * SDK1.2.2まではJDOExceptionが発生していたが、SDK1.2.5からは問題なく
	 * </p>
	 */
	@Test//(expected = JDOException.class)
	public void 親子関係の子を更新する_NG() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity.getChildA1().setValue("childA1_1更新済み");
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1().getValue(), is("childA1_1更新済み"));
	}

	/**
	 * ChildのみをKey指定して取得し、ChildのみをmakePersist()する。
	 */
	@Test
	public void 親子関係の子だけを更新する_OK2() {
		ParentA entity = createParentA1();

		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			ChildA1 childA1 = getParentA1ChildA(pm);
			assertThat(childA1, notNullValue());
			assertThat(childA1.getValue(), is("ChildA1_1"));
			childA1.setValue("childA1更新済み");
			pm.makePersistent(childA1);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1().getValue(), is("childA1更新済み"));
	}

	/**
	 * ChildのみをKey指定して取得し、ChildのみをmakePersist()する。
	 * <p>
	 * Childは、Transactionの外でQueryしたものを使用する。
	 * </p>
	 */
	@Test
	public void 親子関係の子だけを更新する_OK3() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();

		ChildA1 childA1 = getParentA1ChildA(pm);

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			childA1.setValue("childA1更新済み");
			pm.makePersistent(childA1);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1().getValue(), is("childA1更新済み"));
	}

	/**
	 * ChildのみをKey指定して取得し、ChildのみをmakePersist()する。
	 * <p>
	 * 別の親Entityに属するChildをひとつのTransaction内で更新すると…更新に成功する！？
	 * </p>
	 */
	@Test
	public void 親子関係の子だけを更新する_OK4() {
		ParentA entity1 = createParentA1();
		ParentA entity2 = createParentA2();
		PersistenceManager pm = getFactory().getPersistenceManager();

		ChildA1 childA1_1 = getParentA1ChildA(pm);
		ChildA1 childA1_2 = getParentA2ChildA(pm);

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			childA1_1.setValue("childA1更新済み");
			pm.makePersistent(childA1_1);
			childA1_2.setValue("childA2更新済み");
			pm.makePersistent(childA1_2);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity1a = log1(entity1.getKey());
		assertThat(entity1a.getChildA1().getValue(), is("childA1更新済み"));
		ParentA entity2a = log1(entity2.getKey());
		assertThat(entity2a.getChildA1().getValue(), is("childA2更新済み"));
	}

	/**
	 * ChildのみをKey指定して取得し、ChildのみをmakePersist()する。
	 * <p>
	 * 別の親Entityに属するChildをそれぞれ更新し、ひとつのTransaction内で親Entityを更新すると…こっちは失敗する。
	 * </p>
	 */
	@Test(expected = JDOException.class)
	public void 親子関係の子を更新する_NG4() {
		ParentA entity1 = createParentA1();
		ParentA entity2 = createParentA2();
		PersistenceManager pm = getFactory().getPersistenceManager();

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity1 = pm.getObjectById(ParentA.class, entity1.getKey());
			entity1.getChildA1().setValue("childA1_1更新済み");
			pm.makePersistent(entity1);
			entity2 = pm.getObjectById(ParentA.class, entity2.getKey()); // ここでException
			entity2.getChildA1().setValue("childA1_2更新済み");
			pm.makePersistent(entity2);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity1a = log1(entity1.getKey());
		assertThat(entity1a.getChildA1().getValue(), is("childA1更新済み"));
		ParentA entity2a = log1(entity2.getKey());
		assertThat(entity2a.getChildA1().getValue(), is("childA2更新済み"));
	}

	/**
	 * ChildのみをKey指定して取得し、ChildのみをmakePersist()する。
	 * <p>
	 * Childは、Transactionの外で、しかも違うPersistenceManagerを使ってQueryしたものを使用する。 この場合は
	 * {@literal ... is managed by a different Object Manager}となる。
	 * </p>
	 */
	@Test(expected = JDOException.class)
	public void 親子関係の子だけを更新する_NG1() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();

		ChildA1 childA1 = getParentA1ChildA(pm);

		pm = getFactory().getPersistenceManager(); // 別のPersistenceManager()を使う。
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			childA1.setValue("childA1更新済み");
			pm.makePersistent(childA1);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA1().getValue(), is("childA1更新済み"));
	}

	@Test
	public void 親子関係の子の配列を更新する_要素を削除する() {
		ParentA entity = createParentA1();
		assertThat(entity.getChildA2list().size(), is(2));
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity = pm.getObjectById(ParentA.class, entity.getKey());
			entity.getChildA2list().remove(0);
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA2list().size(), is(1));
	}

	/**
	 * Collectionを空にする。
	 * <p>
	 * 空っぽにしても問題無いようだ。
	 * </p>
	 */
	@Test
	public void 親子関係の子の配列を更新する_空にする() {
		ParentA entity = createParentA1();
		assertThat(entity.getChildA2list().size(), is(2));
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity = pm.getObjectById(ParentA.class, entity.getKey());
			entity.getChildA2list().clear();
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA2list().size(), is(0));
	}

	/**
	 * Collectionを{@code null}にする。
	 * <p>
	 * {@code null}にしても問題無いようだが、勝手に空のCollectionが作成されている？
	 * </p>
	 */
	@Test
	public void 親子関係の子の配列を更新する_nullを設定する() {
		ParentA entity = createParentA1();
		assertThat(entity.getChildA2list().size(), is(2));
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity = pm.getObjectById(ParentA.class, entity.getKey());
			entity.setChildA2list(null);
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		// assertThat(entity2.getChildA2list(), nullValue());
		assertThat(entity2.getChildA2list(), notNullValue());
	}

	@Test
	public void 親子関係の子の配列の要素内を更新する_親EntityをmakePersistent() {
		ParentA entity = createParentA1();
		assertThat(entity.getChildA2list().size(), is(2));
		assertThat(entity.getChildA2list().get(0).getValue(), is("ChildA2_1"));
		assertThat(entity.getChildA2list().get(1).getValue(), is("ChildA2_2"));
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity = pm.getObjectById(ParentA.class, entity.getKey());
			entity.getChildA2list().get(0).setValue("ChildA2_1更新済み");
			entity.getChildA2list().get(1).setValue("ChildA2_2更新済み");
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA2list().size(), is(2));
		assertThat(entity2.getChildA2list().get(0).getValue(),
				is("ChildA2_1更新済み"));
		assertThat(entity2.getChildA2list().get(1).getValue(),
				is("ChildA2_2更新済み"));
	}

	@Test
	public void 親子関係の子の配列の要素内を更新する_子EntityをmakePersistent() {
		ParentA entity = createParentA1();
		assertThat(entity.getChildA2list().size(), is(2));
		assertThat(entity.getChildA2list().get(0).getValue(), is("ChildA2_1"));
		assertThat(entity.getChildA2list().get(1).getValue(), is("ChildA2_2"));
		PersistenceManager pm = getFactory().getPersistenceManager();
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			ChildA2 childA2_1 = pm.getObjectById(ChildA2.class, this.childA2_1
					.getKey());
			childA2_1.setValue("ChildA2_1更新済み");
			ChildA2 childA2_2 = pm.getObjectById(ChildA2.class, this.childA2_2
					.getKey());
			childA2_2.setValue("ChildA2_2更新済み");
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();

		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getChildA2list().size(), is(2));
		assertThat(entity2.getChildA2list().get(0).getValue(),
				is("ChildA2_1更新済み"));
		assertThat(entity2.getChildA2list().get(1).getValue(),
				is("ChildA2_2更新済み"));
	}

	/**
	 * 別Entityの子供を自身に追加してmakePersistent()する。
	 * <p>
	 * {@literal ... but the entity identified by ... is already a child of ...}
	 * となる。
	 * </p>
	 */
	@Test(expected = JDOException.class)
	public void 別Entityの子供を自身に追加する() {
		createParentA1();
		ParentA entity2 = createParentA2();
		PersistenceManager pm = getFactory().getPersistenceManager();

		ChildA1 childA1 = getParentA1ChildA(pm);

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			entity2 = pm.getObjectById(ParentA.class, entity2.getKey());
			entity2.setChildA1(childA1);
			pm.makePersistent(entity2);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();
	}

	@Test
	public void Unownedな参照を更新する() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();
		entity = pm.getObjectById(ParentA.class, entity.getKey()); // 自身のPersistenceManagerを使って取得し直す
		assertThat(entity.getOtherAKey(), is(otherA_1.getKey()));
		entity.setOtherAKey(otherA_2.getKey());
		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();
		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getOtherAKey(), is(otherA_2.getKey()));
	}

	@Test
	public void Unownedな参照の配列を更新する() {
		ParentA entity = createParentA1();
		PersistenceManager pm = getFactory().getPersistenceManager();
		entity = pm.getObjectById(ParentA.class, entity.getKey()); // 自身のPersistenceManagerを使って取得し直す
		assertThat(entity.getOtherBKeys().contains(otherB_1.getKey()), is(true));
		assertThat(entity.getOtherBKeys().contains(otherB_2.getKey()), is(true));
		entity.getOtherBKeys().remove(otherB_1.getKey());

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();
		ParentA entity2 = log1(entity.getKey());
		assertThat(entity2.getOtherBKeys().contains(otherB_1.getKey()),
				is(false));
		assertThat(entity2.getOtherBKeys().contains(otherB_2.getKey()),
				is(true));
	}

	@SuppressWarnings("unchecked")
	@Test
	public void deleteCascadeの確認() {
		ParentA entity1 = createParentA1();
		createParentA2();

		PersistenceManager pm = null;
		Query query = null;

		// ParentA, ChildA1, ChildA2, OtherA, OtherBの件数を確認
		pm = getFactory().getPersistenceManager();
		query = pm.newQuery(ParentA.class);
		assertThat(((List<ParentA>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		query = pm.newQuery(ChildA1.class);
		assertThat(((List<ChildA1>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		query = pm.newQuery(ChildA2.class);
		assertThat(((List<ChildA2>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		query = pm.newQuery(OtherA.class);
		assertThat(((List<OtherA>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		query = pm.newQuery(OtherB.class);
		assertThat(((List<OtherB>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		pm.close();

		pm = getFactory().getPersistenceManager();
		ParentA entity = pm.getObjectById(ParentA.class, entity1.getKey());
		pm.deletePersistent(entity);
		pm.close();

		// ChildA1, ChildA2, OtherA,
		// OtherBの件数を確認(ParentAは1件、ChildA1は1件、ChildA2は2件、減っているはず)
		pm = getFactory().getPersistenceManager();
		query = pm.newQuery(ParentA.class);
		assertThat(((List<ParentA>) query.execute()).size(), is(equalTo(1)));
		query.closeAll();

		// 1:1で保持している子EntityはDeleteCascadeされない！
		query = pm.newQuery(ChildA1.class);
		// assertThat(((List<ChildA1>) query.execute()).size(), is(equalTo(2 -
		// 1)));
		assertThat(((List<ChildA1>) query.execute()).size(), is(equalTo(2 - 0)));
		query.closeAll();

		query = pm.newQuery(ChildA2.class);
		assertThat(((List<ChildA2>) query.execute()).size(), is(equalTo(2 - 2)));
		query.closeAll();
		query = pm.newQuery(OtherA.class);
		assertThat(((List<OtherA>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		query = pm.newQuery(OtherB.class);
		assertThat(((List<OtherB>) query.execute()).size(), is(equalTo(2)));
		query.closeAll();
		pm.close();
	}

	/**
	 * クエリを閉じない、PersistenceManagerを閉じない、という状態でシリアライズする。
	 * <p>
	 * {@link ObjectState#PERSISTENT_NONTRANSACTIONAL_DIRTY}の状態でシリアライズする事になる。
	 * </p>
	 * 
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@Test
	public void serialize_OK1() throws IOException, ClassNotFoundException {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = createParentA1(pm);
		Query query = pm.newQuery(ParentA.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute(entity.getKey());
		entity = results.get(0); // クエリしなおす

		System.out.println("state=" + JDOHelper.getObjectState(entity));
		assertThat(JDOHelper.getObjectState(entity),
				is(equalTo(ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL)));
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(entity);
		} finally {
			out.close();
		}
		query.closeAll(); // クエリを閉じる
		pm.close(); // PersistenceManagerを閉じる
		assertDeserialize(bytes.toByteArray());
	}

	/**
	 * クエリを閉じる、PersistenceManagerを閉じない、という状態でシリアライズする。
	 * <p>
	 * {@link ObjectState#PERSISTENT_NONTRANSACTIONAL_DIRTY}の状態でシリアライズする事になる。
	 * </p>
	 * 
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@Test
	public void serialize_OK2() throws IOException, ClassNotFoundException {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = createParentA1(pm);
		Query query = pm.newQuery(ParentA.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute(entity.getKey());
		entity = results.get(0); // クエリしなおす
		query.closeAll(); // クエリを閉じる

		System.out.println("state=" + JDOHelper.getObjectState(entity));
		assertThat(JDOHelper.getObjectState(entity),
				is(equalTo(ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL)));
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(entity);
		} finally {
			out.close();
		}
		pm.close(); // PersistenceManagerを閉じる
		assertDeserialize(bytes.toByteArray());
	}

	/**
	 * クエリを閉じる、PersistenceManagerを閉じる、という状態でシリアライズする。
	 * <p>
	 * {@link ObjectState#TRANSIENT}の状態でシリアライズする事になる。
	 * </p>
	 * 
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@Test
	public void serialize_OK3() throws IOException, ClassNotFoundException {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = createParentA1(pm);
		Query query = pm.newQuery(ParentA.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute(entity.getKey());
		entity = results.get(0); // クエリしなおす
		query.closeAll(); // クエリを閉じる
		pm.close(); // PersistenceManagerを閉じる

		assertThat(JDOHelper.getObjectState(entity),
				is(equalTo(ObjectState.TRANSIENT)));
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(entity);
		} finally {
			out.close();
		}
		assertDeserialize(bytes.toByteArray());
	}

	/**
	 * クエリを閉じない、PersistenceManagerを閉じない、値を変えてmakePersistent()してコミットする直前、
	 * という状態でシリアライズする。
	 * 
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@Test
	public void serialize_OK4() throws IOException, ClassNotFoundException {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = createParentA1(pm);
		Query query = pm.newQuery(ParentA.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute(entity.getKey());
		entity = results.get(0); // クエリしなおす
		query.closeAll(); // クエリを閉じる

		Transaction transaction = pm.currentTransaction();
		transaction.begin();
		entity.setText(new Text("a"));
		pm.makePersistent(entity);
		// commitする直前だと状態はObjectState.PERSISTENT_NONTRANSACTIONAL_DIRTY
		assertThat(JDOHelper.getObjectState(entity),
				is(equalTo(ObjectState.PERSISTENT_DIRTY)));
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(entity);
		} finally {
			out.close();
		}

		transaction.commit(); // ここでcommitする

		// commitすると状態が変わる
		assertThat(JDOHelper.getObjectState(entity),
				is(equalTo(ObjectState.HOLLOW_PERSISTENT_NONTRANSACTIONAL)));

		pm.close(); // PersistenceManagerを閉じる
		assertDeserialize(bytes.toByteArray());
	}

	/**
	 * クエリした結果をSerializeする。
	 * <p>
	 * {@link Query#closeAll()}する前にSerializeする。
	 * </p>
	 * 
	 * @throws IOException
	 */
	@Test(expected = NotSerializableException.class)
	public void serializeQueryResult_NG1() throws IOException {
		createParentA1();
		createParentA2();

		PersistenceManager pm = getFactory().getPersistenceManager();
		Query query = pm.newQuery(ParentA.class);
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute();
		assertThat(results.size(), is(equalTo(2)));

		// クエリを閉じずにSerializeする
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(results);
		} finally {
			out.close();
		}
		query.closeAll(); // クエリを閉じる
		pm.close(); // PersistenceManagerを閉じる
	}

	/**
	 * クエリした結果をSerializeする。
	 * <p>
	 * {@link Query#closeAll()}した後で、{@link PersistenceManager#close()}
	 * する前にSerializeする。
	 * </p>
	 * 
	 * @throws IOException
	 */
	@Test(expected = NotSerializableException.class)
	public void serializeQueryResult_NG2() throws IOException {
		createParentA1();
		createParentA2();

		PersistenceManager pm = getFactory().getPersistenceManager();
		Query query = pm.newQuery(ParentA.class);
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute();
		assertThat(results.size(), is(equalTo(2)));
		query.closeAll(); // クエリを閉じる

		// PersistenceManagerを閉じずにSerializeする
		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(results);
		} finally {
			out.close();
		}
		pm.close(); // PersistenceManagerを閉じる
	}

	/**
	 * クエリした結果をSerializeする。
	 * <p>
	 * {@link Query#closeAll()}、{@link PersistenceManager#close()}
	 * した後でSerializeする。
	 * </p>
	 * 
	 * @throws IOException
	 */
	@Test(expected = NotSerializableException.class)
	public void serializeQueryResult_NG3() throws IOException {
		createParentA1();
		createParentA2();

		PersistenceManager pm = getFactory().getPersistenceManager();
		Query query = pm.newQuery(ParentA.class);
		@SuppressWarnings("unchecked")
		List<ParentA> results = (List<ParentA>) query.execute();
		assertThat(results.size(), is(equalTo(2)));
		query.closeAll(); // クエリを閉じる
		pm.close(); // PersistenceManagerを閉じる

		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(results);
		} finally {
			out.close();
		}
	}

	/**
	 * クエリした結果を別のListに移し替えてSerializeする。
	 * <p>
	 * {@link Query#closeAll()}、{@link PersistenceManager#close()}
	 * した後でSerializeする。
	 * </p>
	 * 
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	@Test
	public void serializeQueryResult_OK() throws IOException,
			ClassNotFoundException {
		createParentA1();
		createParentA2();

		PersistenceManager pm = getFactory().getPersistenceManager();
		Query query = pm.newQuery(ParentA.class);
		@SuppressWarnings("unchecked")
		List<ParentA> _results = (List<ParentA>) query.execute();
		assertThat(_results.size(), is(equalTo(2)));
		List<ParentA> results = new ArrayList<ParentA>(_results);

		ByteArrayOutputStream bytes = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bytes);
		try {
			out.writeObject(results);
		} finally {
			out.close();
		}
		query.closeAll(); // クエリを閉じる
		pm.close(); // PersistenceManagerを閉じる

		// デシリアライズ
		@SuppressWarnings("unchecked")
		List<ParentA> deserialized = (List<ParentA>) new ObjectInputStream(
				new ByteArrayInputStream(bytes.toByteArray())).readObject();
		assertThat(deserialized.size(), is(equalTo(2)));
		ParentA entity2 = deserialized.get(0);
		assertThat(entity2.getChildA1(), notNullValue());
		assertThat(entity2.getChildA2list().size(), is(2));
		assertThat(entity2.getOtherAKey(), notNullValue());
		assertThat(entity2.getOtherBKeys().size(), is(2));
		assertThat(childA1_1.getKey(), notNullValue());
		assertThat(childA1_1.getKey().getId(), not((long) 0));
	}

	private void assertDeserialize(byte[] bytes) throws IOException,
			ClassNotFoundException {
		ParentA entity = (ParentA) new ObjectInputStream(
				new ByteArrayInputStream(bytes)).readObject();
		assertThat(entity.getChildA1(), notNullValue());
		assertThat(entity.getChildA1().getValue(), is("ChildA1_1"));
		assertThat(entity.getChildA2list().size(), is(2));
		assertThat(entity.getChildA2list().get(0).getValue(), is("ChildA2_1"));
		assertThat(entity.getChildA2list().get(1).getValue(), is("ChildA2_2"));
		assertThat(entity.getOtherAKey(), notNullValue());
		assertThat(entity.getOtherAKey().toString(), is(equalTo(otherA_1
				.getKey().toString())));
		assertThat(entity.getOtherBKeys().size(), is(2));
		assertThat(entity.getOtherBKeys().contains(otherB_1.getKey()), is(true));
		assertThat(entity.getOtherBKeys().contains(otherB_2.getKey()), is(true));
		System.out.println(ToStringBuilder.reflectionToString(entity));
	}

	/**
	 * 対象Entityが{@literal @Version(strategy = VersionStrategy.VERSION_NUMBER) }
	 * で修飾されていれば楽観的排他制御が働く。
	 * <p>
	 * バージョン番号は{@link JDOHelper#getVersion(Object)}で取得できる。 Object型で返ってくるが、
	 * {@link VersionStrategy.VERSION_NUMBER}を使っていれば数値に変換可能だと思われる。
	 * </p>
	 */
	@Test(expected = JDOOptimisticVerificationException.class)
	public void 楽観的排他制御の確認() {
		Key key = createParentA1().getKey();

		PersistenceManager pm1 = getFactory().getPersistenceManager();
		ParentA entity1 = pm1.getObjectById(ParentA.class, key);
		int oldVersion = Integer.parseInt(JDOHelper.getVersion(entity1)
				.toString());

		PersistenceManager pm2 = getFactory().getPersistenceManager();
		ParentA entity2 = pm2.getObjectById(ParentA.class, key);

		Transaction transaction1 = pm1.currentTransaction();
		try {
			transaction1.begin();
			entity1.setText(new Text("1"));
			pm1.makePersistent(entity1);
			transaction1.commit();
			assertThat(Integer.parseInt(JDOHelper.getVersion(entity1)
					.toString()), is(equalTo(oldVersion + 1)));
		} finally {
			if (transaction1.isActive()) {
				transaction1.rollback();
			}
			pm1.close();
		}
		ParentA updatedEntity = getFactory().getPersistenceManager()
				.getObjectById(ParentA.class, key);
		// 別のPersistenceManagerから取得するとversionが上がっている事を確認する。
		assertThat(Integer.parseInt(JDOHelper.getVersion(updatedEntity)
				.toString()), is(equalTo(oldVersion + 1)));

		entity2.setText(new Text("2"));
		Transaction transaction2 = pm2.currentTransaction();
		try {
			transaction2.begin();
			// entity1の更新前に取得したentity2のバージョン番号は、もちろん古い値。
			assertThat(Integer.parseInt(JDOHelper.getVersion(entity2)
					.toString()), is(equalTo(oldVersion)));
			pm2.makePersistent(entity2);
			transaction2.commit(); // JDOOptimisticVerificationException
		} finally {
			if (transaction2.isActive()) {
				transaction2.rollback();
			}
			pm2.close();
		}
	}

	/**
	 * 対象Entityが{@literal @Version(strategy = VersionStrategy.VERSION_NUMBER) }
	 * で修飾されていれば楽観的排他制御が働く。
	 * <p>
	 * んじゃ、{@link @Version}で修飾されたEntityだけど、Owned参照する子Entityが{@link @Version}で
	 * 修飾されていないとして、それらを子要素だけで更新した場合の排他制御はどうなるのか？
	 * </p>
	 * <p>
	 * →後勝ちになる。
	 * </p>
	 */
	@Test
	public void 子Entityの楽観的排他制御の確認() {
		Key key = createParentA1().getChildA1().getKey();

		PersistenceManager pm1 = getFactory().getPersistenceManager();
		ChildA1 entity1 = pm1.getObjectById(ChildA1.class, key);

		PersistenceManager pm2 = getFactory().getPersistenceManager();
		ChildA1 entity2 = pm2.getObjectById(ChildA1.class, key);

		Transaction transaction1 = pm1.currentTransaction();
		try {
			transaction1.begin();
			entity1.setValue("1");
			pm1.makePersistent(entity1);
			transaction1.commit();
		} finally {
			if (transaction1.isActive()) {
				transaction1.rollback();
			}
			pm1.close();
		}

		entity2.setValue("2");
		Transaction transaction2 = pm2.currentTransaction();
		try {
			transaction2.begin();
			pm2.makePersistent(entity2);
			transaction2.commit(); // JDOOptimisticVerificationException
		} finally {
			if (transaction2.isActive()) {
				transaction2.rollback();
			}
			pm2.close();
		}

		// 後勝ちになっているはず。
		PersistenceManager pm = getFactory().getPersistenceManager();
		ChildA1 updatedEntity = pm.getObjectById(ChildA1.class, key);
		assertThat(updatedEntity.getValue(), is(equalTo("2")));
		// 念のため親Entityからも確認する。
		ParentA updatedEntityParent = pm.getObjectById(ParentA.class, key
				.getParent());
		assertThat(updatedEntityParent.getChildA1().getValue(),
				is(equalTo("2")));
	}

	// 以下はテスト用のヘルパーメソッド

	private ParentA createParentA1() {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = createParentA1(pm);
		pm.close();
		return entity;
	}

	private ParentA createParentA1(PersistenceManager pm) {
		ParentA entity = new ParentA();
		entity.setText(new Text("ParentA1"));
		entity.setChildA1(childA1_1);
		entity.setChildA2list(Arrays.asList(new ChildA2[] { childA2_1,
				childA2_2 }));
		entity.setOtherAKey(otherA_1.getKey());
		Set<Key> otherBKeys = new HashSet<Key>();
		otherBKeys.add(otherB_1.getKey());
		otherBKeys.add(otherB_2.getKey());
		entity.setOtherBKeys(otherBKeys);

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		return entity;
	}

	private ParentA createParentA2() {
		PersistenceManager pm = getFactory().getPersistenceManager();
		ParentA entity = new ParentA();
		entity.setText(new Text("ParentA2"));
		entity.setChildA1(childA1_2);
		entity.setOtherAKey(otherA_2.getKey());

		Transaction transaction = pm.currentTransaction();
		try {
			transaction.begin();
			pm.makePersistent(entity);
			transaction.commit();
		} finally {
			if (transaction.isActive()) {
				transaction.rollback();
			}
		}
		pm.close();
		return entity;
	}

	private ChildA1 getParentA1ChildA(PersistenceManager pm) {
		Query query = pm.newQuery(ChildA1.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		ChildA1 childA1 = ((List<ChildA1>) query.execute(childA1_1.getKey()))
				.get(0);
		assertThat(childA1, notNullValue());
		assertThat(childA1.getValue(), is("ChildA1_1"));
		return childA1;
	}

	private ChildA1 getParentA2ChildA(PersistenceManager pm) {
		Query query = pm.newQuery(ChildA1.class);
		query.setFilter("key == pKey");
		query.declareParameters("java.lang.String pKey");
		@SuppressWarnings("unchecked")
		ChildA1 childA1 = ((List<ChildA1>) query.execute(childA1_2.getKey()))
				.get(0);
		assertThat(childA1, notNullValue());
		assertThat(childA1.getValue(), is("ChildA1_2"));
		return childA1;
	}

	private ParentA log1(Key key) {
		PersistenceManager pm = getFactory().getPersistenceManager();

		try {
			ParentA entity = pm.getObjectById(ParentA.class, key);
			System.out.println(ToStringBuilder.reflectionToString(entity));
			return entity;
		} finally {
			pm.close();
		}
	}
}
