/*
* 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.management.internal.cli.commands;
import org.apache.geode.cache.*;
import org.apache.geode.cache.query.Index;
import org.apache.geode.cache.query.IndexStatistics;
import org.apache.geode.cache.query.IndexType;
import org.apache.geode.cache.query.SelectResults;
import org.apache.geode.internal.lang.MutableIdentifiable;
import org.apache.geode.internal.lang.ObjectUtils;
import org.apache.geode.internal.lang.StringUtils;
import org.apache.geode.management.cli.Result;
import org.apache.geode.management.internal.cli.domain.IndexDetails;
import org.apache.geode.management.internal.cli.i18n.CliStrings;
import org.apache.geode.test.dunit.Host;
import org.apache.geode.test.dunit.SerializableRunnable;
import org.apache.geode.test.dunit.SerializableRunnableIF;
import org.apache.geode.test.dunit.VM;
import org.apache.geode.test.junit.categories.DistributedTest;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import static org.apache.geode.test.dunit.Assert.*;
import static org.apache.geode.test.dunit.LogWriterUtils.getDUnitLogLevel;
import static org.apache.geode.test.dunit.LogWriterUtils.getLogWriter;
import static org.apache.geode.distributed.ConfigurationProperties.*;
/**
* The ListIndexCommandDUnitTest class is distributed test suite of test cases for testing the
* index-based GemFire shell (Gfsh) commands.
*
* @see org.apache.geode.management.internal.cli.commands.CliCommandTestBase
* @see org.apache.geode.management.internal.cli.commands.IndexCommands
* @since GemFire 7.0
*/
@SuppressWarnings("unused")
@Category(DistributedTest.class)
public class ListIndexCommandDUnitTest extends CliCommandTestBase {
private static final int DEFAULT_REGION_INITIAL_CAPACITY = 10000;
private final AtomicLong idGenerator = new AtomicLong(0l);
@Override
public final void postSetUpCliCommandTestBase() throws Exception {
setUpJmxManagerOnVm0ThenConnect(null);
setupGemFire();
}
private static String toString(final Result result) {
assert result != null : "The Result object from the command execution cannot be null!";
final StringBuilder buffer = new StringBuilder(System.getProperty("line.separator"));
while (result.hasNextLine()) {
buffer.append(result.nextLine());
buffer.append(System.getProperty("line.separator"));
}
return buffer.toString();
}
private Index createIndex(final String name, final String indexedExpression,
final String fromClause) {
return createIndex(name, IndexType.FUNCTIONAL, indexedExpression, fromClause);
}
private Index createIndex(final String name, final IndexType type, final String indexedExpression,
final String fromClause) {
return new IndexAdapter(name, type, indexedExpression, fromClause);
}
private Peer createPeer(final VM vm, final Properties distributedSystemProperties,
final RegionDefinition... regions) {
final Peer peer = new Peer(vm, distributedSystemProperties);
peer.add(regions);
return peer;
}
private RegionDefinition createRegionDefinition(final String regionName,
final Class<?> keyConstraint, final Class<?> valueConstraint, final Index... indexes) {
final RegionDefinition regionDefinition =
new RegionDefinition(regionName, keyConstraint, valueConstraint);
regionDefinition.add(indexes);
return regionDefinition;
}
private void setupGemFire() throws Exception {
final Host host = Host.getHost(0);
final VM vm1 = host.getVM(1);
final VM vm2 = host.getVM(2);
final Peer peer1 = createPeer(vm1, createDistributedSystemProperties("consumerServer"),
createRegionDefinition("consumers", Long.class, Consumer.class,
createIndex("cidIdx", IndexType.PRIMARY_KEY, "id", "/consumers"),
createIndex("cnameIdx", "name", "/consumers")));
final Peer peer2 =
createPeer(vm2, createDistributedSystemProperties("producerServer"), createRegionDefinition(
"producers", Long.class, Producer.class, createIndex("pidIdx", "id", "/producers")));
createRegionWithIndexes(peer1);
createRegionWithIndexes(peer2);
loadConsumerData(peer1, 10000);
loadProducerData(peer2, 10000);
}
private Properties createDistributedSystemProperties(final String gemfireName) {
final Properties distributedSystemProperties = new Properties();
distributedSystemProperties.setProperty(LOG_LEVEL, getDUnitLogLevel());
distributedSystemProperties.setProperty(NAME, gemfireName);
return distributedSystemProperties;
}
private void createRegionWithIndexes(final Peer peer) throws Exception {
peer.run(new SerializableRunnable(
String.format("Creating Regions with Indexes on GemFire peer (%1$s).", peer.getName())) {
public void run() {
// create the GemFire distributed system with custom configuration properties...
getSystem(peer.getConfiguration());
final Cache cache = getCache();
final RegionFactory regionFactory = cache.createRegionFactory();
for (RegionDefinition regionDefinition : peer) {
regionFactory.setDataPolicy(DataPolicy.REPLICATE);
regionFactory.setIndexMaintenanceSynchronous(true);
regionFactory.setInitialCapacity(DEFAULT_REGION_INITIAL_CAPACITY);
regionFactory.setKeyConstraint(regionDefinition.getKeyConstraint());
regionFactory.setScope(Scope.DISTRIBUTED_NO_ACK);
regionFactory.setStatisticsEnabled(true);
regionFactory.setValueConstraint(regionDefinition.getValueConstraint());
final Region region = regionFactory.create(regionDefinition.getRegionName());
String indexName = null;
try {
for (Index index : regionDefinition) {
indexName = index.getName();
if (IndexType.PRIMARY_KEY.equals(index.getType())) {
cache.getQueryService().createKeyIndex(indexName, index.getIndexedExpression(),
region.getFullPath());
} else {
cache.getQueryService().createIndex(indexName, index.getIndexedExpression(),
region.getFullPath());
}
}
} catch (Exception e) {
getLogWriter().error(
String.format("Error occurred creating Index (%1$s) on Region (%2$s) - (%3$s)",
indexName, region.getFullPath(), e.getMessage()));
}
}
}
});
}
private void loadConsumerData(final Peer peer, final int operationsTotal) throws Exception {
peer.run(new SerializableRunnable("Load /consumers Region with data") {
public void run() {
final Cache cache = getCache();
final Region<Long, Consumer> consumerRegion = cache.getRegion("/consumers");
final Random random = new Random(System.currentTimeMillis());
int count = 0;
final List<Proxy> proxies = new ArrayList<Proxy>();
Consumer consumer;
Proxy proxy;
while (count++ < operationsTotal) {
switch (CrudOperation.values()[random.nextInt(CrudOperation.values().length)]) {
case RETRIEVE:
if (!proxies.isEmpty()) {
proxy = proxies.get(random.nextInt(proxies.size()));
consumer = query(consumerRegion, "id = " + proxy.getId() + "l"); // works
// consumer = query(consumerRegion, "Id = " + proxy.getId()); // works
// consumer = query(consumerRegion, "id = " + proxy.getId()); // does not work
proxy.setUnitsSnapshot(consumer.getUnits());
break;
}
case UPDATE:
if (!proxies.isEmpty()) {
proxy = proxies.get(random.nextInt(proxies.size()));
consumer = query(consumerRegion, "Name = " + proxy.getName());
consumer.consume();
break;
}
case CREATE:
default:
consumer = new Consumer(idGenerator.incrementAndGet());
proxies.add(new Proxy(consumer));
consumerRegion.put(consumer.getId(), consumer);
assertTrue(consumerRegion.containsKey(consumer.getId()));
assertTrue(consumerRegion.containsValueForKey(consumer.getId()));
assertSame(consumer, consumerRegion.get(consumer.getId()));
}
}
}
});
}
private void loadProducerData(final Peer peer, final int operationsTotal) throws Exception {
peer.run(new SerializableRunnable("Load /producers Region with data") {
public void run() {
final Cache cache = getCache();
final Region<Long, Producer> producerRegion = cache.getRegion("/producers");
final Random random = new Random(System.currentTimeMillis());
int count = 0;
final List<Proxy> proxies = new ArrayList<Proxy>();
Producer producer;
Proxy proxy;
while (count++ < operationsTotal) {
switch (CrudOperation.values()[random.nextInt(CrudOperation.values().length)]) {
case RETRIEVE:
if (!proxies.isEmpty()) {
proxy = proxies.get(random.nextInt(proxies.size()));
producer = query(producerRegion, "Id = " + proxy.getId());
proxy.setUnitsSnapshot(producer.getUnits());
break;
}
case UPDATE:
if (!proxies.isEmpty()) {
proxy = proxies.get(random.nextInt(proxies.size()));
producer = query(producerRegion, "Id = " + proxy.getId());
producer.produce();
break;
}
case CREATE:
default:
producer = new Producer(idGenerator.incrementAndGet());
proxies.add(new Proxy(producer));
producerRegion.put(producer.getId(), producer);
assertTrue(producerRegion.containsKey(producer.getId()));
assertTrue(producerRegion.containsValueForKey(producer.getId()));
assertSame(producer, producerRegion.get(producer.getId()));
}
}
}
});
}
@SuppressWarnings("unchecked")
private <T extends Comparable<T>, B extends AbstractBean<T>> B query(final Cache cache,
final String queryString) {
try {
getLogWriter().info(String.format("Running Query (%1$s) in GemFire...", queryString));
final SelectResults<B> results =
(SelectResults<B>) cache.getQueryService().newQuery(queryString).execute();
getLogWriter()
.info(String.format("Running Query (%1$s) in GemFire returned (%2$d) result(s).",
queryString, results.size()));
return (results.iterator().hasNext() ? results.iterator().next() : null);
} catch (Exception e) {
throw new RuntimeException(
String.format("An error occurred running Query (%1$s)!", queryString), e);
}
}
private <T extends Comparable<T>, B extends AbstractBean<T>> B query(final Region<T, B> region,
final String queryPredicate) {
try {
getLogWriter().info(String.format("Running Query (%1$s) on Region (%2$s)...", queryPredicate,
region.getFullPath()));
final SelectResults<B> results = region.query(queryPredicate);
getLogWriter()
.info(String.format("Running Query (%1$s) on Region (%2$s) returned (%3$d) result(s).",
queryPredicate, region.getFullPath(), results.size()));
return (results.iterator().hasNext() ? results.iterator().next() : null);
} catch (Exception e) {
throw new RuntimeException(
String.format("An error occurred running Query (%1$s) on Region (%2$s)!", queryPredicate,
region.getFullPath()),
e);
}
}
@Test
public void testListIndex() throws Exception {
final Result result =
executeCommand(CliStrings.LIST_INDEX + " --" + CliStrings.LIST_INDEX__STATS);
assertNotNull(result);
getLogWriter().info(toString(result));
assertEquals(Result.Status.OK, result.getStatus());
}
private static class Peer implements Iterable<RegionDefinition>, Serializable {
private final Properties distributedSystemProperties;
private final Set<RegionDefinition> regions = new HashSet<RegionDefinition>();
private final VM vm;
public Peer(final VM vm, final Properties distributedSystemProperties) {
assert distributedSystemProperties != null : "The GemFire Distributed System configuration properties cannot be null!";
this.distributedSystemProperties = distributedSystemProperties;
this.vm = vm;
}
public Properties getConfiguration() {
return this.distributedSystemProperties;
}
public String getName() {
return getConfiguration().getProperty(NAME);
}
public VM getVm() {
return vm;
}
public boolean add(final RegionDefinition... regionDefinitions) {
return (regionDefinitions != null && regions.addAll(Arrays.asList(regionDefinitions)));
}
@Override
public Iterator<RegionDefinition> iterator() {
return Collections.unmodifiableSet(regions).iterator();
}
public boolean remove(final RegionDefinition... regionDefinitions) {
return (regionDefinitions != null && regions.removeAll(Arrays.asList(regionDefinitions)));
}
public void run(final SerializableRunnableIF runnable) throws Exception {
if (getVm() == null) {
runnable.run();
} else {
getVm().invoke(runnable);
}
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(getClass().getSimpleName());
buffer.append(" {configuration = ").append(getConfiguration());
buffer.append(", name = ").append(getName());
buffer.append(", pid = ").append(getVm().getPid());
buffer.append("}");
return buffer.toString();
}
}
private static class IndexAdapter implements Index, Serializable {
private final IndexDetails.IndexType type;
private final String fromClause;
private final String indexedExpression;
private final String name;
protected IndexAdapter(final String name, final String indexedExpression,
final String fromClause) {
this(name, IndexType.FUNCTIONAL, indexedExpression, fromClause);
}
protected IndexAdapter(final String name, final IndexType type, final String indexedExpression,
final String fromClause) {
assert name != null : "The name of the Index cannot be null!";
assert indexedExpression != null : String
.format("The expression to index for Index (%1$s) cannot be null!", name);
assert fromClause != null : String.format("The from clause for Index (%1$s) cannot be null!",
name);
this.type = ObjectUtils.defaultIfNull(IndexDetails.IndexType.valueOf(type),
IndexDetails.IndexType.FUNCTIONAL);
this.name = name;
this.indexedExpression = indexedExpression;
this.fromClause = fromClause;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getFromClause() {
return this.fromClause;
}
@Override
public String getCanonicalizedFromClause() {
return this.fromClause;
}
@Override
public String getIndexedExpression() {
return this.indexedExpression;
}
@Override
public String getCanonicalizedIndexedExpression() {
return this.indexedExpression;
}
@Override
public String getProjectionAttributes() {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override
public String getCanonicalizedProjectionAttributes() {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override
public Region<?, ?> getRegion() {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override
public IndexStatistics getStatistics() {
throw new UnsupportedOperationException("Not Implemented!");
}
@Override
public IndexType getType() {
return type.getType();
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(getClass().getSimpleName());
buffer.append(" {indexName = ").append(getName());
buffer.append(", indexType = ").append(getType());
buffer.append(", indexedExpression = ").append(getIndexedExpression());
buffer.append(", fromClause = ").append(getFromClause());
buffer.append("}");
return buffer.toString();
}
}
private static class RegionDefinition implements Iterable<Index>, Serializable {
private final Class<?> keyConstraint;
private final Class<?> valueConstraint;
private final Set<Index> indexes = new HashSet<Index>();
private final String regionName;
@SuppressWarnings("unchecked")
protected RegionDefinition(final String regionName, final Class<?> keyConstraint,
final Class<?> valueConstraint) {
assert !StringUtils.isBlank(regionName) : "The name of the Region must be specified!";
this.regionName = regionName;
this.keyConstraint = ObjectUtils.defaultIfNull(keyConstraint, Object.class);
this.valueConstraint = ObjectUtils.defaultIfNull(valueConstraint, Object.class);
}
public String getRegionName() {
return regionName;
}
public Class<?> getKeyConstraint() {
return keyConstraint;
}
public Class<?> getValueConstraint() {
return valueConstraint;
}
public boolean add(final Index... indexes) {
return (indexes != null && this.indexes.addAll(Arrays.asList(indexes)));
}
@Override
public Iterator<Index> iterator() {
return Collections.unmodifiableSet(indexes).iterator();
}
public boolean remove(final Index... indexes) {
return (indexes != null && this.indexes.removeAll(Arrays.asList(indexes)));
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof RegionDefinition)) {
return false;
}
final RegionDefinition that = (RegionDefinition) obj;
return ObjectUtils.equals(getRegionName(), that.getRegionName());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.hashCode(getRegionName());
return hashValue;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(getClass().getSimpleName());
buffer.append(" {regionName = ").append(getRegionName());
buffer.append(", keyConstraint = ").append(getKeyConstraint());
buffer.append(", valueConstraint = ").append(getValueConstraint());
buffer.append("}");
return buffer.toString();
}
}
private static abstract class AbstractBean<T extends Comparable<T>>
implements MutableIdentifiable<T>, Serializable {
private T id;
private String name;
public AbstractBean() {}
public AbstractBean(final T id) {
this.id = id;
}
@Override
public T getId() {
return id;
}
@Override
public void setId(final T id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(getClass().isInstance(obj))) {
return false;
}
final AbstractBean bean = (AbstractBean) obj;
return ObjectUtils.equals(getId(), bean.getId());
}
@Override
public int hashCode() {
int hashValue = 17;
hashValue = 37 * hashValue + ObjectUtils.hashCode(getId());
return hashValue;
}
@Override
public String toString() {
final StringBuilder buffer = new StringBuilder(getClass().getSimpleName());
buffer.append(" {id = ").append(getId());
buffer.append(", name = ").append(getName());
buffer.append("}");
return buffer.toString();
}
}
public static class Consumer extends AbstractBean<Long> {
private volatile int units;
public Consumer() {}
public Consumer(final Long id) {
super(id);
}
public int getUnits() {
return units;
}
public int consume() {
return ++units;
}
}
public static class Producer extends AbstractBean<Long> {
private volatile int units;
public Producer() {}
public Producer(final Long id) {
super(id);
}
public int getUnits() {
return units;
}
public int produce() {
return ++units;
}
}
private static class Proxy extends AbstractBean<Long> {
private final AbstractBean<Long> bean;
private int unitsSnapshot;
public Proxy(final AbstractBean<Long> bean) {
assert bean != null : "The bean to proxy cannot be null!";
this.bean = bean;
}
public AbstractBean<Long> getBean() {
return bean;
}
@Override
public Long getId() {
return getBean().getId();
}
@Override
public String getName() {
return getBean().getName();
}
public int getUnitsSnapshot() {
return unitsSnapshot;
}
public void setUnitsSnapshot(final int unitsSnapshot) {
this.unitsSnapshot = unitsSnapshot;
}
}
private static enum CrudOperation {
CREATE, RETRIEVE, UPDATE, DELETE
}
}