/*
 * 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.resource.connectionmanager;

import java.lang.reflect.Method;
import java.rmi.RemoteException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.ejb.EJBException;
import javax.ejb.RemoveException;
import javax.management.MBeanServer;
import javax.resource.ResourceException;

import org.jboss.ejb.Container;
import org.jboss.ejb.EnterpriseContext;
import org.jboss.ejb.EntityContainer;
import org.jboss.ejb.EntityEnterpriseContext;
import org.jboss.ejb.EntityPersistenceManager;
import org.jboss.ejb.GenericEntityObjectFactory;
import org.jboss.ejb.plugins.AbstractInterceptor;
import org.jboss.invocation.Invocation;
import org.jboss.invocation.InvocationType;
import org.jboss.logging.Logger;
import org.jboss.metadata.ApplicationMetaData;
import org.jboss.metadata.BeanMetaData;
import org.jboss.metadata.ResourceRefMetaData;
import org.jboss.mx.util.JMXExceptionDecoder;
import org.jboss.mx.util.MBeanServerLocator;

/**
 * CachedConnectionInterceptor
 *
 * @author <a href="mailto:d_jencks@users.sourceforge.net">David Jencks</a>
 * @author <a href="mailto:E.Guib@ceyoniq.com">Erwin Guib</a>
 * @author <a href="mailto:adrian@jboss.com">Adrian Brock</a>
 * @version $Revision$
 */
public class CachedConnectionInterceptor extends AbstractInterceptor implements EntityPersistenceManager
{
   private final CachedConnectionManager ccm;

   private final Logger log = Logger.getLogger(getClass());

   private Container container;

   private EntityPersistenceManager pm;

   // contains the JNDI names of unshareable resources
   private Set unsharableResources = new HashSet();

   public CachedConnectionInterceptor() throws Exception
   {
      try
      {
         MBeanServer server = MBeanServerLocator.locateJBoss();
         ccm = (CachedConnectionManager) server.getAttribute(CachedConnectionManagerMBean.OBJECT_NAME, "Instance");
      }
      catch (Exception e)
      {
         JMXExceptionDecoder.rethrow(e);
         throw e;
      }
   }

   public void start() throws Exception
   {
      log.debug("start called in CachedConnectionInterceptor");
      if (container == null)
      {
         log.warn("container is null, can't steal persistence manager");
         return;
      }
      if (container instanceof EntityContainer)
      {
         EntityContainer ec = (EntityContainer) container;

         if (ec.getPersistenceManager() == null)
         {
            log.info("no persistence manager in container!");
            return;
         }
         if (ec.getPersistenceManager() == this)
         {
            log.info(" persistence manager in container already set!");
            return;
         }
         pm = ec.getPersistenceManager();
         ec.setPersistenceManager(this);
      }

      // get the JNDI names for all resources that are referenced "Unshareable"
      BeanMetaData bmd = container.getBeanMetaData();
      ApplicationMetaData appMetaData = bmd.getApplicationMetaData();
      ResourceRefMetaData resRefMetaData;
      String jndiName;

      for (Iterator iter = bmd.getResourceReferences(); iter.hasNext();)
      {
         resRefMetaData = (ResourceRefMetaData) iter.next();
         jndiName = resRefMetaData.getJndiName();
         if (jndiName == null)
         {
            jndiName = appMetaData.getResourceByName(resRefMetaData.getResourceName());
         }
         if (jndiName != null && resRefMetaData.isShareable() == false)
         {
            int i = jndiName.indexOf(':');
            if (jndiName.charAt(i + 1) == '/')
            {
               i++;
            }
            unsharableResources.add(jndiName.substring(i + 1));
         }
      }

   }

   public void stop()
   {
      if (container != null && pm != null && ((EntityContainer) container).getPersistenceManager() == this)
      {
         ((EntityContainer) container).setPersistenceManager(pm);
         pm = null;
      }
      unsharableResources.clear();
   }

