/*
 * $Header: /home/cvspublic/jakarta-slide/src/stores/org/apache/slide/store/mem/AbstractTransientStore.java,v 1.7 2004/12/15 16:50:34 ozeigermann Exp $
 * $Revision: 1.7 $
 * $Date: 2004/12/15 16:50:34 $
 *
 * ====================================================================
 *
 * Copyright 1999-2002 The Apache Software Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.apache.slide.store.mem;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.MessageFormat;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;

import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;

import org.apache.commons.transaction.memory.ConflictException;
import org.apache.commons.transaction.memory.TransactionalMapWrapper;
import org.apache.commons.transaction.memory.jca.MapXAResource;
import org.apache.commons.transaction.util.LoggerFacade;

import org.apache.slide.common.AbstractServiceBase;
import org.apache.slide.common.NamespaceAccessToken;
import org.apache.slide.common.ServiceAccessException;
import org.apache.slide.common.ServiceConnectionFailedException;
import org.apache.slide.common.ServiceDisconnectionFailedException;
import org.apache.slide.common.ServiceInitializationFailedException;
import org.apache.slide.common.ServiceParameterErrorException;
import org.apache.slide.common.ServiceParameterMissingException;
import org.apache.slide.common.ServiceResetFailedException;
import org.apache.slide.store.ConcurrencyConflictError;
import org.apache.slide.util.logger.Logger;
import org.apache.slide.util.logger.TxLogger;

/**
 * Base class for the stores that what to store its data transient in a
 * map.
 * 
 * <p>Implementations access the backing map only via {@link #put(Object, Object)},
 * {@link #get(Object)} and {@link #remove(Object)}.
 * 
 * <p>The backed map can be any class that is derived from 
 * {@link org.apache.commons.transaction.memory.TransactionalMapWrapper}.
 * 
 * <p>The class may be configured in the <code>domain.xml</code> using the
 * parameter <code>map-classname</code>. 
 * Default is {@link org.apache.commons.transaction.memory.OptimisticMapWrapper}.
 * 
 * 
 */
public abstract class AbstractTransientStore extends AbstractServiceBase {
   
   static final String MAP_IMPL_PARAMETER = "map-classname";
   static final String MAP_IMPL_PARAMETER_DEFAULT = 
      "org.apache.commons.transaction.memory.OptimisticMapWrapper";
   
   private Map parameters = null;
   private boolean isConnected = false;
   
   /**
    * The map containing the stored content.
    */
   private TransactionalMapWrapper store = null;

   /**
    * The XAResource to delegate all XA requests.
    */
   private MapXAResource xaResource = null; 

   // ----------------------------------------------------- XAResource Mathods
   
   public void start(Xid xid, int flags)
      throws XAException 
   {
      debug("start {0} {1}", xid, Integer.toString(flags));
      this.xaResource.start(xid, flags);
   }
   
   public void commit(Xid xid, boolean onePhase)
      throws XAException 
   {
      debug("commit {0} {1}", xid, onePhase ? "true" : "false");
      try {
         this.xaResource.commit(xid, onePhase);
      }
      catch (ConflictException e) {
          throw new ConcurrencyConflictError();
/*
         this.xaResource.rollback(xid);
         // TODO it would be great if we could throw something
         // that would (on the webdav layer) leads to a 409 Conflict
         // instead of 500 Internal Server Error
         throw new XAException(XAException.XA_RBOTHER); // ??
         */
      }
   }
   
   public int prepare(Xid xid)
      throws XAException 
   {
      debug("prepare {0}", xid);
      return this.xaResource.prepare(xid);   
   }

   public void end(Xid xid, int flags)
       throws XAException 
   {
      debug("end {0} {1}", xid, Integer.toString(flags));
      this.xaResource.end(xid, flags);
   }
   
   public void rollback(Xid xid)
       throws XAException 
   {
      debug("rollback {0}", xid);
      this.xaResource.rollback(xid);
   }
   
   public Xid[] recover(int flag)
      throws XAException 
   {
      debug("recover {0}", Integer.toString(flag));
      return this.xaResource.recover(flag);
   }

   public void forget(Xid xid)
       throws XAException 
   {
       debug("forget {0}", xid);
       this.xaResource.forget(xid);
   }
   
   public int getTransactionTimeout()
       throws XAException 
   {
       return this.xaResource.getTransactionTimeout();
   }

   public boolean setTransactionTimeout(int seconds)
       throws XAException
   {
       return this.xaResource.setTransactionTimeout(seconds);
   }
   
   public boolean isSameRM(XAResource xares)
       throws XAException 
   {
      return this == xares;
   }
   
   
   

   // ------------------------------------- Store accessors for implementations

   protected void put(Object key, Object value) {
      this.store.put(key, value);
   }
   
   protected Object get(Object key) {
      return this.store.get(key);
   }
   
   protected Object remove(Object key) {
      return this.store.remove(key);
   }
   
