/********************************************************************
 * 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;
import gov.va.med.fw.service.transaction.TransactionSynchronizationForCacheManager;

// 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();
	}

	// can be overridden
	protected boolean shouldCacheItem() {
		// this logic ensures that the "auto cache cleanup" mechanism is in place
		return TransactionSynchronizationManager.isActualTransactionActive() && TransactionSynchronizationManager.hasResource(
				TransactionSynchronizationForCacheManager.CACHE_AUTOCLEANUP_VIA_TRANSACTION_SYNCHRONIZATION);
	}
	
   /** 
    * 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(!shouldCacheItem()) {
    		if(logger.isDebugEnabled())
				logger.debug("Not caching entity with key [" + key +
						"] since either outside of Transaction scope or TransactionSynchronizationForCacheManager not registered");	    	  
      } else {     
	      String name = this.getCacheName();
	      if( name != null ) {
	    	  if(logger.isDebugEnabled())
	    		  logger.debug("[Cache name: " + name + "] - adding data of type: " + entity.getClass().getName());
	    	  
	         // 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;
   }
}