   public Object invoke(Invocation mi) throws Exception
   {
      Object key = ((EnterpriseContext) mi.getEnterpriseContext()).getInstance();
      try
      {
         ccm.pushMetaAwareObject(key, unsharableResources);
         try
         {
            return getNext().invoke(mi);
         }
         finally
         {
            ccm.popMetaAwareObject(unsharableResources);
         }
      }
      catch (ResourceException e)
      {
         InvocationType type = mi.getType();
         boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
         if (isLocal)
            throw new EJBException("Resource problem during invoke", e);
         else
            throw new RemoteException("Resource problem during invoke", e);
      }
   }

   public Object invokeHome(Invocation mi) throws Exception
   {
      EnterpriseContext ctx = (EnterpriseContext) mi.getEnterpriseContext();
      if (ctx == null)
         return getNext().invokeHome(mi);
      else
      {
         Object key = ctx.getInstance();
         try
         {
            ccm.pushMetaAwareObject(key, unsharableResources);
            try
            {
               return getNext().invokeHome(mi);
            }
            finally
            {
               ccm.popMetaAwareObject(unsharableResources);
            }
         }
         catch (ResourceException e)
         {
            InvocationType type = mi.getType();
            boolean isLocal = (type == InvocationType.LOCAL || type == InvocationType.LOCALHOME);
            if (isLocal)
               throw new EJBException("Resource problem during invokeHome", e);
            else
               throw new RemoteException("Resource problem during invokeHome", e);
         }
      }
   }

   public void setContainer(Container container)
   {
      this.container = container;
   }

   public Container getContainer()
   {
      return container;
   }

   public Object createBeanClassInstance() throws Exception
   {
      return pm.createBeanClassInstance();
   }

   public void createEntity(Method m, Object[] args, EntityEnterpriseContext instance) throws Exception
   {
      pm.createEntity(m, args, instance);
   }

   public void postCreateEntity(Method m, Object[] args, EntityEnterpriseContext instance) throws Exception
   {
      pm.postCreateEntity(m, args, instance);
   }

   public Object findEntity(Method finderMethod, Object[] args, EntityEnterpriseContext instance,
         GenericEntityObjectFactory factory) throws Exception
   {
      return pm.findEntity(finderMethod, args, instance, factory);
   }

   public Collection findEntities(Method finderMethod, Object[] args, EntityEnterpriseContext instance,
         GenericEntityObjectFactory factory) throws Exception
   {
      return pm.findEntities(finderMethod, args, instance, factory);
   }

   public void activateEntity(EntityEnterpriseContext instance) throws RemoteException
   {
      pm.activateEntity(instance);
   }

   public void loadEntity(EntityEnterpriseContext instance) throws RemoteException
   {
      pm.loadEntity(instance);
   }

   public boolean isStoreRequired(EntityEnterpriseContext instance) throws Exception
   {
      return pm.isStoreRequired(instance);
   }

   public boolean isModified(EntityEnterpriseContext ctx) throws Exception
   {
      return pm.isModified(ctx);
   }

   public void storeEntity(EntityEnterpriseContext ctx) throws RemoteException
   {
      Object key = ctx.getInstance();
      try
      {
         ccm.pushMetaAwareObject(key, unsharableResources);
         try
         {
            pm.storeEntity(ctx);
         }
         finally
         {
            ccm.popMetaAwareObject(unsharableResources);
         }
      }
      catch (ResourceException e)
      {
         throw new RemoteException("Could not store!: ", e);
      }
   }

   public void invokeEjbStore(EntityEnterpriseContext ctx) throws RemoteException
   {
      Object key = ctx.getInstance();
      try
      {
         ccm.pushMetaAwareObject(key, unsharableResources);
         try
         {
            pm.invokeEjbStore(ctx);
         }
         finally
         {
            ccm.popMetaAwareObject(unsharableResources);
         }
      }
      catch (ResourceException e)
      {
         throw new RemoteException("Could not store!: ", e);
      }
   }

   public void passivateEntity(EntityEnterpriseContext instance) throws RemoteException
   {
      pm.passivateEntity(instance);
   }

   public void removeEntity(EntityEnterpriseContext instance) throws RemoteException, RemoveException
   {
      pm.removeEntity(instance);
   }

   /**
    Return the real EntityPersistenceManager to which this interceptor delegates.
    @return the real EntityPersistenceManager
    */
   public EntityPersistenceManager getDelegatePersistenceManager()
   {
      return pm;
   }
}
