/*
 * JBoss, Home of Professional Open Source
 * Copyright 2009 Red Hat Inc. and/or its affiliates and other
 * contributors as indicated by the @author tags. All rights reserved.
 * 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.infinispan.configuration;

import static org.infinispan.test.TestingUtil.INFINISPAN_END_TAG;
import static org.infinispan.test.TestingUtil.INFINISPAN_START_TAG;
import static org.infinispan.test.TestingUtil.INFINISPAN_START_TAG_40;
import static org.infinispan.test.TestingUtil.INFINISPAN_START_TAG_NO_SCHEMA;
import static org.infinispan.test.TestingUtil.withCacheManager;
import static org.testng.AssertJUnit.assertEquals;

import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.infinispan.Version;
import org.infinispan.config.ConfigurationException;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.ClusterCacheLoaderConfiguration;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.FileCacheStoreConfiguration;
import org.infinispan.configuration.cache.FileCacheStoreConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.ShutdownHookBehavior;
import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.eviction.EvictionThreadPolicy;
import org.infinispan.executors.DefaultExecutorFactory;
import org.infinispan.executors.DefaultScheduledExecutorFactory;
import org.infinispan.jmx.PerThreadMBeanServerLookup;
import org.infinispan.manager.EmbeddedCacheManager;
import org.infinispan.marshall.AdvancedExternalizer;
import org.infinispan.marshall.AdvancedExternalizerTest;
import org.infinispan.marshall.VersionAwareMarshaller;
import org.infinispan.remoting.transport.jgroups.JGroupsTransport;
import org.infinispan.test.AbstractInfinispanTest;
import org.infinispan.test.CacheManagerCallable;
import org.infinispan.test.TestingUtil;
import org.infinispan.test.fwk.TestCacheManagerFactory;
import org.infinispan.test.tx.TestLookup;
import org.infinispan.transaction.LockingMode;
import org.infinispan.transaction.lookup.GenericTransactionManagerLookup;
import org.infinispan.util.concurrent.IsolationLevel;
import org.testng.Assert;
import org.testng.annotations.Test;

@Test(groups = "unit", testName = "configuration.XmlFileParsingTest")
public class XmlFileParsingTest extends AbstractInfinispanTest {

   public void testOneLetterMode() throws Exception {
      String config = INFINISPAN_START_TAG +
            "   <default>\n" +
            "      <clustering mode=\"r\">\n" +
            "      </clustering>\n" +
            "   </default>\n" +
            TestingUtil.INFINISPAN_END_TAG;

      assertCacheMode(config);
   }

   private void assertCacheMode(String config) throws IOException {
      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)) {

         @Override
         public void call() {
            Assert.assertEquals(cm.getDefaultCacheConfiguration().clustering().cacheMode(), CacheMode.REPL_SYNC);
         }
      });

   }

   public void testShortMode() throws Exception {
      String config = INFINISPAN_START_TAG +
            "   <default>\n" +
            "      <clustering mode=\"repl\">\n" +
            "      </clustering>\n" +
            "   </default>\n" +
            TestingUtil.INFINISPAN_END_TAG;

      assertCacheMode(config);
   }

   @Test(expectedExceptions=FileNotFoundException.class)
   public void testFailOnUnexpectedConfigurationFile() throws IOException {
      TestCacheManagerFactory.fromXml( "does-not-exist.xml");
   }

   public void testDeprecatedNonsenseMode() throws Exception {
      // TODO When we remove the nonsense mode, this test should be deleted
      String config = INFINISPAN_START_TAG +
            "   <default>\n" +
            "      <clustering mode=\"raphael\">\n" +
            "      </clustering>\n" +
            "   </default>\n" +
            TestingUtil.INFINISPAN_END_TAG;

      assertCacheMode(config);
   }

   public void testNamedCacheFile() throws IOException {
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromXml("configs/named-cache-test.xml", true)) {
         @Override
         public void call() {
            assertNamedCacheFile(cm, false);
         }
      });
   }

   public void testOldNamedCacheFile() throws IOException {
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromXml("configs/named-cache-test-51.xml", true)) {
         @Override
         public void call() {
            assertNamedCacheFile(cm, true);
         }
      });
   }

   public void testNoNamedCaches() throws Exception {
      String config = INFINISPAN_START_TAG +
            "   <global>\n" +
            "      <transport clusterName=\"demoCluster\"/>\n" +
            "   </global>\n" +
            "\n" +
            "   <default>\n" +
            "      <clustering mode=\"replication\">\n" +
            "      </clustering>\n" +
            "   </default>\n" +
            TestingUtil.INFINISPAN_END_TAG;

      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)) {

         @Override
         public void call() {
            GlobalConfiguration globalCfg = cm.getCacheManagerConfiguration();

            assert globalCfg.transport().transport() instanceof JGroupsTransport;
            assert globalCfg.transport().clusterName().equals("demoCluster");

            Configuration cfg = cm.getDefaultCacheConfiguration();
            assert cfg.clustering().cacheMode() == CacheMode.REPL_SYNC;
         }

      });

   }

   @Test(expectedExceptions = ConfigurationException.class)
   public void testBackwardCompatibleInputCacheConfiguration() throws Exception {
      // Read 4.0 configuration file against 4.1 schema
      String config = INFINISPAN_START_TAG_40 +
            "   <global>\n" +
            "      <transport clusterName=\"demoCluster\"/>\n" +
            "   </global>\n" +
            "\n" +
            "   <default>\n" +
            "      <clustering mode=\"replication\">\n" +
            "      </clustering>\n" +
            "   </default>\n" +
            TestingUtil.INFINISPAN_END_TAG;

      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)));
   }

   public void testNoSchemaWithStuff() throws IOException {
      String config = INFINISPAN_START_TAG_NO_SCHEMA +
            "    <default>\n" +
            "        <locking concurrencyLevel=\"10000\" isolationLevel=\"REPEATABLE_READ\" />\n" +
            "    </default>\n" +
            INFINISPAN_END_TAG;

      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)) {

         @Override
         public void call() {
            Configuration cfg = cm.getDefaultCacheConfiguration();
            assert cfg.locking().concurrencyLevel() == 10000;
            assert cfg.locking().isolationLevel() == IsolationLevel.REPEATABLE_READ;
         }

      });

   }

   public void testPassivationOnDefaultEvictionOnNamed() throws Exception {

      //should not throw a warning id 152 for each named caches, just for default
      //https://issues.jboss.org/browse/ISPN-1938
      String config = INFINISPAN_START_TAG_NO_SCHEMA +
      "<default>\n" +
      "<transaction \n" +
      "transactionManagerLookupClass=\"org.infinispan.transaction.lookup.GenericTransactionManagerLookup\" \n" +
      "syncRollbackPhase=\"false\" syncCommitPhase=\"false\" useEagerLocking=\"false\" />\n" +
      "<loaders passivation=\"true\" shared=\"true\" preload=\"true\"> \n" +
      "<loader class=\"org.infinispan.loaders.file.FileCacheStore\" \n" +
      "fetchPersistentState=\"true\" purgerThreads=\"3\" purgeSynchronously=\"true\" \n" +
      "ignoreModifications=\"false\" purgeOnStartup=\"false\"> \n" +
      "</loader>\n" +
      "</loaders>\n" +
      "</default>\n" +
      "<namedCache name=\"Cache1\"> \n" +
      "<jmxStatistics enabled=\"true\" />\n" +
      "<eviction strategy=\"LIRS\" maxEntries=\"60000\" />\n" +
      "</namedCache> \n" +
      "<namedCache name=\"Cache2\"> \n" +
      "<jmxStatistics enabled=\"true\" />\n" +
      "<eviction strategy=\"LIRS\" maxEntries=\"60000\" />\n" +
      "</namedCache> \n" + INFINISPAN_END_TAG;
      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)));
   }

   public void testVersioning() throws Exception {
      String config = INFINISPAN_START_TAG_NO_SCHEMA +
            "<default>\n" +
            "<locking isolationLevel=\"REPEATABLE_READ\" lockAcquisitionTimeout=\"15000\" writeSkewCheck=\"true\"/>\n" +
            "<transaction transactionManagerLookupClass=\"org.infinispan.transaction.lookup.GenericTransactionManagerLookup\" transactionMode=\"TRANSACTIONAL\" lockingMode=\"OPTIMISTIC\"/>\n" +
            "<invocationBatching enabled=\"true\"/>\n" +
            "<versioning versioningScheme=\"SIMPLE\" enabled=\"true\"/>\n" +
            "<clustering mode=\"LOCAL\"/>\n" +
            "</default>\n" +
            INFINISPAN_END_TAG;
      InputStream is = new ByteArrayInputStream(config.getBytes());
      withCacheManager(new CacheManagerCallable(TestCacheManagerFactory.fromStream(is)) {
         @Override
         public void call() {
            cm.getDefaultCacheConfiguration();
         }
      });

   }

   private void assertNamedCacheFile(EmbeddedCacheManager cm, boolean deprecated) {
      final GlobalConfiguration gc = cm.getCacheManagerConfiguration();

      assert gc.asyncListenerExecutor().factory() instanceof DefaultExecutorFactory;
      assert gc.asyncListenerExecutor().properties().getProperty("maxThreads").equals("5");
      assert gc.asyncListenerExecutor().properties().getProperty("threadNamePrefix").equals("AsyncListenerThread");

      assert gc.asyncTransportExecutor().factory() instanceof DefaultExecutorFactory;
      // Should be 25, but it's overriden by the test cache manager factory
      assertEquals("6", gc.asyncTransportExecutor().properties().getProperty("maxThreads"));
      assert gc.asyncTransportExecutor().properties().getProperty("threadNamePrefix").equals("AsyncSerializationThread");

      assert gc.evictionScheduledExecutor().factory() instanceof DefaultScheduledExecutorFactory;
      assert gc.evictionScheduledExecutor().properties().getProperty("threadNamePrefix").equals("EvictionThread");

      assert gc.replicationQueueScheduledExecutor().factory() instanceof DefaultScheduledExecutorFactory;
      assert gc.replicationQueueScheduledExecutor().properties().getProperty("threadNamePrefix").equals("ReplicationQueueThread");

      assert gc.transport().transport() instanceof JGroupsTransport;
      assert gc.transport().clusterName().equals("infinispan-cluster");
      // Should be "Jalapeno" but it's overriden by the test cache manager factory
      assert gc.transport().nodeName().contains("Node");
      assert gc.transport().distributedSyncTimeout() == 50000;

      assert gc.shutdown().hookBehavior().equals(ShutdownHookBehavior.REGISTER);

      assert gc.serialization().marshaller() instanceof VersionAwareMarshaller;
      assert gc.serialization().version() == Version.getVersionShort("1.0");
      final Map<Integer, AdvancedExternalizer<?>> externalizers = gc.serialization().advancedExternalizers();
      assert externalizers.size() == 3;
      assert externalizers.get(1234) instanceof AdvancedExternalizerTest.IdViaConfigObj.Externalizer;
      assert externalizers.get(5678) instanceof AdvancedExternalizerTest.IdViaAnnotationObj.Externalizer;
      assert externalizers.get(3456) instanceof AdvancedExternalizerTest.IdViaBothObj.Externalizer;

      Configuration defaultCfg = cm.getDefaultCacheConfiguration();

      assert defaultCfg.locking().lockAcquisitionTimeout() == 1000;
      assert defaultCfg.locking().concurrencyLevel() == 100;
      assert defaultCfg.locking().isolationLevel() == IsolationLevel.READ_COMMITTED;
      if (!deprecated) {
         assertReaperAndTimeoutInfo(defaultCfg);
      }


      Configuration c = cm.getCacheConfiguration("transactional");
      assert !c.clustering().cacheMode().isClustered();
      assert c.transaction().transactionManagerLookup() instanceof GenericTransactionManagerLookup;
      assert c.transaction().useEagerLocking();
      assert c.transaction().eagerLockingSingleNode();
      assert !c.transaction().syncRollbackPhase();
      if (!deprecated) {
         assertReaperAndTimeoutInfo(defaultCfg);
      }

      c = cm.getCacheConfiguration("transactional2");
      assert c.transaction().transactionManagerLookup() instanceof TestLookup;
      assert c.transaction().cacheStopTimeout() == 10000;
      assert c.transaction().lockingMode().equals(LockingMode.PESSIMISTIC);
      assert !c.transaction().autoCommit();

      c = cm.getCacheConfiguration("syncRepl");

      assert c.clustering().cacheMode() == CacheMode.REPL_SYNC;
      assert !c.clustering().stateTransfer().fetchInMemoryState();
      assert c.clustering().sync().replTimeout() == 15000;

      c = cm.getCacheConfiguration("asyncRepl");

      assert c.clustering().cacheMode() == CacheMode.REPL_ASYNC;
      assert !c.clustering().async().useReplQueue();
      assert !c.clustering().async().asyncMarshalling();
      assert !c.clustering().stateTransfer().fetchInMemoryState();

      c = cm.getCacheConfiguration("asyncReplQueue");

      assert c.clustering().cacheMode() == CacheMode.REPL_ASYNC;
      assert c.clustering().async().useReplQueue();
      assert !c.clustering().async().asyncMarshalling();
      assert !c.clustering().stateTransfer().fetchInMemoryState();

      c = cm.getCacheConfiguration("txSyncRepl");

      assert c.transaction().transactionManagerLookup() instanceof GenericTransactionManagerLookup;
      assert c.clustering().cacheMode() == CacheMode.REPL_SYNC;
      assert !c.clustering().stateTransfer().fetchInMemoryState();
      assert c.clustering().sync().replTimeout() == 15000;

      c = cm.getCacheConfiguration("overriding");

      assert c.clustering().cacheMode() == CacheMode.LOCAL;
      assert c.locking().lockAcquisitionTimeout() == 20000;
      assert c.locking().concurrencyLevel() == 1000;
      assert c.locking().isolationLevel() == IsolationLevel.REPEATABLE_READ;
      assert !c.storeAsBinary().enabled();

      c = cm.getCacheConfiguration("storeAsBinary");
      assert c.storeAsBinary().enabled();

      c = cm.getCacheConfiguration("withFileStore");
      assert c.loaders().preload();
      assert !c.loaders().passivation();
      assert !c.loaders().shared();
      assert c.loaders().cacheLoaders().size() == 1;

      FileCacheStoreConfiguration loaderCfg = (FileCacheStoreConfiguration) c.loaders().cacheLoaders().get(0);

      assert loaderCfg.fetchPersistentState();
      assert loaderCfg.ignoreModifications();
      assert loaderCfg.purgeOnStartup();
      assertEquals("/tmp/FileCacheStore-Location", loaderCfg.location());
      assert loaderCfg.fsyncMode() == FileCacheStoreConfigurationBuilder.FsyncMode.PERIODIC;
      assert loaderCfg.fsyncInterval() == 2000;
      assert loaderCfg.singletonStore().pushStateTimeout() == 20000;
      assert loaderCfg.singletonStore().pushStateWhenCoordinator();
      assert loaderCfg.async().threadPoolSize() == 5;
      assert loaderCfg.async().flushLockTimeout() == 15000;
      assert loaderCfg.async().enabled();
      assert loaderCfg.async().modificationQueueSize() == 700;

      c = cm.getCacheConfiguration("withClusterLoader");
      assert c.loaders().cacheLoaders().size() == 1;
      ClusterCacheLoaderConfiguration clusterLoaderCfg = (ClusterCacheLoaderConfiguration) c.loaders().cacheLoaders().get(0);
      assert clusterLoaderCfg.remoteCallTimeout() == 15000;

      c = cm.getCacheConfiguration("withLoaderDefaults");
      loaderCfg = (FileCacheStoreConfiguration) c.loaders().cacheLoaders().get(0);
      assert loaderCfg.location().equals("/tmp/Another-FileCacheStore-Location");
      assert loaderCfg.fsyncMode() == FileCacheStoreConfigurationBuilder.FsyncMode.DEFAULT;

      c = cm.getCacheConfiguration("withouthJmxEnabled");
      assert !c.jmxStatistics().enabled();
      assert gc.globalJmxStatistics().enabled();
      assert gc.globalJmxStatistics().allowDuplicateDomains();
      assertEquals("funky_domain", gc.globalJmxStatistics().domain());
      assert gc.globalJmxStatistics().mbeanServerLookup() instanceof PerThreadMBeanServerLookup;

      c = cm.getCacheConfiguration("dist");
      assert c.clustering().cacheMode() == CacheMode.DIST_SYNC;
      assert c.clustering().l1().lifespan() == 600000;
      assert !deprecated || c.clustering().hash().rehashRpcTimeout() == 120000;
      assert c.clustering().stateTransfer().timeout() == 120000;
      assert c.clustering().l1().cleanupTaskFrequency() == 1200;
      assert c.clustering().hash().consistentHash() == null; // this is just an override.
      assert c.clustering().hash().numOwners() == 3;
      assert c.clustering().l1().enabled();

      c = cm.getCacheConfiguration("dist_with_vnodes");
      assert c.clustering().cacheMode() == CacheMode.DIST_SYNC;
      assert c.clustering().l1().lifespan() == 600000;
      assert !deprecated || c.clustering().hash().rehashRpcTimeout() == 120000;
      assert c.clustering().stateTransfer().timeout() == 120000;
      assert c.clustering().hash().consistentHash() == null; // this is just an override.
      assert c.clustering().hash().numOwners() == 3;
      assert c.clustering().l1().enabled();
      assert c.clustering().hash().numVirtualNodes() == 1;
      if (!deprecated) assert c.clustering().hash().numSegments() == 1000;

      c = cm.getCacheConfiguration("groups");
      assert c.clustering().hash().groups().enabled();
      assert c.clustering().hash().groups().groupers().size() == 1;
      assert c.clustering().hash().groups().groupers().get(0).getKeyType().equals(String.class);

      c = cm.getCacheConfiguration("chunkSize");
      assert c.clustering().stateTransfer().fetchInMemoryState();
      assert c.clustering().stateTransfer().timeout() == 120000;
      assert c.clustering().stateTransfer().chunkSize() == 1000;

      c = cm.getCacheConfiguration("cacheWithCustomInterceptors");
      assert !c.customInterceptors().interceptors().isEmpty();
      assert c.customInterceptors().interceptors().size() == 5;

      c = cm.getCacheConfiguration("evictionCache");
      assert c.eviction().maxEntries() == 5000;
      assert c.eviction().strategy().equals(EvictionStrategy.LRU);
      assert c.expiration().lifespan() == 60000;
      assert c.expiration().maxIdle() == 1000;
      assert c.eviction().threadPolicy() == EvictionThreadPolicy.PIGGYBACK;
      assert c.expiration().wakeUpInterval() == 500;

      c = cm.getCacheConfiguration("withDeadlockDetection");
      assert c.deadlockDetection().enabled();
      assert c.deadlockDetection().spinDuration() == 1221;
      assert c.clustering().cacheMode() == CacheMode.DIST_SYNC;

      c = cm.getCacheConfiguration("storeKeyValueBinary");
      assert c.storeAsBinary().enabled();
      assert c.storeAsBinary().storeKeysAsBinary();
      assert !c.storeAsBinary().storeValuesAsBinary();

      if (!deprecated) {
         Configuration withJDBCLoader = cm.getCacheConfiguration("withJDBCLoader");
         assert !withJDBCLoader.locking().supportsConcurrentUpdates();
      }
   }

   private void assertReaperAndTimeoutInfo(Configuration defaultCfg) {
      assertEquals(123, defaultCfg.transaction().reaperWakeUpInterval());
      assertEquals(3123, defaultCfg.transaction().completedTxTimeout());
   }

}