/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You 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.geode.internal.compression;

import static org.apache.geode.distributed.ConfigurationProperties.*;
import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Properties;

import org.junit.Test;
import org.junit.experimental.categories.Category;

import org.apache.geode.cache.Region;
import org.apache.geode.compression.Compressor;
import org.apache.geode.compression.SnappyCompressor;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.IgnoredException;
import org.apache.geode.test.dunit.LogWriterUtils;
import org.apache.geode.test.dunit.SerializableCallable;
import org.apache.geode.test.dunit.SerializableRunnable;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.cache.internal.JUnit4CacheTestCase;
import org.apache.geode.test.junit.categories.DistributedTest;

/**
 * Tests configured and badly configured cache.xml files with regards to compression.
 */
@Category(DistributedTest.class)
public class CompressionCacheConfigDUnitTest extends JUnit4CacheTestCase {

  /**
   * The name of our test region.
   */
  public static final String REGION_NAME = "compressedRegion";

  /**
   * Sample cache.xml with a recognized compressor.
   */
  private static final String GOOD_COMPRESSOR =
      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE cache PUBLIC \"-//GemStone Systems, Inc.//GemFire Declarative Cache 8.0//EN\" \"http://www.gemstone.com/dtd/cache8_0.dtd\">\n<cache lock-lease=\"120\" lock-timeout=\"60\" search-timeout=\"300\" is-server=\"true\" copy-on-read=\"false\">\n<region name=\"compressedRegion\">\n<region-attributes data-policy=\"replicate\" cloning-enabled=\"true\">\n<compressor>\n<class-name>org.apache.geode.compression.SnappyCompressor</class-name>\n</compressor>\n</region-attributes>\n</region>\n</cache>";

  /**
   * Sample cache.xml with an unrecognized compressor.
   */
  private static final String BAD_COMPRESSOR =
      "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE cache PUBLIC \"-//GemStone Systems, Inc.//GemFire Declarative Cache 8.0//EN\" \"http://www.gemstone.com/dtd/cache8_0.dtd\">\n<cache lock-lease=\"120\" lock-timeout=\"60\" search-timeout=\"300\" is-server=\"true\" copy-on-read=\"false\">\n<region name=\"compressedRegion\">\n<region-attributes data-policy=\"replicate\" cloning-enabled=\"true\">\n<compressor>\n<class-name>BAD_COMPRESSOR</class-name>\n</compressor>\n</region-attributes>\n</region>\n</cache>";

  /**
   * Asserts that a member is successfully initialized with a compressed region when a compressor is
   * included in the region attributes.
   */
  @Test
  public void testCreateCacheWithGoodCompressor() throws Exception {
    File cacheXml = createCacheXml(GOOD_COMPRESSOR);
    assertTrue(createCacheOnVM(getVM(0), cacheXml.getCanonicalPath()));
    assertCompressorOnVM(getVM(0), new SnappyCompressor(), REGION_NAME);
    cleanup(getVM(0));
    cacheXml.delete();
  }

  /**
   * Asserts that member initialization fails when an unrecognized compressor is declared in the
   * cache.xml.
   */
  @Test
  public void testCreateCacheWithBadCompressor() throws Exception {
    IgnoredException.addIgnoredException("Unable to load class BAD_COMPRESSOR");
    File cacheXml = createCacheXml(BAD_COMPRESSOR);
    IgnoredException expectedException =
        IgnoredException.addIgnoredException("While reading Cache XML file");
    try {
      assertFalse(createCacheOnVM(getVM(0), cacheXml.getCanonicalPath()));
    } finally {
      expectedException.remove();
      cacheXml.delete();
    }
  }

  /**
   * Asserts that a region is compressed using a given compressor.
   *
   * @param vm a peer.
   * @param compressor a compressor.
   * @param regionName a compressed region.
   */
  private void assertCompressorOnVM(final VM vm, final Compressor compressor,
      final String regionName) {
    vm.invoke(new SerializableRunnable() {
      @Override
      public void run() {
        Region<String, String> region = getCache().getRegion(regionName);
        assertNotNull(region);
        assertTrue(compressor.equals(((LocalRegion) region).getCompressor()));
      }
    });
  }

  /**
   * Creates a new Cache for a given VM using a cache.xml.
   *
   * @param vm a peer.
   * @param cacheXml a declaritive xml file.
   * @return true if the cache was created, false otherwise.
   */
  private boolean createCacheOnVM(final VM vm, final String cacheXml) {
    return (Boolean) vm.invoke(new SerializableCallable() {
      @Override
      public Object call() throws Exception {
        try {
          disconnectFromDS();
          Properties props = new Properties();
          props.setProperty(CACHE_XML_FILE, cacheXml);
          LogWriterUtils.getLogWriter()
              .info("<ExpectedException action=add>ClassNotFoundException</ExpectedException>");
          getSystem(props);
          assertNotNull(getCache());
          return Boolean.TRUE;
        } catch (Exception e) {
          LogWriterUtils.getLogWriter().error("Could not create the cache", e);
          return Boolean.FALSE;
        } finally {
          LogWriterUtils.getLogWriter()
              .info("<ExpectedException action=remove>ClassNotFoundException</ExpectedException>");
        }
      }
    });
  }

  /**
   * Creates a temporary cache.xml on the file system.
   *
   * @param contents cache.xml contents.
   * @return A File representing the created file.
   * @throws IOException something bad happened.
   */
  private File createCacheXml(String contents) throws IOException {
    File cacheXml = File.createTempFile("cache", "xml");
    PrintStream pstream = new PrintStream(cacheXml);
    pstream.print(contents);
    pstream.close();

    return cacheXml;
  }

  /**
   * Returns the VM for a given identifier.
   *
   * @param vm a virtual machine identifier.
   */
  private VM getVM(int vm) {
    return Host.getHost(0).getVM(vm);
  }

  /**
   * Removes created regions from a VM.
   *
   * @param vm the virtual machine to cleanup.
   */
  private void cleanup(final VM vm) {
    vm.invoke(new SerializableRunnable() {
      @Override
      public void run() {
        Region<String, String> region = getCache().getRegion(REGION_NAME);
        assertNotNull(region);
        region.destroyRegion();
        disconnectFromDS();
      }
    });
  }
}
