/*
* JBoss, Home of Professional Open Source
* Copyright 2006, 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.cache.statetransfer;

import junit.framework.TestCase;
import org.jboss.cache.Fqn;
import org.jboss.cache.PropertyConfigurator;
import org.jboss.cache.TreeCache;
import org.jboss.cache.TreeCacheMBean;
import org.jboss.cache.marshall.SelectedClassnameClassLoader;
import org.jboss.cache.xml.XmlHelper;
import org.w3c.dom.Element;

import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * Abstract superclass of the StateTransfer tests.
 * 
 * @author <a href="mailto://brian.stansberry@jboss.com">Brian Stansberry</a>
 * @version $Revision$
 */
public abstract class StateTransferTestBase extends TestCase
{
   public static final Fqn A_B = Fqn.fromString("/a/b");
   public static final Fqn A_C = Fqn.fromString("/a/c");
   public static final Fqn A_D = Fqn.fromString("/a/d");
   public static final String JOE = "JOE";
   public static final String BOB = "BOB";
   public static final String JANE = "JANE";
   public static final Integer TWENTY = new Integer(20);
   public static final Integer FORTY = new Integer(40);

   protected Map caches;
   private ClassLoader orig_TCL;
   
   
   protected abstract String getReplicationVersion();

   protected TreeCacheMBean createCache(String cacheID, 
                                        boolean sync, 
                                        boolean useMarshalling, 
                                        boolean useCacheLoader) 
      throws Exception
   {
      return createCache(cacheID, sync, useMarshalling, useCacheLoader, false, true);
   }

   protected TreeCacheMBean createCache(String cacheID, 
                                        boolean sync, 
                                        boolean useMarshalling, 
                                        boolean useCacheLoader, 
                                        boolean cacheLoaderAsync, 
                                        boolean startCache) 
      throws Exception
   {
      if (caches.get(cacheID) != null)
         throw new IllegalStateException(cacheID + " already created");
      
      TreeCacheMBean tree=new TreeCache();
      PropertyConfigurator config=new PropertyConfigurator();
      String configFile = sync ? "META-INF/replSync-service.xml"
                               : "META-INF/replAsync-service.xml";
      config.configure(tree, configFile); // read in generic replAsync xml
      tree.setClusterName("VersionedTestBase");
      tree.setReplicationVersion(getReplicationVersion());
      // Use a long timeout to facilitate setting debugger breakpoints
      tree.setInitialStateRetrievalTimeout(60000);
      if (useMarshalling) {
         tree.setUseRegionBasedMarshalling(true);
         tree.setInactiveOnStartup(true);
      }
      if (useCacheLoader) {
         configureCacheLoader(tree, cacheID, useMarshalling, cacheLoaderAsync);
      }
      
      configureMultiplexer(tree);
      
      // Put the cache in the map before starting, so if it fails in
      // start it can still be destroyed later
      caches.put(cacheID, tree);
      
      if (startCache)
      {
         startCache(tree);
      }
      
      return tree;
   }
   
   /**
    * Provides a hook for multiplexer integration. This default implementation
    * is a no-op; subclasses that test mux integration would override
    * to integrate the given cache with a multiplexer.
    *
    * param cache a cache that has been configured but not yet created.
    */
   protected void configureMultiplexer(TreeCacheMBean cache) throws Exception
   {
      // default does nothing
   }
   
   /**
    * Provides a hook to check that the cache's channel came from the
    * multiplexer, or not, as expected.  This default impl asserts that
    * the channel did not come from the multiplexer.
    * 
    * @param cache a cache that has already been started
    */
   protected void validateMultiplexer(TreeCacheMBean cache)
   {
      assertFalse("Cache is not using multiplexer", cache.isUsingMultiplexer());
   }

   protected void startCache(TreeCacheMBean cache) throws Exception
   {
      cache.createService();
      cache.startService();
      
      validateMultiplexer(cache);
   }

