use of com.github.ambry.store.StoreKey in project ambry by linkedin.
the class MessageFormatInputStreamTest method messageFormatPutRecordsTest.
private void messageFormatPutRecordsTest(short blobVersion, BlobType blobType, short headerVersion) throws IOException, MessageFormatException {
StoreKey key = new MockId("id1");
StoreKeyFactory keyFactory = new MockIdFactory();
short accountId = Utils.getRandomShort(TestUtils.RANDOM);
short containerId = Utils.getRandomShort(TestUtils.RANDOM);
BlobProperties prop = new BlobProperties(10, "servid", accountId, containerId, false);
byte[] encryptionKey = new byte[100];
new Random().nextBytes(encryptionKey);
byte[] usermetadata = new byte[1000];
new Random().nextBytes(usermetadata);
int blobContentSize = 2000;
byte[] data = new byte[blobContentSize];
new Random().nextBytes(data);
short lifeVersion = 1;
long blobSize = -1;
MessageFormatRecord.headerVersionToUse = headerVersion;
if (blobVersion == MessageFormatRecord.Blob_Version_V1) {
blobSize = MessageFormatRecord.Blob_Format_V1.getBlobRecordSize(blobContentSize);
} else if (blobVersion == MessageFormatRecord.Blob_Version_V2 && blobType == BlobType.DataBlob) {
blobSize = (int) MessageFormatRecord.Blob_Format_V2.getBlobRecordSize(blobContentSize);
} else if (blobVersion == MessageFormatRecord.Blob_Version_V2 && blobType == BlobType.MetadataBlob) {
ByteBuffer byteBufferBlob = MessageFormatTestUtils.getBlobContentForMetadataBlob(blobContentSize);
data = byteBufferBlob.array();
blobContentSize = data.length;
blobSize = (int) MessageFormatRecord.Blob_Format_V2.getBlobRecordSize(blobContentSize);
}
ByteBufferInputStream stream = new ByteBufferInputStream(ByteBuffer.wrap(data));
MessageFormatInputStream messageFormatStream = (blobVersion == MessageFormatRecord.Blob_Version_V2) ? new PutMessageFormatInputStream(key, ByteBuffer.wrap(encryptionKey), prop, ByteBuffer.wrap(usermetadata), stream, blobContentSize, blobType, lifeVersion) : new PutMessageFormatBlobV1InputStream(key, prop, ByteBuffer.wrap(usermetadata), stream, blobContentSize, blobType);
int headerSize = MessageFormatRecord.getHeaderSizeForVersion(headerVersion);
int blobEncryptionKeySize = headerVersion != MessageFormatRecord.Message_Header_Version_V1 ? MessageFormatRecord.BlobEncryptionKey_Format_V1.getBlobEncryptionKeyRecordSize(ByteBuffer.wrap(encryptionKey)) : 0;
int blobPropertiesRecordSize = MessageFormatRecord.BlobProperties_Format_V1.getBlobPropertiesRecordSize(prop);
int userMetadataSize = MessageFormatRecord.UserMetadata_Format_V1.getUserMetadataSize(ByteBuffer.wrap(usermetadata));
Assert.assertEquals(messageFormatStream.getSize(), headerSize + blobEncryptionKeySize + blobPropertiesRecordSize + userMetadataSize + blobSize + key.sizeInBytes());
// verify header
byte[] headerOutput = new byte[headerSize];
messageFormatStream.read(headerOutput);
ByteBuffer headerBuf = ByteBuffer.wrap(headerOutput);
Assert.assertEquals(headerVersion, headerBuf.getShort());
if (headerVersion == MessageFormatRecord.Message_Header_Version_V3) {
Assert.assertEquals(lifeVersion, headerBuf.getShort());
}
Assert.assertEquals(blobEncryptionKeySize + blobPropertiesRecordSize + userMetadataSize + blobSize, headerBuf.getLong());
switch(headerVersion) {
case MessageFormatRecord.Message_Header_Version_V1:
Assert.assertEquals(headerSize + key.sizeInBytes(), headerBuf.getInt());
Assert.assertEquals(MessageFormatRecord.Message_Header_Invalid_Relative_Offset, headerBuf.getInt());
Assert.assertEquals(headerSize + key.sizeInBytes() + blobPropertiesRecordSize, headerBuf.getInt());
Assert.assertEquals(headerSize + key.sizeInBytes() + blobPropertiesRecordSize + userMetadataSize, headerBuf.getInt());
break;
default:
// case MessageFormatRecord.Message_Header_Version_V2 or V3:
Assert.assertEquals(headerSize + key.sizeInBytes(), headerBuf.getInt());
Assert.assertEquals(headerSize + key.sizeInBytes() + blobEncryptionKeySize, headerBuf.getInt());
Assert.assertEquals(MessageFormatRecord.Message_Header_Invalid_Relative_Offset, headerBuf.getInt());
Assert.assertEquals(headerSize + key.sizeInBytes() + blobEncryptionKeySize + blobPropertiesRecordSize, headerBuf.getInt());
Assert.assertEquals(headerSize + key.sizeInBytes() + blobEncryptionKeySize + blobPropertiesRecordSize + userMetadataSize, headerBuf.getInt());
}
Crc32 crc = new Crc32();
crc.update(headerOutput, 0, headerSize - MessageFormatRecord.Crc_Size);
Assert.assertEquals(crc.getValue(), headerBuf.getLong());
// verify handle
byte[] handleOutput = new byte[key.sizeInBytes()];
ByteBuffer handleOutputBuf = ByteBuffer.wrap(handleOutput);
messageFormatStream.read(handleOutput);
byte[] dest = new byte[key.sizeInBytes()];
handleOutputBuf.get(dest);
Assert.assertArrayEquals(dest, key.toBytes());
// verify encryption key
if (headerVersion != MessageFormatRecord.Message_Header_Version_V1) {
byte[] blobEncryptionKeyOutput = new byte[blobEncryptionKeySize];
ByteBuffer blobEncryptionKeyBuf = ByteBuffer.wrap(blobEncryptionKeyOutput);
messageFormatStream.read(blobEncryptionKeyOutput);
Assert.assertEquals(blobEncryptionKeyBuf.getShort(), MessageFormatRecord.Blob_Encryption_Key_V1);
Assert.assertEquals(blobEncryptionKeyBuf.getInt(), 100);
dest = new byte[100];
blobEncryptionKeyBuf.get(dest);
Assert.assertArrayEquals(dest, encryptionKey);
crc = new Crc32();
crc.update(blobEncryptionKeyOutput, 0, blobEncryptionKeySize - MessageFormatRecord.Crc_Size);
Assert.assertEquals(crc.getValue(), blobEncryptionKeyBuf.getLong());
}
// verify blob properties
byte[] blobPropertiesOutput = new byte[blobPropertiesRecordSize];
ByteBuffer blobPropertiesBuf = ByteBuffer.wrap(blobPropertiesOutput);
messageFormatStream.read(blobPropertiesOutput);
Assert.assertEquals(blobPropertiesBuf.getShort(), 1);
BlobProperties propOutput = BlobPropertiesSerDe.getBlobPropertiesFromStream(new DataInputStream(new ByteBufferInputStream(blobPropertiesBuf)));
Assert.assertEquals(10, propOutput.getBlobSize());
Assert.assertEquals("servid", propOutput.getServiceId());
Assert.assertEquals("AccountId mismatch", accountId, propOutput.getAccountId());
Assert.assertEquals("ContainerId mismatch", containerId, propOutput.getContainerId());
crc = new Crc32();
crc.update(blobPropertiesOutput, 0, blobPropertiesRecordSize - MessageFormatRecord.Crc_Size);
Assert.assertEquals(crc.getValue(), blobPropertiesBuf.getLong());
// verify user metadata
byte[] userMetadataOutput = new byte[userMetadataSize];
ByteBuffer userMetadataBuf = ByteBuffer.wrap(userMetadataOutput);
messageFormatStream.read(userMetadataOutput);
Assert.assertEquals(userMetadataBuf.getShort(), 1);
Assert.assertEquals(userMetadataBuf.getInt(), 1000);
dest = new byte[1000];
userMetadataBuf.get(dest);
Assert.assertArrayEquals(dest, usermetadata);
crc = new Crc32();
crc.update(userMetadataOutput, 0, userMetadataSize - MessageFormatRecord.Crc_Size);
Assert.assertEquals(crc.getValue(), userMetadataBuf.getLong());
// verify blob
CrcInputStream crcstream = new CrcInputStream(messageFormatStream);
DataInputStream streamData = new DataInputStream(crcstream);
Assert.assertEquals(streamData.readShort(), blobVersion);
if (blobVersion == MessageFormatRecord.Blob_Version_V2) {
Assert.assertEquals(streamData.readShort(), blobType.ordinal());
}
Assert.assertEquals(streamData.readLong(), blobContentSize);
for (int i = 0; i < blobContentSize; i++) {
Assert.assertEquals((byte) streamData.read(), data[i]);
}
long crcVal = crcstream.getValue();
Assert.assertEquals(crcVal, streamData.readLong());
// Verify Blob All
stream = new ByteBufferInputStream(ByteBuffer.wrap(data));
messageFormatStream = (blobVersion == MessageFormatRecord.Blob_Version_V2) ? new PutMessageFormatInputStream(key, ByteBuffer.wrap(encryptionKey), prop, ByteBuffer.wrap(usermetadata), stream, blobContentSize, blobType) : new PutMessageFormatBlobV1InputStream(key, prop, ByteBuffer.wrap(usermetadata), stream, blobContentSize, blobType);
int totalSize;
switch(headerVersion) {
case MessageFormatRecord.Message_Header_Version_V1:
totalSize = headerSize + key.sizeInBytes() + blobPropertiesRecordSize + userMetadataSize + (int) blobSize;
break;
default:
// case MessageFormatRecord.Message_Header_Version_V2:
totalSize = headerSize + key.sizeInBytes() + blobEncryptionKeySize + blobPropertiesRecordSize + userMetadataSize + (int) blobSize;
}
ByteBuffer allBuf = ByteBuffer.allocate(totalSize);
messageFormatStream.read(allBuf.array());
BlobAll blobAll = MessageFormatRecord.deserializeBlobAll(new ByteBufferInputStream(allBuf), keyFactory);
Assert.assertEquals(key, blobAll.getStoreKey());
Assert.assertArrayEquals(usermetadata, blobAll.getBlobInfo().getUserMetadata());
Assert.assertEquals(blobContentSize, blobAll.getBlobData().getSize());
Assert.assertEquals(blobType, blobAll.getBlobData().getBlobType());
if (headerVersion != MessageFormatRecord.Message_Header_Version_V1) {
Assert.assertEquals(ByteBuffer.wrap(encryptionKey), blobAll.getBlobEncryptionKey());
} else {
Assert.assertEquals(null, blobAll.getBlobEncryptionKey());
}
ByteBuf byteBuf = blobAll.getBlobData().content();
try {
Assert.assertEquals(Unpooled.wrappedBuffer(data), byteBuf);
} finally {
byteBuf.release();
}
}
use of com.github.ambry.store.StoreKey in project ambry by linkedin.
the class MessageFormatSendTest method doSendWriteSingleMessageTest.
/**
* Helper method for testing single message sends.
* @param encryptionKey the encryption key to include in the message while writing it.
* @param expectedEncryptionKey the key expected when reading the sent message.
*/
private void doSendWriteSingleMessageTest(ByteBuffer encryptionKey, ByteBuffer expectedEncryptionKey) throws Exception {
String serviceId = "serviceId";
String ownerId = "owner";
String contentType = "bin";
short accountId = 10;
short containerId = 2;
byte[] blob = TestUtils.getRandomBytes(10000);
byte[] userMetadata = TestUtils.getRandomBytes(2000);
StoreKey storeKey = new MockId("012345678910123456789012");
BlobProperties properties = new BlobProperties(blob.length, serviceId, ownerId, contentType, false, 100, accountId, containerId, encryptionKey != null, null, null, null);
MessageFormatInputStream putStream;
MessageFormatRecord.MessageHeader_Format header;
if (putFormat.equals(PutMessageFormatInputStream.class.getSimpleName())) {
header = getHeader(new PutMessageFormatInputStream(storeKey, encryptionKey == null ? null : encryptionKey.duplicate(), properties, ByteBuffer.wrap(userMetadata), new ByteBufferInputStream(ByteBuffer.wrap(blob)), blob.length, BlobType.DataBlob));
putStream = new PutMessageFormatInputStream(storeKey, encryptionKey, properties, ByteBuffer.wrap(userMetadata), new ByteBufferInputStream(ByteBuffer.wrap(blob)), blob.length, BlobType.DataBlob);
} else {
header = getHeader(new PutMessageFormatBlobV1InputStream(storeKey, properties, ByteBuffer.wrap(userMetadata), new ByteBufferInputStream(ByteBuffer.wrap(blob)), blob.length, BlobType.DataBlob));
putStream = new PutMessageFormatBlobV1InputStream(storeKey, properties, ByteBuffer.wrap(userMetadata), new ByteBufferInputStream(ByteBuffer.wrap(blob)), blob.length, BlobType.DataBlob);
}
ByteBuffer buf1 = ByteBuffer.allocate((int) putStream.getSize());
putStream.read(buf1.array());
ArrayList<ByteBuffer> listbuf = new ArrayList<ByteBuffer>();
listbuf.add(buf1);
ArrayList<StoreKey> storeKeys = new ArrayList<StoreKey>();
storeKeys.add(storeKey);
MockMessageReadSet readSet = new MockMessageReadSet(listbuf, storeKeys);
MetricRegistry registry = new MetricRegistry();
MessageFormatMetrics metrics = new MessageFormatMetrics(registry);
// get all
MessageFormatSend send = new MessageFormatSend(readSet, MessageFormatFlags.All, metrics, new MockIdFactory());
Assert.assertEquals(send.sizeInBytes(), putStream.getSize());
Assert.assertEquals(1, send.getMessageMetadataList().size());
Assert.assertEquals(null, send.getMessageMetadataList().get(0));
ByteBuffer bufresult = ByteBuffer.allocate((int) putStream.getSize());
WritableByteChannel channel = Channels.newChannel(new ByteBufferOutputStream(bufresult));
while (!send.isSendComplete()) {
send.writeTo(channel);
}
Assert.assertArrayEquals(buf1.array(), bufresult.array());
Assert.assertTrue(readSet.isPrefetchInfoCorrect(0, readSet.sizeInBytes(0)));
// get blob
send = new MessageFormatSend(readSet, MessageFormatFlags.Blob, metrics, new MockIdFactory());
long blobRecordSize = putFormat.equals(PutMessageFormatInputStream.class.getSimpleName()) ? MessageFormatRecord.Blob_Format_V2.getBlobRecordSize(blob.length) : MessageFormatRecord.Blob_Format_V1.getBlobRecordSize(blob.length);
Assert.assertEquals(send.sizeInBytes(), blobRecordSize);
bufresult.clear();
channel = Channels.newChannel(new ByteBufferOutputStream(bufresult));
while (!send.isSendComplete()) {
send.writeTo(channel);
}
for (int i = 0; i < blob.length; i++) {
Assert.assertEquals(blob[i], bufresult.array()[i + (int) blobRecordSize - MessageFormatRecord.Crc_Size - blob.length]);
}
if (expectedEncryptionKey == null) {
Assert.assertEquals(null, send.getMessageMetadataList().get(0));
} else {
Assert.assertEquals(expectedEncryptionKey, send.getMessageMetadataList().get(0).getEncryptionKey());
}
Assert.assertTrue(readSet.isPrefetchInfoCorrect(0, readSet.sizeInBytes(0)));
// get user metadata
send = new MessageFormatSend(readSet, MessageFormatFlags.BlobUserMetadata, metrics, new MockIdFactory());
long userMetadataRecordSize = MessageFormatRecord.UserMetadata_Format_V1.getUserMetadataSize(ByteBuffer.wrap(userMetadata));
Assert.assertEquals(send.sizeInBytes(), userMetadataRecordSize);
bufresult.clear();
channel = Channels.newChannel(new ByteBufferOutputStream(bufresult));
while (!send.isSendComplete()) {
send.writeTo(channel);
}
bufresult.flip();
// read off the header.
for (int i = 0; i < userMetadataRecordSize - MessageFormatRecord.Crc_Size - userMetadata.length; i++) {
bufresult.get();
}
verifyBlobUserMetadata(userMetadata, bufresult);
if (expectedEncryptionKey == null) {
Assert.assertEquals(null, send.getMessageMetadataList().get(0));
} else {
Assert.assertEquals(expectedEncryptionKey, send.getMessageMetadataList().get(0).getEncryptionKey());
}
Assert.assertTrue(readSet.isPrefetchInfoCorrect(header.getUserMetadataRecordRelativeOffset(), header.getUserMetadataRecordSize()));
// get blob properties
send = new MessageFormatSend(readSet, MessageFormatFlags.BlobProperties, metrics, new MockIdFactory());
long blobPropertiesRecordSize = MessageFormatRecord.BlobProperties_Format_V1.getBlobPropertiesRecordSize(properties);
Assert.assertEquals(send.sizeInBytes(), blobPropertiesRecordSize);
bufresult.clear();
channel = Channels.newChannel(new ByteBufferOutputStream(bufresult));
while (!send.isSendComplete()) {
send.writeTo(channel);
}
bufresult.flip();
// read off the header.
for (int i = 0; i < blobPropertiesRecordSize - MessageFormatRecord.Crc_Size - BlobPropertiesSerDe.getBlobPropertiesSerDeSize(properties); i++) {
bufresult.get();
}
verifyBlobProperties(properties, BlobPropertiesSerDe.getBlobPropertiesFromStream(new DataInputStream(new ByteBufferInputStream(bufresult))));
Assert.assertEquals(null, send.getMessageMetadataList().get(0));
Assert.assertTrue(readSet.isPrefetchInfoCorrect(header.getBlobPropertiesRecordRelativeOffset(), header.getBlobPropertiesRecordSize()));
// get blob info
send = new MessageFormatSend(readSet, MessageFormatFlags.BlobInfo, metrics, new MockIdFactory());
Assert.assertEquals(send.sizeInBytes(), blobPropertiesRecordSize + userMetadataRecordSize);
bufresult.clear();
channel = Channels.newChannel(new ByteBufferOutputStream(bufresult));
while (!send.isSendComplete()) {
send.writeTo(channel);
}
bufresult.flip();
for (int i = 0; i < blobPropertiesRecordSize - MessageFormatRecord.Crc_Size - BlobPropertiesSerDe.getBlobPropertiesSerDeSize(properties); i++) {
bufresult.get();
}
verifyBlobProperties(properties, BlobPropertiesSerDe.getBlobPropertiesFromStream(new DataInputStream(new ByteBufferInputStream(bufresult))));
for (int i = 0; i < userMetadataRecordSize - userMetadata.length; i++) {
bufresult.get();
}
verifyBlobUserMetadata(userMetadata, bufresult);
if (expectedEncryptionKey == null) {
Assert.assertEquals(null, send.getMessageMetadataList().get(0));
} else {
Assert.assertEquals(expectedEncryptionKey, send.getMessageMetadataList().get(0).getEncryptionKey());
}
Assert.assertTrue(readSet.isPrefetchInfoCorrect(header.getBlobPropertiesRecordRelativeOffset(), header.getBlobPropertiesRecordSize() + header.getUserMetadataRecordSize()));
}
use of com.github.ambry.store.StoreKey in project ambry by linkedin.
the class MessageFormatSendTest method sendWriteCompositeMessagesTest.
/**
* Tests involving multiple messages in a single Send involving different combinations of header format versions, put
* formats and encryption keys.
*/
@Test
public void sendWriteCompositeMessagesTest() throws Exception {
short savedVersion = MessageFormatRecord.headerVersionToUse;
if (!putFormat.equals(PutMessageFormatInputStream.class.getSimpleName())) {
return;
}
String putFormat1 = PutMessageFormatBlobV1InputStream.class.getSimpleName();
String putFormat2 = PutMessageFormatInputStream.class.getSimpleName();
short headerFormatV1 = MessageFormatRecord.Message_Header_Version_V1;
short headerFormatV2 = MessageFormatRecord.Message_Header_Version_V2;
short headerFormatV3 = MessageFormatRecord.Message_Header_Version_V3;
byte[][] blob = { TestUtils.getRandomBytes(1000), TestUtils.getRandomBytes(2000), TestUtils.getRandomBytes(10000), TestUtils.getRandomBytes(20000), TestUtils.getRandomBytes(40000) };
byte[][] userMetadata = { TestUtils.getRandomBytes(200), TestUtils.getRandomBytes(400), TestUtils.getRandomBytes(2000), TestUtils.getRandomBytes(4000), TestUtils.getRandomBytes(8000) };
StoreKey[] storeKeys = { new MockId("64"), new MockId("32"), new MockId("16"), new MockId("08"), new MockId("04") };
ByteBuffer[] encryptionKeys = { ByteBuffer.wrap(TestUtils.getRandomBytes(64)), ByteBuffer.wrap(TestUtils.getRandomBytes(128)), ByteBuffer.wrap(TestUtils.getRandomBytes(256)), ByteBuffer.wrap(TestUtils.getRandomBytes(512)), ByteBuffer.wrap(TestUtils.getRandomBytes(1024)) };
String[] putFormat1s = { putFormat1, putFormat1, putFormat1, putFormat1, putFormat1 };
String[] putFormat2s = { putFormat2, putFormat2, putFormat2, putFormat2, putFormat2 };
String[] putFormatComposite1 = { putFormat1, putFormat2, putFormat2, putFormat2, putFormat1 };
String[] putFormatComposite2 = { putFormat2, putFormat1, putFormat1, putFormat2, putFormat2 };
short[] headerFormat1s = { headerFormatV1, headerFormatV1, headerFormatV1, headerFormatV1, headerFormatV1 };
short[] headerFormat2s = { headerFormatV2, headerFormatV2, headerFormatV2, headerFormatV2, headerFormatV2 };
short[] headerFormat3s = { headerFormatV3, headerFormatV3, headerFormatV3, headerFormatV3, headerFormatV3 };
short[] headerFormatComposite1 = { headerFormatV1, headerFormatV2, headerFormatV2, headerFormatV1, headerFormatV1 };
short[] headerFormatComposite2 = { headerFormatV2, headerFormatV1, headerFormatV1, headerFormatV2, headerFormatV2 };
short[] headerFormatComposite3 = { headerFormatV3, headerFormatV2, headerFormatV2, headerFormatV3, headerFormatV3 };
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat1s, headerFormat1s);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormat1s);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormat2s);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormat3s);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormatComposite1);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormatComposite2);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormat2s, headerFormatComposite3);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormatComposite1, headerFormatComposite1);
doSendWriteCompositeMessagesTest(blob, userMetadata, storeKeys, encryptionKeys, putFormatComposite2, headerFormatComposite2);
MessageFormatRecord.headerVersionToUse = savedVersion;
}
use of com.github.ambry.store.StoreKey in project ambry by linkedin.
the class ValidatingKeyConvertingTransformer method convert.
@Override
public Map<StoreKey, StoreKey> convert(Collection<? extends StoreKey> input) {
Map<StoreKey, StoreKey> output = new HashMap<>();
input.forEach(inKey -> {
if (onceConverted.containsKey(inKey)) {
output.put(inKey, onceConverted.get(inKey));
} else {
StoreKey replaceMent = invalids.contains(inKey) ? null : new MockId(inKey.getID().substring(0, inKey.getID().length() / 2) + Integer.toString(RANDOM.nextInt(1000)));
onceConverted.put(inKey, replaceMent);
output.put(inKey, replaceMent);
}
});
return output;
}
use of com.github.ambry.store.StoreKey in project ambry by linkedin.
the class MessageFormatRecordTest method getKeys.
private List<StoreKey> getKeys(int keySize, int numberOfKeys) {
List<StoreKey> keys = new ArrayList<StoreKey>();
for (int i = 0; i < numberOfKeys; i++) {
MockId mockId = new MockId(TestUtils.getRandomString(keySize));
keys.add(mockId);
}
return keys;
}
Aggregations