blob: 75ab6d6f9ccc708ae7ac7215555011a6fc63f84b [file] [log] [blame]
/*
* 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.tephra.hbase.txprune;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.HRegionLocation;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.TableName;
import org.apache.hadoop.hbase.client.Put;
import org.apache.hadoop.hbase.client.Table;
import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
import org.apache.hadoop.hbase.regionserver.Store;
import org.apache.hadoop.hbase.regionserver.StoreFile;
import org.apache.hadoop.hbase.regionserver.compactions.CompactionRequest;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.tephra.TransactionContext;
import org.apache.tephra.TransactionManager;
import org.apache.tephra.TxConstants;
import org.apache.tephra.coprocessor.CacheSupplier;
import org.apache.tephra.coprocessor.TransactionStateCache;
import org.apache.tephra.hbase.AbstractHBaseTableTest;
import org.apache.tephra.hbase.TransactionAwareHTable;
import org.apache.tephra.hbase.coprocessor.TransactionProcessor;
import org.apache.tephra.inmemory.InMemoryTxSystemClient;
import org.apache.tephra.metrics.TxMetricsCollector;
import org.apache.tephra.persist.InMemoryTransactionStateStorage;
import org.apache.tephra.persist.TransactionSnapshot;
import org.apache.tephra.persist.TransactionStateStorage;
import org.apache.tephra.persist.TransactionVisibilityState;
import org.apache.tephra.txprune.TransactionPruningPlugin;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import java.io.IOException;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
/**
* Test invalid list pruning
*/
public class InvalidListPruneTest extends AbstractHBaseTableTest {
private static final byte[] family = Bytes.toBytes("f1");
private static final byte[] qualifier = Bytes.toBytes("col1");
private static final int MAX_ROWS = 1000;
private static TableName txDataTable1;
private static TableName pruneStateTable;
private static DataJanitorState dataJanitorState;
// Override AbstractHBaseTableTest.startMiniCluster to setup configuration
@BeforeClass
public static void startMiniCluster() throws Exception {
// Setup the configuration to start HBase cluster with the invalid list pruning enabled
conf = HBaseConfiguration.create();
conf.setBoolean(TxConstants.TransactionPruning.PRUNE_ENABLE, true);
// Flush prune data to table quickly, so that tests don't need have to wait long to see updates
conf.setLong(TxConstants.TransactionPruning.PRUNE_FLUSH_INTERVAL, 0L);
AbstractHBaseTableTest.startMiniCluster();
TransactionStateStorage txStateStorage = new InMemoryTransactionStateStorage();
TransactionManager txManager = new TransactionManager(conf, txStateStorage, new TxMetricsCollector());
txManager.startAndWait();
// Do some transactional data operations
txDataTable1 = TableName.valueOf("invalidListPruneTestTable1");
Table hTable = createTable(txDataTable1.getName(), new byte[][]{family}, false,
Collections.singletonList(TestTransactionProcessor.class.getName()));
try (TransactionAwareHTable txTable = new TransactionAwareHTable(hTable, TxConstants.ConflictDetection.ROW)) {
TransactionContext txContext = new TransactionContext(new InMemoryTxSystemClient(txManager), txTable);
txContext.start();
for (int i = 0; i < MAX_ROWS; ++i) {
txTable.put(new Put(Bytes.toBytes(i)).addColumn(family, qualifier, Bytes.toBytes(i)));
}
txContext.finish();
}
testUtil.flush(txDataTable1);
txManager.stopAndWait();
pruneStateTable = TableName.valueOf(conf.get(TxConstants.TransactionPruning.PRUNE_STATE_TABLE,
TxConstants.TransactionPruning.DEFAULT_PRUNE_STATE_TABLE));
dataJanitorState =
new DataJanitorState(new DataJanitorState.TableSupplier() {
@Override
public Table get() throws IOException {
return testUtil.getConnection().getTable(pruneStateTable);
}
});
}
@AfterClass
public static void shutdownAfterClass() throws Exception {
hBaseAdmin.disableTable(txDataTable1);
hBaseAdmin.deleteTable(txDataTable1);
}
@Before
public void beforeTest() throws Exception {
createPruneStateTable();
InMemoryTransactionStateCache.setTransactionSnapshot(null);
}
private void createPruneStateTable() throws Exception {
Table table = createTable(pruneStateTable.getName(), new byte[][]{DataJanitorState.FAMILY}, false,
// Prune state table is a non-transactional table, hence no transaction co-processor
Collections.<String>emptyList());
table.close();
}
@After
public void afterTest() throws Exception {
// Disable the data table so that prune writer thread gets stopped,
// this makes sure that any cached value will not interfere with next test
hBaseAdmin.disableTable(txDataTable1);
deletePruneStateTable();
// Enabling the table enables the prune writer thread again
hBaseAdmin.enableTable(txDataTable1);
}
private void deletePruneStateTable() throws Exception {
if (hBaseAdmin.tableExists(pruneStateTable)) {
hBaseAdmin.disableTable(pruneStateTable);
hBaseAdmin.deleteTable(pruneStateTable);
}
}
@Test
public void testRecordCompactionState() throws Exception {
// No prune upper bound initially
Assert.assertEquals(-1,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
// Create a new transaction snapshot
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(100, 100, 100, ImmutableSet.of(50L),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
// Run minor compaction
testUtil.compact(txDataTable1, false);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
// No prune upper bound after minor compaction too
Assert.assertEquals(-1,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
// Run major compaction, and verify prune upper bound
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
Assert.assertEquals(50,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
// Run major compaction again with same snapshot, prune upper bound should not change
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
Assert.assertEquals(50,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
// Create a new transaction snapshot
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(110, 111, 112, ImmutableSet.of(150L),
ImmutableSortedMap.of(105L, new TransactionManager.InProgressTx(
100, 30, TransactionManager.InProgressType.SHORT))));
Assert.assertEquals(50,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
// Run major compaction again, now prune upper bound should change
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
Assert.assertEquals(104,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
}
@Test
public void testRecordCompactionStateNoTable() throws Exception {
// Create a new transaction snapshot
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(100, 100, 100, ImmutableSet.of(50L),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
// Run major compaction, and verify it completes
long now = System.currentTimeMillis();
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
long lastMajorCompactionTime = TestTransactionProcessor.lastMajorCompactionTime.get();
Assert.assertTrue(String.format("Expected %d, but was %d", now, lastMajorCompactionTime),
lastMajorCompactionTime >= now);
}
@Test
public void testRecordCompactionStateNoTxSnapshot() throws Exception {
// Test recording state without having a transaction snapshot to make sure we don't disrupt
// major compaction in that case
InMemoryTransactionStateCache.setTransactionSnapshot(null);
// Run major compaction, and verify it completes
long now = System.currentTimeMillis();
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
long lastMajorCompactionTime = TestTransactionProcessor.lastMajorCompactionTime.get();
Assert.assertTrue(String.format("Expected %d, but was %d", now, lastMajorCompactionTime),
lastMajorCompactionTime >= now);
}
@Test
public void testPruneUpperBound() throws Exception {
TransactionPruningPlugin transactionPruningPlugin = new TestTransactionPruningPlugin();
transactionPruningPlugin.initialize(conf);
try {
// Run without a transaction snapshot first
long now1 = 200;
long inactiveTxTimeNow1 = 150 * TxConstants.MAX_TX_PER_MS;
long expectedPruneUpperBound1 = -1;
// fetch prune upper bound
long pruneUpperBound1 = transactionPruningPlugin.fetchPruneUpperBound(now1, inactiveTxTimeNow1);
Assert.assertEquals(expectedPruneUpperBound1, pruneUpperBound1);
TimeRegions expectedRegions1 =
new TimeRegions(now1,
ImmutableSortedSet.orderedBy(Bytes.BYTES_COMPARATOR)
.add(getRegionName(txDataTable1, Bytes.toBytes(0)))
.build());
// Assert prune state is recorded correctly
Assert.assertEquals(expectedRegions1, dataJanitorState.getRegionsOnOrBeforeTime(now1));
Assert.assertEquals(expectedPruneUpperBound1,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
Assert.assertEquals(inactiveTxTimeNow1, dataJanitorState.getInactiveTransactionBoundForTime(now1));
// Run prune complete
transactionPruningPlugin.pruneComplete(now1, expectedPruneUpperBound1);
// Assert prune state was cleaned up correctly based on the prune time
Assert.assertEquals(expectedRegions1, dataJanitorState.getRegionsOnOrBeforeTime(now1));
Assert.assertEquals(expectedPruneUpperBound1,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
Assert.assertEquals(inactiveTxTimeNow1, dataJanitorState.getInactiveTransactionBoundForTime(now1));
// Create a new transaction snapshot, and run major compaction on txDataTable1
// And run all assertions again
long now2 = 300;
long inactiveTxTimeNow2 = 250 * TxConstants.MAX_TX_PER_MS;
long expectedPruneUpperBound2 = 200 * TxConstants.MAX_TX_PER_MS;
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(expectedPruneUpperBound2, expectedPruneUpperBound2, expectedPruneUpperBound2,
ImmutableSet.of(expectedPruneUpperBound2),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
TimeRegions expectedRegions2 =
new TimeRegions(now2,
ImmutableSortedSet.orderedBy(Bytes.BYTES_COMPARATOR)
.add(getRegionName(txDataTable1, Bytes.toBytes(0)))
.build());
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
long pruneUpperBound2 = transactionPruningPlugin.fetchPruneUpperBound(now2, inactiveTxTimeNow2);
Assert.assertEquals(expectedPruneUpperBound2, pruneUpperBound2);
Assert.assertEquals(expectedRegions2, dataJanitorState.getRegionsOnOrBeforeTime(now2));
Assert.assertEquals(expectedPruneUpperBound2,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
Assert.assertEquals(inactiveTxTimeNow2, dataJanitorState.getInactiveTransactionBoundForTime(now2));
Assert.assertEquals(expectedRegions1, dataJanitorState.getRegionsOnOrBeforeTime(now1));
Assert.assertEquals(inactiveTxTimeNow1, dataJanitorState.getInactiveTransactionBoundForTime(now1));
transactionPruningPlugin.pruneComplete(now2, pruneUpperBound2);
Assert.assertEquals(expectedRegions2, dataJanitorState.getRegionsOnOrBeforeTime(now2));
Assert.assertEquals(expectedPruneUpperBound2,
dataJanitorState.getPruneUpperBoundForRegion(getRegionName(txDataTable1, Bytes.toBytes(0))));
Assert.assertEquals(inactiveTxTimeNow2, dataJanitorState.getInactiveTransactionBoundForTime(now2));
Assert.assertNull(dataJanitorState.getRegionsOnOrBeforeTime(now1));
Assert.assertEquals(expectedPruneUpperBound1, dataJanitorState.getInactiveTransactionBoundForTime(now1));
} finally {
transactionPruningPlugin.destroy();
}
}
@Test
public void testPruneEmptyTable() throws Exception {
// Make sure that empty tables do not block the progress of pruning
// Create an empty table
TableName txEmptyTable = TableName.valueOf("emptyPruneTestTable");
Table emptyHTable = createTable(txEmptyTable.getName(), new byte[][]{family}, false,
Collections.singletonList(TestTransactionProcessor.class.getName()));
TransactionPruningPlugin transactionPruningPlugin = new TestTransactionPruningPlugin();
transactionPruningPlugin.initialize(conf);
try {
long now1 = System.currentTimeMillis();
long inactiveTxTimeNow1 = (now1 - 150) * TxConstants.MAX_TX_PER_MS;
long noPruneUpperBound = -1;
long expectedPruneUpperBound1 = (now1 - 200) * TxConstants.MAX_TX_PER_MS;
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(expectedPruneUpperBound1, expectedPruneUpperBound1, expectedPruneUpperBound1,
ImmutableSet.of(expectedPruneUpperBound1),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
testUtil.compact(txEmptyTable, true);
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
// fetch prune upper bound, there should be no prune upper bound since txEmptyTable cannot be compacted
long pruneUpperBound1 = transactionPruningPlugin.fetchPruneUpperBound(now1, inactiveTxTimeNow1);
Assert.assertEquals(noPruneUpperBound, pruneUpperBound1);
transactionPruningPlugin.pruneComplete(now1, noPruneUpperBound);
// Now flush the empty table, this will record the table region as empty, and then pruning will continue
hBaseAdmin.flush(txEmptyTable);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
// fetch prune upper bound, again, this time it should work
pruneUpperBound1 = transactionPruningPlugin.fetchPruneUpperBound(now1, inactiveTxTimeNow1);
Assert.assertEquals(expectedPruneUpperBound1, pruneUpperBound1);
transactionPruningPlugin.pruneComplete(now1, expectedPruneUpperBound1);
// Now add some data to the empty table
// (adding data non-transactionally is okay too, we just need some data for the compaction to run)
emptyHTable.put(new Put(Bytes.toBytes(1)).addColumn(family, qualifier, Bytes.toBytes(1)));
emptyHTable.close();
// Now run another compaction on txDataTable1 with an updated tx snapshot
long now2 = System.currentTimeMillis();
long inactiveTxTimeNow2 = (now2 - 150) * TxConstants.MAX_TX_PER_MS;
long expectedPruneUpperBound2 = (now2 - 200) * TxConstants.MAX_TX_PER_MS;
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(expectedPruneUpperBound2, expectedPruneUpperBound2, expectedPruneUpperBound2,
ImmutableSet.of(expectedPruneUpperBound2),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
testUtil.flush(txEmptyTable);
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
// Running a prune now should still return min(inactiveTxTimeNow1, expectedPruneUpperBound1) since
// txEmptyTable is no longer empty. This information is returned since the txEmptyTable was recorded as being
// empty in the previous run with inactiveTxTimeNow1
long pruneUpperBound2 = transactionPruningPlugin.fetchPruneUpperBound(now2, inactiveTxTimeNow2);
Assert.assertEquals(inactiveTxTimeNow1, pruneUpperBound2);
transactionPruningPlugin.pruneComplete(now2, expectedPruneUpperBound1);
// However, after compacting txEmptyTable we should get the latest upper bound
testUtil.flush(txEmptyTable);
testUtil.compact(txEmptyTable, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
pruneUpperBound2 = transactionPruningPlugin.fetchPruneUpperBound(now2, inactiveTxTimeNow2);
Assert.assertEquals(expectedPruneUpperBound2, pruneUpperBound2);
transactionPruningPlugin.pruneComplete(now2, expectedPruneUpperBound2);
} finally {
transactionPruningPlugin.destroy();
hBaseAdmin.disableTable(txEmptyTable);
hBaseAdmin.deleteTable(txEmptyTable);
}
}
@Test
public void testPruneTransientTable() throws Exception {
// Make sure that transient tables do not block the progress of pruning
// Create a temp table
TableName txTempTable = TableName.valueOf("tempTable");
createTable(txTempTable.getName(), new byte[][]{family}, false,
Collections.singletonList(TestTransactionProcessor.class.getName()));
TableName txDataTable2 = null;
TransactionPruningPlugin transactionPruningPlugin = new TestTransactionPruningPlugin();
transactionPruningPlugin.initialize(conf);
try {
long now1 = System.currentTimeMillis();
long inactiveTxTimeNow1 = (now1 - 150) * TxConstants.MAX_TX_PER_MS;
long noPruneUpperBound = -1;
long expectedPruneUpperBound1 = (now1 - 200) * TxConstants.MAX_TX_PER_MS;
InMemoryTransactionStateCache.setTransactionSnapshot(
new TransactionSnapshot(expectedPruneUpperBound1, expectedPruneUpperBound1, expectedPruneUpperBound1,
ImmutableSet.of(expectedPruneUpperBound1),
ImmutableSortedMap.<Long, TransactionManager.InProgressTx>of()));
// fetch prune upper bound, there should be no prune upper bound since nothing has been compacted yet.
// This run is only to store the initial set of regions
long pruneUpperBound1 = transactionPruningPlugin.fetchPruneUpperBound(now1, inactiveTxTimeNow1);
Assert.assertEquals(noPruneUpperBound, pruneUpperBound1);
transactionPruningPlugin.pruneComplete(now1, noPruneUpperBound);
// Now delete the transient table
hBaseAdmin.disableTable(txTempTable);
hBaseAdmin.deleteTable(txTempTable);
txTempTable = null;
// Compact the data table now
testUtil.compact(txDataTable1, true);
// Since the write to prune table happens async, we need to sleep a bit before checking the state of the table
TimeUnit.SECONDS.sleep(2);
// Create a new table that will not be compacted
txDataTable2 = TableName.valueOf("invalidListPruneTestTable2");
createTable(txDataTable2.getName(), new byte[][]{family}, false,
Collections.singletonList(TestTransactionProcessor.class.getName()));
// fetch prune upper bound, there should be a prune upper bound even though txTempTable does not exist anymore,
// and txDataTable2 has not been compacted/flushed yet
long now2 = System.currentTimeMillis();
long inactiveTxTimeNow2 = (now1 - 150) * TxConstants.MAX_TX_PER_MS;
long pruneUpperBound2 = transactionPruningPlugin.fetchPruneUpperBound(now2, inactiveTxTimeNow2);
Assert.assertEquals(expectedPruneUpperBound1, pruneUpperBound2);
transactionPruningPlugin.pruneComplete(now2, expectedPruneUpperBound1);
} finally {
transactionPruningPlugin.destroy();
if (txDataTable2 != null) {
hBaseAdmin.disableTable(txDataTable2);
hBaseAdmin.deleteTable(txDataTable2);
}
if (txTempTable != null) {
hBaseAdmin.disableTable(txTempTable);
hBaseAdmin.deleteTable(txTempTable);
}
}
}
private byte[] getRegionName(TableName dataTable, byte[] row) throws IOException {
HRegionLocation regionLocation =
testUtil.getConnection().getRegionLocator(dataTable).getRegionLocation(row);
return regionLocation.getRegionInfo().getRegionName();
}
/**
* A transaction co-processor that uses in-memory {@link TransactionSnapshot} for testing
*/
@SuppressWarnings("WeakerAccess")
public static class TestTransactionProcessor extends TransactionProcessor {
private static final AtomicLong lastMajorCompactionTime = new AtomicLong(-1);
@Override
protected CacheSupplier<TransactionStateCache> getTransactionStateCacheSupplier(RegionCoprocessorEnvironment env) {
return new CacheSupplier<TransactionStateCache>() {
@Override
public TransactionStateCache get() {
return new InMemoryTransactionStateCache();
}
@Override
public void release() {
// no-op
}
};
}
@Override
public void postCompact(
org.apache.hadoop.hbase.coprocessor.ObserverContext<RegionCoprocessorEnvironment> c,
Store store, StoreFile resultFile,
org.apache.hadoop.hbase.regionserver.compactions.CompactionLifeCycleTracker tracker,
CompactionRequest request) throws IOException {
super.postCompact(c, store, resultFile, tracker, request);
lastMajorCompactionTime.set(System.currentTimeMillis());
}
}
/**
* Used to supply in-memory {@link TransactionSnapshot} to {@link TestTransactionProcessor} for testing
*/
@SuppressWarnings("WeakerAccess")
public static class InMemoryTransactionStateCache extends TransactionStateCache {
private static TransactionVisibilityState transactionSnapshot;
public static void setTransactionSnapshot(TransactionVisibilityState transactionSnapshot) {
InMemoryTransactionStateCache.transactionSnapshot = transactionSnapshot;
}
@Override
protected void startUp() throws Exception {
// Nothing to do
}
@Override
protected void shutDown() throws Exception {
// Nothing to do
}
@Override
public TransactionVisibilityState getLatestState() {
return transactionSnapshot;
}
}
@SuppressWarnings("WeakerAccess")
public static class TestTransactionPruningPlugin extends HBaseTransactionPruningPlugin {
@Override
protected boolean isTransactionalTable(HTableDescriptor tableDescriptor) {
return tableDescriptor.hasCoprocessor(TestTransactionProcessor.class.getName());
}
}
}