Java tutorial
/** * 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.bookkeeper.metastore; import static org.apache.bookkeeper.metastore.MetastoreScannableTable.EMPTY_END_KEY; import static org.apache.bookkeeper.metastore.MetastoreScannableTable.EMPTY_START_KEY; import static org.apache.bookkeeper.metastore.MetastoreTable.ALL_FIELDS; import static org.apache.bookkeeper.metastore.MetastoreTable.NON_FIELDS; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.apache.bookkeeper.metastore.InMemoryMetastoreTable.MetadataVersion; import org.apache.bookkeeper.metastore.MSException.Code; import org.apache.bookkeeper.metastore.MetastoreScannableTable.Order; import org.apache.bookkeeper.versioning.Version; import org.apache.bookkeeper.versioning.Versioned; import org.apache.commons.configuration.CompositeConfiguration; import org.apache.commons.configuration.Configuration; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.MapDifference; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import static org.junit.Assert.*; public class TestMetaStore { private final static Logger logger = LoggerFactory.getLogger(TestMetaStore.class); protected final static String TABLE = "myTable"; protected final static String RECORDID = "test"; protected final static String FIELD_NAME = "name"; protected final static String FIELD_COUNTER = "counter"; protected String getFieldFromValue(Value value, String field) { byte[] v = value.getField(field); return v == null ? null : new String(v); } protected static Value makeValue(String name, Integer counter) { Value data = new Value(); if (name != null) { data.setField(FIELD_NAME, name.getBytes()); } if (counter != null) { data.setField(FIELD_COUNTER, counter.toString().getBytes()); } return data; } protected class Record { String name; Integer counter; Version version; public Record() { } public Record(String name, Integer counter, Version version) { this.name = name; this.counter = counter; this.version = version; } public Record(Versioned<Value> vv) { version = vv.getVersion(); Value value = vv.getValue(); if (value == null) { return; } name = getFieldFromValue(value, FIELD_NAME); String c = getFieldFromValue(value, FIELD_COUNTER); if (c != null) { counter = new Integer(c); } } public Version getVersion() { return version; } public Value getValue() { return TestMetaStore.makeValue(name, counter); } public Versioned<Value> getVersionedValue() { return new Versioned<Value>(getValue(), version); } public void merge(String name, Integer counter, Version version) { if (name != null) { this.name = name; } if (counter != null) { this.counter = counter; } if (version != null) { this.version = version; } } public void merge(Record record) { merge(record.name, record.counter, record.version); } public void checkEqual(Versioned<Value> vv) { Version v = vv.getVersion(); Value value = vv.getValue(); assertEquals(name, getFieldFromValue(value, FIELD_NAME)); String c = getFieldFromValue(value, FIELD_COUNTER); if (counter == null) { assertNull(c); } else { assertEquals(counter.toString(), c); } assertTrue(isEqualVersion(version, v)); } } protected MetaStore metastore; protected MetastoreScannableTable myActualTable; protected MetastoreScannableTableAsyncToSyncConverter myTable; protected String getMetaStoreName() { return InMemoryMetaStore.class.getName(); } protected Configuration getConfiguration() { return new CompositeConfiguration(); } protected Version newBadVersion() { return new MetadataVersion(-1); } protected Version nextVersion(Version version) { if (Version.NEW == version) { return new MetadataVersion(0); } if (Version.ANY == version) { return Version.ANY; } assertTrue(version instanceof MetadataVersion); return new MetadataVersion(((MetadataVersion) version).incrementVersion()); } private void checkVersion(Version v) { assertNotNull(v); if (v != Version.NEW && v != Version.ANY) { assertTrue(v instanceof MetadataVersion); } } protected boolean isEqualVersion(Version v1, Version v2) { checkVersion(v1); checkVersion(v2); return v1.compare(v2) == Version.Occurred.CONCURRENTLY; } @Before public void setUp() throws Exception { metastore = MetastoreFactory.createMetaStore(getMetaStoreName()); Configuration config = getConfiguration(); metastore.init(config, metastore.getVersion()); myActualTable = metastore.createScannableTable(TABLE); myTable = new MetastoreScannableTableAsyncToSyncConverter(myActualTable); // setup a clean environment clearTable(); } @After public void tearDown() throws Exception { // also clear table after test clearTable(); myActualTable.close(); metastore.close(); } void checkExpectedValue(Versioned<Value> vv, String expectedName, Integer expectedCounter, Version expectedVersion) { Record expected = new Record(expectedName, expectedCounter, expectedVersion); expected.checkEqual(vv); } protected Integer getRandom() { return (int) (Math.random() * 65536); } protected Versioned<Value> getRecord(String recordId) throws Exception { try { return myTable.get(recordId); } catch (MSException.NoKeyException nke) { return null; } } /** * get record with specific fields, assume record EXIST! */ protected Versioned<Value> getExistRecordFields(String recordId, Set<String> fields) throws Exception { Versioned<Value> retValue = myTable.get(recordId, fields); return retValue; } /** * put and check fields */ protected void putAndCheck(String recordId, String name, Integer counter, Version version, Record expected, Code expectedCode) throws Exception { Version retVersion = null; Code code = Code.OperationFailure; try { retVersion = myTable.put(recordId, makeValue(name, counter), version); code = Code.OK; } catch (MSException.BadVersionException bve) { code = Code.BadVersion; } catch (MSException.NoKeyException nke) { code = Code.NoKey; } catch (MSException.KeyExistsException kee) { code = Code.KeyExists; } assertEquals(expectedCode, code); // get and check all fields of record if (Code.OK == code) { assertTrue(isEqualVersion(retVersion, nextVersion(version))); expected.merge(name, counter, retVersion); } Versioned<Value> existedVV = getRecord(recordId); if (null == expected) { assertNull(existedVV); } else { expected.checkEqual(existedVV); } } protected void clearTable() throws Exception { MetastoreCursor cursor = myTable.openCursor(); if (!cursor.hasMoreEntries()) { return; } while (cursor.hasMoreEntries()) { Iterator<MetastoreTableItem> iter = cursor.readEntries(99); while (iter.hasNext()) { MetastoreTableItem item = iter.next(); String key = item.getKey(); myTable.remove(key, Version.ANY); } } cursor.close(); } /** * Test (get, get partial field, remove) on non-existent element. */ @Test(timeout = 60000) public void testNonExistent() throws Exception { // get try { myTable.get(RECORDID); fail("Should fail to get a non-existent key"); } catch (MSException.NoKeyException nke) { } // get partial field Set<String> fields = new HashSet<String>(Arrays.asList(new String[] { FIELD_COUNTER })); try { myTable.get(RECORDID, fields); fail("Should fail to get a non-existent key with specified fields"); } catch (MSException.NoKeyException nke) { } // remove try { myTable.remove(RECORDID, Version.ANY); fail("Should fail to delete a non-existent key"); } catch (MSException.NoKeyException nke) { } } /** * Test usage of get operation on (full and partial) fields. */ @Test(timeout = 60000) public void testGet() throws Exception { Versioned<Value> vv; final Set<String> fields = new HashSet<String>(Arrays.asList(new String[] { FIELD_NAME })); final String name = "get"; final Integer counter = getRandom(); // put test item Version version = myTable.put(RECORDID, makeValue(name, counter), Version.NEW); assertNotNull(version); // fetch with all fields vv = getExistRecordFields(RECORDID, ALL_FIELDS); checkExpectedValue(vv, name, counter, version); // partial get name vv = getExistRecordFields(RECORDID, fields); checkExpectedValue(vv, name, null, version); // non fields vv = getExistRecordFields(RECORDID, NON_FIELDS); checkExpectedValue(vv, null, null, version); // get null key should fail try { getExistRecordFields(null, NON_FIELDS); fail("Should fail to get null key with NON fields"); } catch (MSException.IllegalOpException ioe) { } try { getExistRecordFields(null, ALL_FIELDS); fail("Should fail to get null key with ALL fields."); } catch (MSException.IllegalOpException ioe) { } try { getExistRecordFields(null, fields); fail("Should fail to get null key with fields " + fields); } catch (MSException.IllegalOpException ioe) { } } /** * Test usage of put operation with (full and partial) fields. */ @Test(timeout = 60000) public void testPut() throws Exception { final Integer counter = getRandom(); final String name = "put"; Version version; /** * test correct version put */ // put test item version = myTable.put(RECORDID, makeValue(name, counter), Version.NEW); assertNotNull(version); Record expected = new Record(name, counter, version); // correct version put with only name field changed putAndCheck(RECORDID, "name1", null, expected.getVersion(), expected, Code.OK); // correct version put with only counter field changed putAndCheck(RECORDID, null, counter + 1, expected.getVersion(), expected, Code.OK); // correct version put with all fields filled putAndCheck(RECORDID, "name2", counter + 2, expected.getVersion(), expected, Code.OK); // test put exist entry with Version.ANY checkPartialPut("put exist entry with Version.ANY", Version.ANY, expected, Code.OK); /** * test bad version put */ // put to existed entry with Version.NEW badVersionedPut(Version.NEW, Code.KeyExists); // put to existed entry with bad version badVersionedPut(newBadVersion(), Code.BadVersion); // remove the entry myTable.remove(RECORDID, Version.ANY); // put to non-existent entry with bad version badVersionedPut(newBadVersion(), Code.NoKey); // put to non-existent entry with Version.ANY badVersionedPut(Version.ANY, Code.NoKey); /** * test illegal arguments */ illegalPut(null, Version.NEW); illegalPut(makeValue("illegal value", getRandom()), null); illegalPut(null, null); } protected void badVersionedPut(Version badVersion, Code expectedCode) throws Exception { Versioned<Value> vv = getRecord(RECORDID); Record expected = null; if (expectedCode != Code.NoKey) { assertNotNull(vv); expected = new Record(vv); } checkPartialPut("badVersionedPut", badVersion, expected, expectedCode); } protected void checkPartialPut(String name, Version version, Record expected, Code expectedCode) throws Exception { Integer counter; // bad version put with all fields filled counter = getRandom(); putAndCheck(RECORDID, name + counter, counter, version, expected, expectedCode); // bad version put with only name field changed counter = getRandom(); putAndCheck(RECORDID, name + counter, null, version, expected, expectedCode); // bad version put with only counter field changed putAndCheck(RECORDID, null, counter, version, expected, expectedCode); } protected void illegalPut(Value value, Version version) throws MSException { try { myTable.put(RECORDID, value, version); fail("Should fail to do versioned put with illegal arguments"); } catch (MSException.IllegalOpException ioe) { } } /** * Test usage of (unconditional remove, BadVersion remove, CorrectVersion * remove) operation. */ @Test(timeout = 60000) public void testRemove() throws Exception { final Integer counter = getRandom(); final String name = "remove"; Version version; // insert test item version = myTable.put(RECORDID, makeValue(name, counter), Version.NEW); assertNotNull(version); // test unconditional remove myTable.remove(RECORDID, Version.ANY); // insert test item version = myTable.put(RECORDID, makeValue(name, counter), Version.NEW); assertNotNull(version); // test remove with bad version try { myTable.remove(RECORDID, Version.NEW); fail("Should fail to remove a given key with bad version"); } catch (MSException.BadVersionException bve) { } try { myTable.remove(RECORDID, newBadVersion()); fail("Should fail to remove a given key with bad version"); } catch (MSException.BadVersionException bve) { } // test remove with correct version myTable.remove(RECORDID, version); } protected void openCursorTest(MetastoreCursor cursor, Map<String, Value> expectedValues, int numEntriesPerScan) throws Exception { try { Map<String, Value> entries = Maps.newHashMap(); while (cursor.hasMoreEntries()) { Iterator<MetastoreTableItem> iter = cursor.readEntries(numEntriesPerScan); while (iter.hasNext()) { MetastoreTableItem item = iter.next(); entries.put(item.getKey(), item.getValue().getValue()); } } MapDifference<String, Value> diff = Maps.difference(expectedValues, entries); assertTrue(diff.areEqual()); } finally { cursor.close(); } } void openRangeCursorTest(String firstKey, boolean firstInclusive, String lastKey, boolean lastInclusive, Order order, Set<String> fields, Iterator<Map.Entry<String, Value>> expectedValues, int numEntriesPerScan) throws Exception { MetastoreCursor cursor = myTable.openCursor(firstKey, firstInclusive, lastKey, lastInclusive, order, fields); try { while (cursor.hasMoreEntries()) { Iterator<MetastoreTableItem> iter = cursor.readEntries(numEntriesPerScan); while (iter.hasNext()) { assertTrue(expectedValues.hasNext()); MetastoreTableItem item = iter.next(); Map.Entry<String, Value> expectedItem = expectedValues.next(); assertEquals(expectedItem.getKey(), item.getKey()); assertEquals(expectedItem.getValue(), item.getValue().getValue()); } } assertFalse(expectedValues.hasNext()); } finally { cursor.close(); } } /** * Test usage of (scan) operation on (full and partial) fields. */ @Test(timeout = 60000) public void testOpenCursor() throws Exception { TreeMap<String, Value> allValues = Maps.newTreeMap(); TreeMap<String, Value> partialValues = Maps.newTreeMap(); TreeMap<String, Value> nonValues = Maps.newTreeMap(); Set<String> counterFields = Sets.newHashSet(FIELD_COUNTER); for (int i = 5; i < 24; i++) { char c = (char) ('a' + i); String key = String.valueOf(c); Value v = makeValue("value" + i, i); Value cv = v.project(counterFields); Value nv = v.project(NON_FIELDS); myTable.put(key, new Value(v), Version.NEW); allValues.put(key, v); partialValues.put(key, cv); nonValues.put(key, nv); } // test open cursor MetastoreCursor cursor = myTable.openCursor(ALL_FIELDS); openCursorTest(cursor, allValues, 7); cursor = myTable.openCursor(counterFields); openCursorTest(cursor, partialValues, 7); cursor = myTable.openCursor(NON_FIELDS); openCursorTest(cursor, nonValues, 7); // test order inclusive exclusive Iterator<Map.Entry<String, Value>> expectedIterator; expectedIterator = allValues.subMap("l", true, "u", true).entrySet().iterator(); openRangeCursorTest("l", true, "u", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.descendingMap().subMap("u", true, "l", true).entrySet().iterator(); openRangeCursorTest("u", true, "l", true, Order.DESC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.subMap("l", false, "u", false).entrySet().iterator(); openRangeCursorTest("l", false, "u", false, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.descendingMap().subMap("u", false, "l", false).entrySet().iterator(); openRangeCursorTest("u", false, "l", false, Order.DESC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.subMap("l", true, "u", false).entrySet().iterator(); openRangeCursorTest("l", true, "u", false, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.descendingMap().subMap("u", true, "l", false).entrySet().iterator(); openRangeCursorTest("u", true, "l", false, Order.DESC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.subMap("l", false, "u", true).entrySet().iterator(); openRangeCursorTest("l", false, "u", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.descendingMap().subMap("u", false, "l", true).entrySet().iterator(); openRangeCursorTest("u", false, "l", true, Order.DESC, ALL_FIELDS, expectedIterator, 7); // test out of range String firstKey = "f"; String lastKey = "x"; expectedIterator = allValues.subMap(firstKey, true, lastKey, true).entrySet().iterator(); openRangeCursorTest("a", true, "z", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.subMap("l", true, lastKey, true).entrySet().iterator(); openRangeCursorTest("l", true, "z", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.subMap(firstKey, true, "u", true).entrySet().iterator(); openRangeCursorTest("a", true, "u", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); // test EMPTY_START_KEY and EMPTY_END_KEY expectedIterator = allValues.subMap(firstKey, true, "u", true).entrySet().iterator(); openRangeCursorTest(EMPTY_START_KEY, true, "u", true, Order.ASC, ALL_FIELDS, expectedIterator, 7); expectedIterator = allValues.descendingMap().subMap(lastKey, true, "l", true).entrySet().iterator(); openRangeCursorTest(EMPTY_END_KEY, true, "l", true, Order.DESC, ALL_FIELDS, expectedIterator, 7); // test illegal arguments try { myTable.openCursor("a", true, "z", true, Order.DESC, ALL_FIELDS); fail("Should fail with wrong range"); } catch (MSException.IllegalOpException ioe) { } try { myTable.openCursor("z", true, "a", true, Order.ASC, ALL_FIELDS); fail("Should fail with wrong range"); } catch (MSException.IllegalOpException ioe) { } try { myTable.openCursor("a", true, "z", true, null, ALL_FIELDS); fail("Should fail with null order"); } catch (MSException.IllegalOpException ioe) { } } }