   protected void configureCacheLoader(TreeCacheMBean cache, 
                                       String cacheID, 
                                       boolean useExtended,
                                       boolean async) 
      throws Exception
   {
      String tmp_location = getTempLocation(cacheID);
      
      // Do cleanup in case it failed before
      File file = new File(tmp_location);      
      cleanFile(file);
      
      file.mkdir();
      
      tmp_location = escapeWindowsPath(tmp_location);
      
      if (useExtended)
         cache.setCacheLoaderConfiguration(getCacheLoaderConfig("org.jboss.cache.loader.FileExtendedCacheLoader", tmp_location, async));
      else
         cache.setCacheLoaderConfiguration(getCacheLoaderConfig("org.jboss.cache.loader.FileCacheLoader", tmp_location, async));
   }

   protected Element getCacheLoaderConfig(String cl, String loc, boolean async) throws Exception
   {
      String xml = "            <config>\n" +
                   "                \n" +
                   "                <passivation>false</passivation>\n" +
                   "                <preload></preload>\n" +
                   "\n" +
                   "                <cacheloader>\n" +
                   "                    <class>" + cl + "</class>\n" +
                   "                    <properties>\n" +
                   "                        location=" + loc + "\n" +
                   "                    </properties>\n" +
                   "                    <async>" + async + "</async>\n" +
                   "                    <fetchPersistentState>true</fetchPersistentState>\n" +
                   "                    <ignoreModifications>false</ignoreModifications>\n" +
                   "                </cacheloader>\n" +
                   "                \n" +
                   "            </config>";
      return XmlHelper.stringToElement(xml);
   }

   protected String getTempLocation(String cacheID)
   {
      String tmp_location=System.getProperty("java.io.tmpdir", "c:\\tmp");
      File file = new File(tmp_location);
      file = new File(file, cacheID);
      return file.getAbsolutePath();
   }

   protected String escapeWindowsPath(String path)
   {
      if ('/' == File.separatorChar)
         return path;
      
      char[] chars = path.toCharArray();
      StringBuffer sb = new StringBuffer();
      for (int i = 0; i < chars.length; i++)
      {
         if (chars[i] == '\\')
            sb.append('\\');
         sb.append(chars[i]);
      }
      return sb.toString();
   }

    protected void setUp() throws Exception
    {
       super.setUp();

       caches = new HashMap();

       // Save the TCL in case a test changes it
       orig_TCL = Thread.currentThread().getContextClassLoader();
    }

   protected void tearDown() throws Exception
   {
      super.tearDown();
      
      // Restore the TCL in case a test changed it
      Thread.currentThread().setContextClassLoader(orig_TCL);
      
      Set keys = caches.keySet();
      String[] cacheIDs = new String[keys.size()];
      cacheIDs = (String[]) keys.toArray(cacheIDs);
      for (int i = 0; i < cacheIDs.length; i++)
      {
         stopCache((TreeCache) caches.get(cacheIDs[i]));
         File file = new File(getTempLocation(cacheIDs[i]));
         cleanFile(file);
      }
   }

   protected void stopCache(TreeCache cache)
   {
      if (cache != null)
      {
         try {
            cache.stopService();
            cache.destroyService();
         }
         catch (Exception e) {
            System.out.println("Exception stopping cache " + e.getMessage());
            e.printStackTrace(System.out);
         }
      }
   }

   protected void cleanFile(File file)
   {
      File[] children = file.listFiles();
      if (children != null)
      {
         for (int i = 0; i < children.length; i++) {
            cleanFile(children[i]);
         }
      }
      
      if (file.exists())
         file.delete();
      if (file.exists())
         file.deleteOnExit();
   }

   protected ClassLoader getClassLoader() throws Exception
   {
      String[] includesClasses = { "org.jboss.cache.marshall.Person",
                                   "org.jboss.cache.marshall.Address" };
      String [] excludesClasses = {};
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return new SelectedClassnameClassLoader(includesClasses, excludesClasses, cl);
   }

   protected ClassLoader getNotFoundClassLoader() throws Exception
   {
      String[] notFoundClasses = { "org.jboss.cache.marshall.Person",
                                   "org.jboss.cache.marshall.Address" };
      ClassLoader cl = Thread.currentThread().getContextClassLoader();
      return new SelectedClassnameClassLoader(null, null, notFoundClasses, cl);
   }

}
