/********************************************************************
 * Copyright © 2004 VHA. All rights reserved
 ********************************************************************/
// Package
package gov.va.med.fw.cache;

// Java classes
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.Validate;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import gov.va.med.fw.model.AbstractEntity;
import gov.va.med.fw.model.EntityKey;

// ESR classes

/**
 * Candidates to be cached are usually entities whose properties are populated
 * with data coming from multiple data sources.
 * 
 * <p>
 * If an entity is retrived from a database using Hibernate framework and all of
 * the entity's properties are mapped to table columns in a hibernate mapping
 * file, there is no need to manage the entity in this class since Hibernate
 * already provides its own caching in a hibernate session.
 * 
 * Project: Framework</br> Created on: 10:01:31 AM </br>
 * 
 * @author DNS   LEV
 */
public class EntityCacheManager extends AbstractCacheManager {

	/**
	 * An instance of serialVersionUID
	 */
	private static final long serialVersionUID = 1633957061242960133L;

	private static SecureRandom secureRandom = new SecureRandom();
	
	/**
	 * A default constructor
	 */
	public EntityCacheManager() {
		super();
	}

	/**
	 * Stores an item by the specific key in a thread-bound data cache
	 * 
	 * @param key
	 *            A unique key representing an entity to store
	 * @param entity
	 *            An entity to be stored in a thread-bound cache
	 */
	public void storeItem(AbstractEntity entity, Object key) {

		Validate.notNull(entity, "An entity must not be null");
		Validate.notNull(key, "A key must not be null");

		if (!TransactionSynchronizationManager.isActualTransactionActive()
				&& !this.shouldCacheOutsideTransaction()) {
			if (logger.isDebugEnabled())
				logger.debug("Not caching entity with key [" + key
						+ "] since outside of Transaction scope");
		} else {
			String name = this.getCacheName();
			if (name != null) {
				// Get a cache for the current transaction
				if (containsItem(name)) {
					Map cache = (Map) getItem(name);
					cache.put(key, entity);
				} else {
					synchronized (this) {
						// A current transaction doesn't have a cache yet,
						// create one
						Map cache = new HashMap();
						cache.put(key, entity);
						cacheItem(name, cache);
					}
				}
			}
		}
	}

	/**
	 * Returns an entity from a thread-bound data cache
	 * 
	 * @param key
	 *            A unique key representing an entity to store
	 * @return Returns an entity from a cache
	 */
	public AbstractEntity getItem(EntityKey key) {

		AbstractEntity entity = null;
		if (key != null) {
			String name = TransactionSynchronizationManager.getCurrentTransactionName();
			if (containsItem(name)) {
				Map cache = (Map) getItem(name);
				Object value = cache.get(key);
				entity = (value instanceof AbstractEntity) ? (AbstractEntity) value : null;
			}
		}
		return entity;
	}

	/**
	 * Returns a name of a cache which is usually a transaction name that is set
	 * in a transaction enity interceptor.
	 * 
	 * @return A cache name
	 */
	protected String getCacheName() {
		// A transaction name is set in an entity interceptor
		String name = TransactionSynchronizationManager.getCurrentTransactionName();
		if (!TransactionSynchronizationManager.isActualTransactionActive()) {
			//name = String.valueOf(Math.random());
			name = String.valueOf(secureRandom.nextInt());
			TransactionSynchronizationManager.setCurrentTransactionName(name);
		}
		return name;
	}
}