/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.ejb.plugins.cmp.jdbc.bridge;

import java.lang.reflect.Field;

import javax.ejb.EJBException;

import org.jboss.deployment.DeploymentException;
import org.jboss.ejb.EntityEnterpriseContext;

import org.jboss.ejb.plugins.cmp.jdbc.JDBCContext;
import org.jboss.ejb.plugins.cmp.jdbc.JDBCStoreManager;
import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCCMPFieldMetaData;


/**
 * JDBCCMP1xFieldBridge is a concrete implementation of JDBCCMPFieldBridge for
 * CMP version 1.x. Getting and setting of instance fields set the
 * corresponding field in bean instance.  Dirty checking is performed by
 * storing the current value in the entity persistence context when ever
 * setClean is called, and comparing current value to the original value.
 *
 * Life-cycle:
 *      Tied to the EntityBridge.
 *
 * Multiplicity:
 *      One for each entity bean cmp field.
 *
 * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a>
 * @author <a href="mailto:alex@jboss.org">Alex Loubyansky</a>
 * @version $Revision$
 */
public class JDBCCMP1xFieldBridge extends JDBCAbstractCMPFieldBridge
{
   private Field field;

   public JDBCCMP1xFieldBridge(JDBCStoreManager manager,
                               JDBCCMPFieldMetaData metadata)
      throws DeploymentException
   {
      super(manager, metadata);

      try
      {
         field = manager.getMetaData().getEntityClass().getField(getFieldName());
      }
      catch(NoSuchFieldException e)
      {
         // Non recoverable internal exception
         throw new DeploymentException("No field named '" + getFieldName() +
            "' found in entity class.");
      }
   }

   public Object getInstanceValue(EntityEnterpriseContext ctx)
   {
      FieldState fieldState = getFieldState(ctx);
      if(!fieldState.isLoaded())
      {
         throw new EJBException("CMP 1.1 field not loaded: " + getFieldName());
      }

      try
      {
         return field.get(ctx.getInstance());
      }
      catch(Exception e)
      {
         // Non recoverable internal exception
         throw new EJBException("Internal error getting instance field " +
            getFieldName(), e);
      }
   }

   public void setInstanceValue(EntityEnterpriseContext ctx, Object value)
   {
      try
      {
         field.set(ctx.getInstance(), value);
         FieldState fieldState = getFieldState(ctx);
         fieldState.setLoaded();
         fieldState.setCheckDirty();
      }
      catch(Exception e)
      {
         // Non recoverable internal exception
         throw new EJBException("Internal error setting instance field " +
            getFieldName(), e);
      }
   }

   public Object getLockedValue(EntityEnterpriseContext ctx)
   {
      throw new UnsupportedOperationException("Optimistic locking is not supported in CMP1.1.");
   }

   public void lockInstanceValue(EntityEnterpriseContext ctx)
   {
      // not supported
   }

   public boolean isLoaded(EntityEnterpriseContext ctx)
   {
      return getFieldState(ctx).isLoaded();
   }

   /**
    * Has the value of this field changes since the last time clean was called.
    */
   public boolean isDirty(EntityEnterpriseContext ctx)
   {
      // read only and primary key fields are never dirty
      if(isReadOnly() || isPrimaryKeyMember())
      {
         return false;
      }

      // has the value changes since setClean
      return isLoaded(ctx) && !stateFactory.isStateValid(getInstanceValue(ctx), getFieldState(ctx).originalValue);
   }

   /**
    * Mark this field as clean.
    * Saves the current state in context, so it can be compared when
    * isDirty is called.
    */
   public void setClean(EntityEnterpriseContext ctx)
   {
      FieldState fieldState = getFieldState(ctx);
      fieldState.originalValue = getInstanceValue(ctx);

      // update last read time
      if(isReadOnly())
      {
         fieldState.lastRead = System.currentTimeMillis();
      }
   }

   public boolean isReadTimedOut(EntityEnterpriseContext ctx)
   {
      // if we are read/write then we are always timed out
      if(!isReadOnly())
      {
         return true;
      }

      // if read-time-out is -1 then we never time out.
      if(getReadTimeOut() == -1)
      {
         return false;
      }

      long readInterval = System.currentTimeMillis() -
         getFieldState(ctx).lastRead;
      return readInterval >= getReadTimeOut();
   }

   public void resetPersistenceContext(EntityEnterpriseContext ctx)
   {
      if(isReadTimedOut(ctx))
      {
         JDBCContext jdbcCtx = (JDBCContext)ctx.getPersistenceContext();
         FieldState fieldState = (FieldState)jdbcCtx.getFieldState(jdbcContextIndex);
         if(fieldState != null)
            fieldState.reset();
      }
   }

   protected void setDirtyAfterGet(EntityEnterpriseContext ctx)
   {
      getFieldState(ctx).setCheckDirty();
   }

   private FieldState getFieldState(EntityEnterpriseContext ctx)
   {
      JDBCContext jdbcCtx = (JDBCContext)ctx.getPersistenceContext();
      FieldState fieldState = (FieldState)jdbcCtx.getFieldState(jdbcContextIndex);
      if(fieldState == null)
      {
         fieldState = new FieldState(jdbcCtx);
         jdbcCtx.setFieldState(jdbcContextIndex, fieldState);
      }
      return fieldState;
   }

   private class FieldState
   {
      private Object originalValue;
      private long lastRead = -1;
      private JDBCEntityBridge.EntityState entityState;

      public FieldState(JDBCContext jdbcContext)
      {
         this.entityState = jdbcContext.getEntityState();
      }

      public boolean isLoaded()
      {
         return entityState.isLoaded(tableIndex);
      }

      public void setLoaded()
      {
         entityState.setLoaded(tableIndex);
      }

      public void setCheckDirty()
      {
         entityState.setCheckDirty(tableIndex);
      }

      public void reset()
      {
         originalValue = null;
         lastRead = -1;
         entityState.resetFlags(tableIndex);
         log.debug("reset field state");
      }
   }
}
