private void releaseInstance(Instance instance) {
    // Don't pool if the bean has been undeployed
    if (instance.beanContext.isDestroyed()) return;

    // verify the instance is not associated with a bean-managed transaction
    if (instance.getBeanTransaction() != null) {
      new IllegalStateException("Instance has an active bean-managed transaction");
    }

    // no longer in use
    instance.setInUse(false);

    if (instance.getTransaction() == null) {
      // return to cache
      cache.checkIn(instance.primaryKey);

      // no longer checked out
      checkedOutInstances.remove(instance.primaryKey);
    }
  }
  protected Object businessMethod(
      BeanContext beanContext,
      Object primKey,
      Class callInterface,
      Method callMethod,
      Object[] args,
      InterfaceType interfaceType)
      throws OpenEJBException {
    ThreadContext callContext = new ThreadContext(beanContext, primKey);
    ThreadContext oldCallContext = ThreadContext.enter(callContext);
    try {
      // Security check
      checkAuthorization(callMethod, interfaceType);

      // Start transaction
      TransactionPolicy txPolicy =
          createTransactionPolicy(
              callContext.getBeanContext().getTransactionType(callMethod, interfaceType),
              callContext);

      Object returnValue = null;
      Instance instance = null;
      try {
        // Obtain instance
        instance = obtainInstance(primKey, callContext);

        // Resume previous Bean transaction if there was one
        if (txPolicy instanceof BeanTransactionPolicy) {
          SuspendedTransaction suspendedTransaction = instance.getBeanTransaction();
          if (suspendedTransaction != null) {
            instance.setBeanTransaction(null);
            BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy;
            beanTxEnv.resumeUserTransaction(suspendedTransaction);
          }
        }

        // Register the entity managers
        registerEntityManagers(instance, callContext);
        // Register for synchronization callbacks
        registerSessionSynchronization(instance, callContext);

        // Setup for business invocation
        callContext.setCurrentOperation(Operation.BUSINESS);
        callContext.setCurrentAllowedStates(null);
        callContext.setInvokedInterface(callInterface);
        Method runMethod = beanContext.getMatchingBeanMethod(callMethod);
        callContext.set(Method.class, runMethod);

        // Initialize interceptor stack
        List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod);
        InterceptorStack interceptorStack =
            new InterceptorStack(
                instance.bean, runMethod, Operation.BUSINESS, interceptors, instance.interceptors);

        // Invoke
        returnValue = interceptorStack.invoke(args);
      } catch (Throwable e) {
        handleException(callContext, txPolicy, e);
      } finally {
        // Commit transaction
        afterInvoke(callContext, txPolicy, instance);
      }
      return returnValue;
    } finally {
      ThreadContext.exit(oldCallContext);
    }
  }
  protected Object removeEJBObject(
      BeanContext beanContext,
      Object primKey,
      Class callInterface,
      Method callMethod,
      Object[] args,
      InterfaceType interfaceType)
      throws OpenEJBException {
    if (primKey == null) throw new NullPointerException("primKey is null");

    ThreadContext callContext = new ThreadContext(beanContext, primKey);
    ThreadContext oldCallContext = ThreadContext.enter(callContext);
    try {
      // Security check
      checkAuthorization(callMethod, interfaceType);

      // If a bean managed transaction is active, the bean can not be removed
      if (interfaceType.isComponent()) {
        Instance instance = checkedOutInstances.get(primKey);

        /**
         * According to EJB 3.0 "4.4.4 Restrictions for Transactions" any remove methods from home
         * or component interfaces must not be allowed if the bean instance is in a transaction.
         * Unfortunately, the Java EE 5 TCK has tests that ignore the restrictions in 4.4.4 and
         * expect beans in transactions can be removed via their home or component interface. The
         * test to see if the bean instance implements javax.ejb.SessionBean is a workaround for
         * passing the TCK while the tests in question can be challenged or the spec can be
         * changed/updated.
         */
        if (instance != null && instance.bean instanceof javax.ejb.SessionBean) {
          throw new ApplicationException(
              new RemoveException("A stateful EJB enrolled in a transaction can not be removed"));
        }
      }

      // Start transaction
      TransactionPolicy txPolicy =
          createTransactionPolicy(
              callContext.getBeanContext().getTransactionType(callMethod, interfaceType),
              callContext);

      Object returnValue = null;
      boolean retain = false;
      Instance instance = null;
      Method runMethod = null;
      try {
        // Obtain instance
        instance = obtainInstance(primKey, callContext);

        // Resume previous Bean transaction if there was one
        if (txPolicy instanceof BeanTransactionPolicy) {
          // Resume previous Bean transaction if there was one
          SuspendedTransaction suspendedTransaction = instance.getBeanTransaction();
          if (suspendedTransaction != null) {
            instance.setBeanTransaction(null);
            BeanTransactionPolicy beanTxEnv = (BeanTransactionPolicy) txPolicy;
            beanTxEnv.resumeUserTransaction(suspendedTransaction);
          }
        }

        // Register the entity managers
        registerEntityManagers(instance, callContext);

        // Register for synchronization callbacks
        registerSessionSynchronization(instance, callContext);

        // Setup for remove invocation
        callContext.setCurrentOperation(Operation.REMOVE);
        callContext.setCurrentAllowedStates(null);
        callContext.setInvokedInterface(callInterface);
        runMethod = beanContext.getMatchingBeanMethod(callMethod);
        callContext.set(Method.class, runMethod);

        // Do not pass arguments on home.remove(remote) calls
        Class<?> declaringClass = callMethod.getDeclaringClass();
        if (declaringClass.equals(EJBHome.class) || declaringClass.equals(EJBLocalHome.class)) {
          args = new Object[] {};
        }

        // Initialize interceptor stack
        List<InterceptorData> interceptors = beanContext.getMethodInterceptors(runMethod);
        InterceptorStack interceptorStack =
            new InterceptorStack(
                instance.bean, runMethod, Operation.REMOVE, interceptors, instance.interceptors);

        // Invoke
        if (args == null) {
          returnValue = interceptorStack.invoke();
        } else {
          returnValue = interceptorStack.invoke(args);
        }
      } catch (InvalidateReferenceException e) {
        throw e;
      } catch (Throwable e) {
        if (interfaceType.isBusiness()) {
          retain = beanContext.retainIfExeption(runMethod);
          handleException(callContext, txPolicy, e);
        } else {
          try {
            handleException(callContext, txPolicy, e);
          } catch (ApplicationException ae) {
            // Don't throw application exceptions for non-business interface removes
          }
        }
      } finally {
        if (!retain) {
          try {
            callContext.setCurrentOperation(Operation.PRE_DESTROY);
            List<InterceptorData> callbackInterceptors = beanContext.getCallbackInterceptors();
            InterceptorStack interceptorStack =
                new InterceptorStack(
                    instance.bean,
                    null,
                    Operation.PRE_DESTROY,
                    callbackInterceptors,
                    instance.interceptors);
            interceptorStack.invoke();
          } catch (Throwable callbackException) {
            String logMessage =
                "An unexpected exception occured while invoking the preDestroy method on the removed Stateful SessionBean instance; "
                    + callbackException.getClass().getName()
                    + " "
                    + callbackException.getMessage();

            /* [1] Log the exception or error */
            logger.error(logMessage);

          } finally {
            callContext.setCurrentOperation(Operation.REMOVE);
          }

          // todo destroy extended persistence contexts
          discardInstance(callContext);
        }

        // Commit transaction
        afterInvoke(callContext, txPolicy, instance);
      }

      return returnValue;
    } finally {
      ThreadContext.exit(oldCallContext);
    }
  }