/*
* 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.test.dunit.cache.internal;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Properties;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.CacheException;
import org.apache.geode.cache.CacheExistsException;
import org.apache.geode.cache.CacheFactory;
import org.apache.geode.cache.CacheTransactionManager;
import org.apache.geode.cache.ExpirationAttributes;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.RegionExistsException;
import org.apache.geode.cache.TimeoutException;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientCacheFactory;
import org.apache.geode.cache.client.PoolManager;
import org.apache.geode.cache30.CacheSerializableRunnable;
import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.DistributionMessageObserver;
import org.apache.geode.internal.FileUtil;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalRegionArguments;
import org.apache.geode.internal.cache.LocalRegion;
import org.apache.geode.internal.cache.xmlcache.CacheCreation;
import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.test.dunit.Assert;
import org.apache.geode.test.dunit.IgnoredException;
import org.apache.geode.test.dunit.Invoke;
import org.apache.geode.test.dunit.LogWriterUtils;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.dunit.Wait;
import org.apache.geode.test.dunit.WaitCriterion;
import org.apache.geode.test.dunit.internal.JUnit4DistributedTestCase;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
/**
* This class is the base class for all distributed tests using JUnit 4 that require the creation of
* a {@link Cache}.
*
* TODO: make this class abstract when JUnit3CacheTestCase is deleted
*/
public abstract class JUnit4CacheTestCase extends JUnit4DistributedTestCase
implements CacheTestFixture {
private static final Logger logger = LogService.getLogger();
/**
* The Cache from which regions are obtained.
*
* <p>
* All references synchronized via {@code JUnit4CacheTestCase.class}.
*
* <p>
* Field is static so it doesn't get serialized with SerializableRunnable inner classes.
*/
private static Cache cache;
private final CacheTestFixture cacheTestFixture;
public JUnit4CacheTestCase() {
this(null);
}
JUnit4CacheTestCase(final CacheTestFixture cacheTestFixture) {
super(cacheTestFixture);
if (cacheTestFixture == null) {
this.cacheTestFixture = this;
} else {
this.cacheTestFixture = cacheTestFixture;
}
}
/**
* Creates the {@code Cache} for this test
*/
private final void createCache() {
createCache(false);
}
private final void createCache(final boolean client) {
createCache(client, null);
}
private final void createCache(final boolean client, final CacheFactory factory) {
synchronized (JUnit4CacheTestCase.class) {
try {
System.setProperty(
DistributionConfig.GEMFIRE_PREFIX + "DISABLE_DISCONNECT_DS_ON_CACHE_CLOSE", "true");
Cache newCache;
if (client) {
System.setProperty(DistributionConfig.GEMFIRE_PREFIX + "locators", "");
System.setProperty(DistributionConfig.GEMFIRE_PREFIX + MCAST_PORT, "0");
newCache = (Cache) new ClientCacheFactory(getSystem().getProperties()).create();
} else {
if (factory == null) {
newCache = CacheFactory.create(getSystem());
} else {
Properties props = getSystem().getProperties();
for (Map.Entry entry : props.entrySet()) {
factory.set((String) entry.getKey(), (String) entry.getValue());
}
newCache = factory.create();
}
}
cache = newCache;
} catch (CacheExistsException e) {
Assert.fail("the cache already exists", e); // TODO: remove error handling
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
Assert.fail("Checked exception while initializing cache??", ex);
} finally {
System.clearProperty(
DistributionConfig.GEMFIRE_PREFIX + "DISABLE_DISCONNECT_DS_ON_CACHE_CLOSE");
System.clearProperty(DistributionConfig.GEMFIRE_PREFIX + "locators");
System.clearProperty(DistributionConfig.GEMFIRE_PREFIX + MCAST_PORT);
}
}
}
/**
* Creates the {@code Cache} for this test that is not connected to other members.
*/
public final Cache createLonerCache() {
synchronized (JUnit4CacheTestCase.class) {
try {
System.setProperty(
DistributionConfig.GEMFIRE_PREFIX + "DISABLE_DISCONNECT_DS_ON_CACHE_CLOSE", "true");
Cache newCache = CacheFactory.create(getLonerSystem());
cache = newCache;
} catch (CacheExistsException e) {
Assert.fail("the cache already exists", e); // TODO: remove error handling
} catch (RuntimeException ex) {
throw ex;
} catch (Exception ex) {
Assert.fail("Checked exception while initializing cache??", ex);
} finally {
System.clearProperty(
DistributionConfig.GEMFIRE_PREFIX + "DISABLE_DISCONNECT_DS_ON_CACHE_CLOSE");
}
return cache;
}
}
/**
* Sets this test up with a {@code CacheCreation} as its cache. Any existing cache is closed.
* Whoever calls this must also call {@code finishCacheXml}.
*/
public static final synchronized void beginCacheXml() {
closeCache();
cache = new TestCacheCreation();
}
/**
* Finish what {@code beginCacheXml} started. It does this be generating a cache.xml file and then
* creating a real cache using that cache.xml.
*/
public final void finishCacheXml(final String name) {
synchronized (JUnit4CacheTestCase.class) {
File file = new File(name + "-cache.xml");
try {
PrintWriter pw = new PrintWriter(new FileWriter(file), true);
CacheXmlGenerator.generate(cache, pw);
pw.close();
} catch (IOException ex) {
Assert.fail("IOException during cache.xml generation to " + file, ex); // TODO: remove error
// handling
}
cache = null;
GemFireCacheImpl.testCacheXml = file;
try {
createCache();
} finally {
GemFireCacheImpl.testCacheXml = null;
}
}
}
/**
* Finish what {@code beginCacheXml} started. It does this be generating a cache.xml file and then
* creating a real cache using that cache.xml.
*/
public final void finishCacheXml(final String name, final boolean useSchema,
final String xmlVersion) {
synchronized (JUnit4CacheTestCase.class) {
File dir = new File("XML_" + xmlVersion);
dir.mkdirs();
File file = new File(dir, name + ".xml");
try {
PrintWriter pw = new PrintWriter(new FileWriter(file), true);
CacheXmlGenerator.generate(cache, pw, useSchema, xmlVersion);
pw.close();
} catch (IOException ex) {
Assert.fail("IOException during cache.xml generation to " + file, ex); // TODO: remove error
// handling
}
cache = null;
GemFireCacheImpl.testCacheXml = file;
try {
createCache();
} finally {
GemFireCacheImpl.testCacheXml = null;
}
}
}
/**
* Return a cache for obtaining regions, created lazily.
*/
public final Cache getCache() {
return getCache(false);
}
public final Cache getCache(final CacheFactory factory) {
return getCache(false, factory);
}
public final Cache getCache(final Properties properties) {
getSystem(properties);
return getCache();
}
public final Cache getCache(final boolean client) {
return getCache(client, null);
}
public final Cache getCache(final boolean client, final CacheFactory factory) {
synchronized (JUnit4CacheTestCase.class) {
final GemFireCacheImpl gemFireCache = GemFireCacheImpl.getInstance();
if (gemFireCache != null && !gemFireCache.isClosed()
&& gemFireCache.getCancelCriterion().isCancelInProgress()) {
Wait.waitForCriterion(new WaitCriterion() { // TODO: replace with Awaitility
@Override
public boolean done() {
return gemFireCache.isClosed();
}
@Override
public String description() {
return "waiting for cache to close";
}
}, 30 * 1000, 300, true);
}
if (cache == null || cache.isClosed()) {
cache = null;
createCache(client, factory);
}
if (client && cache != null) {
IgnoredException.addIgnoredException("java.net.ConnectException");
}
return cache;
}
}
/**
* Creates a client cache from the factory if one does not already exist.
*
* @since GemFire 6.5
*/
public final ClientCache getClientCache(final ClientCacheFactory factory) {
synchronized (JUnit4CacheTestCase.class) {
final GemFireCacheImpl gemFireCache = GemFireCacheImpl.getInstance();
if (gemFireCache != null && !gemFireCache.isClosed()
&& gemFireCache.getCancelCriterion().isCancelInProgress()) {
Wait.waitForCriterion(new WaitCriterion() { // TODO: replace with Awaitility
@Override
public boolean done() {
return gemFireCache.isClosed();
}
@Override
public String description() {
return "waiting for cache to close";
}
}, 30 * 1000, 300, true);
}
if (cache == null || cache.isClosed()) {
cache = null;
disconnectFromDS();
cache = (Cache) factory.create();
}
if (cache != null) {
IgnoredException.addIgnoredException("java.net.ConnectException");
}
return (ClientCache) cache;
}
}
/**
* Invokes {@link #getCache()} and casts the return to {@code GemFireCacheImpl}.
*/
public final GemFireCacheImpl getGemfireCache() { // TODO: remove?
return (GemFireCacheImpl) getCache();
}
public static final synchronized boolean hasCache() {
return cache != null;
}
/**
* Return current cache without creating one.
*/
public static final synchronized Cache basicGetCache() {
return cache;
}
/**
* Close the cache.
*/
public static final synchronized void closeCache() {
// Workaround for that fact that some classes are now extending
// CacheTestCase but not using it properly.
if (cache == null) {
cache = GemFireCacheImpl.getInstance();
}
try {
if (cache != null) {
try {
if (!cache.isClosed()) {
if (cache instanceof GemFireCacheImpl) {
CacheTransactionManager txMgr = ((GemFireCacheImpl) cache).getTxManager();
if (txMgr != null) {
if (txMgr.exists()) {
try {
// make sure we cleanup this threads txid stored in a thread local
txMgr.rollback();
} catch (Exception ignore) {
}
}
}
}
cache.close();
}
} finally {
cache = null;
}
} // cache != null
} finally {
// Make sure all pools are closed, even if we never
// created a cache
PoolManager.close(false);
}
}
/**
* Close the cache in all VMs.
*/
protected final void closeAllCache() {
closeCache();
Invoke.invokeInEveryVM(() -> closeCache());
}
@Override
public final void preTearDown() throws Exception {
preTearDownCacheTestCase();
tearDownCacheTestCase();
postTearDownCacheTestCase();
}
private final void tearDownCacheTestCase() {
remoteTearDown();
Invoke.invokeInEveryVM(() -> remoteTearDown());
}
@Override
public void preTearDownCacheTestCase() throws Exception {
if (this.cacheTestFixture != this) {
this.cacheTestFixture.preTearDownCacheTestCase();
}
}
@Override
public void postTearDownCacheTestCase() throws Exception {
if (this.cacheTestFixture != this) {
this.cacheTestFixture.postTearDownCacheTestCase();
}
}
/**
* Local destroy all root regions and close the cache.
*/
protected static final synchronized void remoteTearDown() {
try {
DistributionMessageObserver.setInstance(null);
destroyRegions(cache);
} finally {
try {
closeCache();
} finally {
try {
cleanDiskDirs();
} catch (Exception e) {
LogWriterUtils.getLogWriter().error("Error cleaning disk dirs", e);
}
}
}
}
/**
* Returns a region with the given name and attributes.
*/
public final Region createRegion(final String name, final RegionAttributes attributes)
throws CacheException {
return createRegion(name, "root", attributes);
}
/**
* Provide any internal region arguments, typically required when internal use (aka meta-data)
* regions are needed.
*
* @return internal arguments, which may be null. If null, then default InternalRegionArguments
* are used to construct the Region
*/
private final InternalRegionArguments getInternalRegionArguments() { // TODO: delete?
return null;
}
public final Region createRegion(final String name, final String rootName,
final RegionAttributes attributes) throws CacheException {
Region root = getRootRegion(rootName);
if (root == null) {
// don't put listeners on root region
RegionAttributes rootAttrs = attributes;
AttributesFactory fac = new AttributesFactory(attributes);
ExpirationAttributes expiration = ExpirationAttributes.DEFAULT;
// fac.setCacheListener(null);
fac.setCacheLoader(null);
fac.setCacheWriter(null);
fac.setPoolName(null);
fac.setPartitionAttributes(null);
fac.setRegionTimeToLive(expiration);
fac.setEntryTimeToLive(expiration);
fac.setRegionIdleTimeout(expiration);
fac.setEntryIdleTimeout(expiration);
rootAttrs = fac.create();
root = createRootRegion(rootName, rootAttrs);
}
InternalRegionArguments internalArgs = getInternalRegionArguments();
if (internalArgs == null) {
return root.createSubregion(name, attributes);
} else {
try {
LocalRegion lr = (LocalRegion) root;
return lr.createSubregion(name, attributes, internalArgs);
} catch (IOException ioe) {
AssertionError assErr = new AssertionError("unexpected exception");
assErr.initCause(ioe);
throw assErr;
} catch (ClassNotFoundException cnfe) {
AssertionError assErr = new AssertionError("unexpected exception");
assErr.initCause(cnfe);
throw assErr;
}
}
}
public final Region getRootRegion() {
return getRootRegion("root");
}
public final Region getRootRegion(final String rootName) {
return getCache().getRegion(rootName);
}
protected final Region createRootRegion(final RegionAttributes attributes)
throws RegionExistsException, TimeoutException {
return createRootRegion("root", attributes);
}
public final Region createRootRegion(final String rootName, final RegionAttributes attributes)
throws RegionExistsException, TimeoutException {
return getCache().createRegion(rootName, attributes);
}
public final Region createExpiryRootRegion(final String rootName,
final RegionAttributes attributes) throws RegionExistsException, TimeoutException {
System.setProperty(LocalRegion.EXPIRY_MS_PROPERTY, "true");
try {
return createRootRegion(rootName, attributes);
} finally {
System.clearProperty(LocalRegion.EXPIRY_MS_PROPERTY);
}
}
/**
* @deprecated Please use {@link IgnoredException#addIgnoredException(String)} instead.
*/
@Deprecated
public final CacheSerializableRunnable addExceptionTag1(final String exceptionStringToIgnore) { // TODO:
// delete
// this
// method
CacheSerializableRunnable addExceptionTag = new CacheSerializableRunnable("addExceptionTag") {
@Override
public void run2() {
getCache().getLogger().info(
"<ExpectedException action=add>" + exceptionStringToIgnore + "</ExpectedException>");
}
};
return addExceptionTag;
}
/**
* @deprecated Please use {@link IgnoredException#addIgnoredException(String)} instead.
*/
@Deprecated
public final CacheSerializableRunnable removeExceptionTag1(final String exceptionStringToIgnore) { // TODO:
// delete
// this
// method
CacheSerializableRunnable removeExceptionTag =
new CacheSerializableRunnable("removeExceptionTag") {
@Override
public void run2() throws CacheException {
getCache().getLogger().info("<ExpectedException action=remove>"
+ exceptionStringToIgnore + "</ExpectedException>");
}
};
return removeExceptionTag;
}
public static final File getDiskDir() {
int vmNum = VM.getCurrentVMNum();
File dir = new File("diskDir", "disk" + String.valueOf(vmNum)).getAbsoluteFile();
dir.mkdirs();
return dir;
}
/**
* Return a set of disk directories for persistence tests. These directories will be automatically
* cleaned up on test case closure.
*/
public static final File[] getDiskDirs() {
return new File[] {getDiskDir()};
}
public static final void cleanDiskDirs() throws IOException {
FileUtil.delete(getDiskDir());
Arrays.stream(new File(".").listFiles()).forEach(file -> deleteBACKUPDiskStoreFile(file));
}
private static void deleteBACKUPDiskStoreFile(final File file) {
if (file.getName().startsWith("BACKUPDiskStore-")) {
try {
FileUtil.delete(file);
} catch (IOException e) {
throw new RuntimeException("Unable to delete BACKUPDiskStore file", e);
}
}
}
/**
* Used to generate a cache.xml. Basically just a {@code CacheCreation} with a few more methods
* implemented.
*/
private static final class TestCacheCreation extends CacheCreation {
private boolean closed = false;
@Override
public void close() {
this.closed = true;
}
@Override
public boolean isClosed() {
return this.closed;
}
}
}