   // ------------------------------------------------ ContextTuple InnerClass
   
   public void setParameters(Hashtable parameters)
         throws ServiceParameterErrorException,
                ServiceParameterMissingException 
   {
      this.parameters = new HashMap(parameters);
   }
   
   protected String getParameter(String name) {
      if (this.parameters != null) {
         return (String)this.parameters.get(name);
      } else {
         throw new IllegalStateException("Parameter not yet set!");
      }
   }
   public void initialize(NamespaceAccessToken token)
      throws ServiceInitializationFailedException
   {
      super.initialize(token);
      
      TxLogger txLogger = new TxLogger(getLogger(), LogChannel());
      
      String param = (String)this.parameters.get(MAP_IMPL_PARAMETER);
      if (param == null) {
         param = MAP_IMPL_PARAMETER_DEFAULT;
      }
      try {
         info("Initializing {0} using {1}.", getClass().getName(), param);
         Map toBeWrapped = new HashMap();
         Class mapClass = Class.forName(param);
         
         try {
            Class[] ctorFormalArgs = { Map.class, LoggerFacade.class };
            Constructor ctor = mapClass.getConstructor(ctorFormalArgs);
            Object[] ctorArgs = { toBeWrapped, txLogger };
            this.store = (TransactionalMapWrapper)ctor.newInstance(ctorArgs);
         }
         catch (NoSuchMethodException e) {
            // try next
            try {
               Class[] ctorFormalArgs = { Map.class };
               Constructor ctor = mapClass.getConstructor(ctorFormalArgs);
               Object[] ctorArgs = { toBeWrapped };
               this.store = (TransactionalMapWrapper)ctor.newInstance(ctorArgs);
            }
            catch (NoSuchMethodException ee) {
               error("Initialization error: ", ee);
               throw new ServiceInitializationFailedException(this, ee);
            }
         }
      } 
      catch (ClassNotFoundException e) {
         error("Initialization error: ", e);
         throw new ServiceInitializationFailedException(this, e);
      } 
      catch (InstantiationException e) {
         error("Initialization error: ", e);
         throw new ServiceInitializationFailedException(this, e);
      } 
      catch (IllegalAccessException e) {
         error("Initialization error: ", e);
         throw new ServiceInitializationFailedException(this, e);
      }
      catch (InvocationTargetException e) {
         error("Initialization error: ", e);
         throw new ServiceInitializationFailedException(this, e);
      }
      catch (ClassCastException e) {
         error("Initialization error: ", e);
         throw new ServiceInitializationFailedException(this, 
               "class in parameter '" + MAP_IMPL_PARAMETER + "' must " +
                     "be derived from TransactionalMapWrapper");
      }
      
      this.xaResource = new MapXAResource(this.store, txLogger);
   }

   public void connect() throws ServiceConnectionFailedException {
      this.isConnected = true;
   }
   public void disconnect() throws ServiceDisconnectionFailedException {
      this.isConnected = false;
   }
   public void reset() throws ServiceResetFailedException {
   }
   public boolean isConnected() throws ServiceAccessException {
      return this.isConnected;
   }
   

   // -------------------------------------------------------------------

   
   protected String LogChannel() {  
      return LOG_CHANNEL;
   }
   protected void debug(String msg, Object o) {
     if (this.getLogger().isEnabled(Logger.DEBUG)) {
        Object[] args = { o };
        this.getLogger().log(MessageFormat.format(msg, args), 
                             LogChannel(), Logger.DEBUG);
     }
   }
   protected void debug(String msg, Object o1, Object o2) {
     if (this.getLogger().isEnabled(Logger.DEBUG)) {
        Object[] args = { o1, o2 };
        this.getLogger().log(MessageFormat.format(msg, args), 
                             LogChannel(), Logger.DEBUG);
     }
   }
   private void info(String msg, Object o1, Object o2) {
      if (this.getLogger().isEnabled(Logger.INFO)) {
         Object[] args = { o1, o2 };
         this.getLogger().log(MessageFormat.format(msg, args), 
                              LogChannel(), Logger.INFO);
      }
   }
   private void error(String msg, Throwable t) {
      if (this.getLogger().isEnabled(Logger.INFO)) {
         this.getLogger().log(msg, t, 
                              LogChannel(), Logger.INFO);
      }
   }
   
   /**
    * Empty enumeration.
    */
   protected static Enumeration EMPTY_ENUM = new Enumeration() {
      public boolean hasMoreElements() {
         return false;
      }
      public Object nextElement() {
         throw new NoSuchElementException();
      }
   };
   
   /**
    * Enumeration wrapper for an Iterator. 
    */
   protected static class IteratorEnum implements Enumeration {
      private Iterator iterator;
      public IteratorEnum(Iterator iterator){
         this.iterator = iterator;
      }
      public boolean hasMoreElements() {
         return this.iterator.hasNext();
      }
      public Object nextElement() {
         return this.iterator.next();
      }
   }
}
