package appengine.util;

import java.io.Serializable;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.builder.ToStringBuilder;

import appengine.util.DatastoreServiceUtil.RetryCounterExceededException;

import com.google.appengine.api.datastore.DatastoreFailureException;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.DatastoreTimeoutException;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.Transaction;

/**
 * Transactionを使った処理を行うためのユーティリティクラス。
 * @author shin1ogawa
 */
public class DatastoreTransactionUtil {

	private DatastoreTransactionUtil() {
	}


	/**
	 * @author shin1ogawa
	 */
	public static interface InTransaction {

		/** Logger */
		Logger LOGGER = Logger.getLogger(InTransaction.class.getName());


		/**
		 * @return 処理の名称
		 */
		String getName();

		/**
		 * Transaction内で実行する処理。
		 * @param service
		 * @param transaction
		 */
		void run(DatastoreService service, Transaction transaction);

		/**
		 * リトライ対象ではない例外が発生したときの処理。
		 * @param th
		 * @param service
		 * @param transaction
		 */
		void onException(Throwable th, DatastoreService service, Transaction transaction);
	}

	/**
	 * {@link InTransaction}の抽象クラス。
	 * @author shin1ogawa
	 */
	public static abstract class AbstractInTransaction implements InTransaction, Serializable {

		private static final long serialVersionUID = 4192579138628412145L;

		protected String name = "no name";


		public String getName() {
			return name;
		}

		public void onException(Throwable th, DatastoreService service, Transaction transaction) {
			if (transaction != null) {
				LOGGER.log(Level.WARNING, "transaction id=" + transaction.getId()
						+ ", inTransaction=" + ToStringBuilder.reflectionToString(this), th);
			} else {
				LOGGER.log(Level.WARNING, "inTransaction="
						+ ToStringBuilder.reflectionToString(this), th);
			}
		}
	}

	/**
	 * Transaction内でEntityのCollectionを一括PUT操作する{@link InTransaction}.
	 * @author shin1ogawa
	 */
	public static class PutAllInTransaction extends AbstractInTransaction {

		private static final long serialVersionUID = 9100049929929955673L;

		final Collection<Entity> entities;


		/**
		 * the constructor.
		 * @param entities
		 * @category constructor
		 */
		public PutAllInTransaction(Collection<Entity> entities) {
			this.entities = entities;
		}

		public void run(DatastoreService service, Transaction transaction) {
			service.put(transaction, entities);
		}
	}


	/**
	 * トランザクション内で処理を実行する。
	 * <p>指定回数のリトライを行う。</p>
	 * @param runIn
	 * @see DatastoreTransactionUtil#runInTransaction(InTransaction, int)
	 */
	public static void runInTransaction(InTransaction runIn) {
		runInTransaction(runIn, 5);
	}

	/**
	 * トランザクション内で処理を実行する。
	 * <p>指定回数のリトライを行う。</p>
	 * @param runIn
	 * @param retryCount
	 */
	public static void runInTransaction(InTransaction runIn, int retryCount) {
		DatastoreService service = DatastoreServiceFactory.getDatastoreService();
		for (int i = 0; i < retryCount; i++) {
			Transaction transaction = service.beginTransaction();
			try {
				runIn.run(service, transaction);
				if (transaction.isActive()) {
					transaction.commit();
				}
				return;
			} catch (DatastoreTimeoutException e) {
				if (transaction.isActive()) {
					transaction.rollback();
				}
				continue;
			} catch (DatastoreFailureException e) {
				if (transaction.isActive()) {
					transaction.rollback();
				}
				continue;
			} catch (Exception e) {
				runIn.onException(e, service, transaction);
				return;
			}
		}
		throw new RetryCounterExceededException();
	}
}
