use of org.apache.geode.internal.cache.tier.sockets.CacheClientProxy in project geode by apache.
the class Simple2CacheServerDUnitTest method checkProxyIsPrimary.
private boolean checkProxyIsPrimary(VM vm) {
SerializableCallable checkProxyIsPrimary = new SerializableCallable() {
@Override
public Object call() throws Exception {
final CacheClientNotifier ccn = CacheClientNotifier.getInstance();
Awaitility.waitAtMost(20, TimeUnit.SECONDS).until(() -> {
return (ccn.getClientProxies().size() == 1);
});
Iterator iter_prox = ccn.getClientProxies().iterator();
CacheClientProxy proxy = (CacheClientProxy) iter_prox.next();
return proxy.isPrimary();
}
};
return (Boolean) vm.invoke(checkProxyIsPrimary);
}
use of org.apache.geode.internal.cache.tier.sockets.CacheClientProxy in project geode by apache.
the class Bug51400DUnitTest method verifyQueueSize.
public static void verifyQueueSize(Boolean isPrimary, Integer numOfEvents) throws Exception {
CacheClientProxyStats stats = ((CacheClientProxy) CacheClientNotifier.getInstance().getClientProxies().toArray()[0]).getStatistics();
if (isPrimary) {
// marker
numOfEvents = numOfEvents + 1;
}
long qSize = stats.getMessageQueueSize();
assertEquals("Expected queue size: " + numOfEvents + " but actual size: " + qSize + " at " + (isPrimary ? "primary." : "secondary."), numOfEvents.intValue(), qSize);
}
use of org.apache.geode.internal.cache.tier.sockets.CacheClientProxy in project geode by apache.
the class RegisterInterest method cmdExecute.
@Override
public void cmdExecute(Message clientMessage, ServerConnection serverConnection, long start) throws IOException, InterruptedException {
Part regionNamePart = null, keyPart = null;
String regionName = null;
Object key = null;
serverConnection.setAsTrue(REQUIRES_RESPONSE);
serverConnection.setAsTrue(REQUIRES_CHUNKED_RESPONSE);
// bserverStats.incLong(readDestroyRequestTimeId,
// DistributionStats.getStatTime() - start);
// bserverStats.incInt(destroyRequestsId, 1);
// start = DistributionStats.getStatTime();
// Retrieve the data from the message parts
regionNamePart = clientMessage.getPart(0);
regionName = regionNamePart.getString();
InterestResultPolicy policy = null;
// Retrieve the interest type
int interestType = clientMessage.getPart(1).getInt();
// Retrieve the InterestResultPolicy
try {
policy = (InterestResultPolicy) clientMessage.getPart(2).getObject();
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean isDurable = false;
try {
Part durablePart = clientMessage.getPart(3);
byte[] durablePartBytes = (byte[]) durablePart.getObject();
isDurable = durablePartBytes[0] == 0x01;
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// Retrieve the key
keyPart = clientMessage.getPart(4);
regionName = regionNamePart.getString();
try {
key = keyPart.getStringOrObject();
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean sendUpdatesAsInvalidates = false;
// time being until refactoring into a new command version.
if (clientMessage.getNumberOfParts() > 5) {
try {
Part notifyPart = clientMessage.getPart(5);
byte[] notifyPartBytes = (byte[]) notifyPart.getObject();
sendUpdatesAsInvalidates = notifyPartBytes[0] == 0x01;
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
}
if (logger.isDebugEnabled()) {
logger.debug("{}: Received register interest request ({} bytes) from {} for region {} key {}", serverConnection.getName(), clientMessage.getPayloadLength(), serverConnection.getSocketString(), regionName, key);
}
// Process the register interest request
if (key == null || regionName == null) {
StringId message = null;
if (key == null) {
message = LocalizedStrings.RegisterInterest_THE_INPUT_KEY_FOR_THE_REGISTER_INTEREST_REQUEST_IS_NULL;
}
if (regionName == null) {
message = LocalizedStrings.RegisterInterest_THE_INPUT_REGION_NAME_FOR_THE_REGISTER_INTEREST_REQUEST_IS_NULL;
}
logger.warn("{}: {}", serverConnection.getName(), message.toLocalizedString());
writeChunkedErrorResponse(clientMessage, MessageType.REGISTER_INTEREST_DATA_ERROR, message.toLocalizedString(), serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// input key not null
LocalRegion region = (LocalRegion) serverConnection.getCache().getRegion(regionName);
if (region == null) {
logger.info(LocalizedMessage.create(LocalizedStrings.RegisterInterest_0_REGION_NAMED_1_WAS_NOT_FOUND_DURING_REGISTER_INTEREST_REQUEST, new Object[] { serverConnection.getName(), regionName }));
// writeChunkedErrorResponse(msg,
// MessageType.REGISTER_INTEREST_DATA_ERROR, message);
// responded = true;
}
// Register interest
try {
if (interestType == InterestType.REGULAR_EXPRESSION) {
this.securityService.authorizeRegionRead(regionName);
} else {
this.securityService.authorizeRegionRead(regionName, key.toString());
}
AuthorizeRequest authzRequest = serverConnection.getAuthzRequest();
if (authzRequest != null) {
if (!DynamicRegionFactory.regionIsDynamicRegionList(regionName)) {
RegisterInterestOperationContext registerContext = authzRequest.registerInterestAuthorize(regionName, key, interestType, policy);
key = registerContext.getKey();
}
}
serverConnection.getAcceptor().getCacheClientNotifier().registerClientInterest(regionName, key, serverConnection.getProxyID(), interestType, isDurable, sendUpdatesAsInvalidates, false, 0, true);
} catch (Exception e) {
// If an interrupted exception is thrown , rethrow it
checkForInterrupt(serverConnection, e);
// Otherwise, write an exception message and continue
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// System.out.println("Received register interest for " + regionName);
// Update the statistics and write the reply
// bserverStats.incLong(processDestroyTimeId,
// DistributionStats.getStatTime() - start);
// start = DistributionStats.getStatTime();
CacheClientProxy ccp = serverConnection.getAcceptor().getCacheClientNotifier().getClientProxy(serverConnection.getProxyID());
if (ccp == null) {
// fix for 37593
IOException ioex = new IOException(LocalizedStrings.RegisterInterest_CACHECLIENTPROXY_FOR_THIS_CLIENT_IS_NO_LONGER_ON_THE_SERVER_SO_REGISTERINTEREST_OPERATION_IS_UNSUCCESSFUL.toLocalizedString());
writeChunkedException(clientMessage, ioex, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean isPrimary = ccp.isPrimary();
ChunkedMessage chunkedResponseMsg = serverConnection.getRegisterInterestResponseMessage();
if (!isPrimary) {
chunkedResponseMsg.setMessageType(MessageType.RESPONSE_FROM_SECONDARY);
chunkedResponseMsg.setTransactionId(clientMessage.getTransactionId());
chunkedResponseMsg.sendHeader();
chunkedResponseMsg.setLastChunk(true);
if (logger.isDebugEnabled()) {
logger.debug("{}: Sending register interest response chunk from secondary for region: {} for key: {} chunk=<{}>", serverConnection.getName(), regionName, key, chunkedResponseMsg);
}
chunkedResponseMsg.sendChunk(serverConnection);
} else // !isPrimary
{
// isPrimary
// Send header which describes how many chunks will follow
chunkedResponseMsg.setMessageType(MessageType.RESPONSE_FROM_PRIMARY);
chunkedResponseMsg.setTransactionId(clientMessage.getTransactionId());
chunkedResponseMsg.sendHeader();
// Send chunk response
try {
fillAndSendRegisterInterestResponseChunks(region, key, interestType, policy, serverConnection);
serverConnection.setAsTrue(RESPONDED);
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection, chunkedResponseMsg);
serverConnection.setAsTrue(RESPONDED);
return;
}
if (logger.isDebugEnabled()) {
// logger.debug(getName() + ": Sent chunk (1 of 1) of register interest
// response (" + chunkedResponseMsg.getBufferLength() + " bytes) for
// region " + regionName + " key " + key);
logger.debug("{}: Sent register interest response for region {} key {}", serverConnection.getName(), regionName, key);
}
// bserverStats.incLong(writeDestroyResponseTimeId,
// DistributionStats.getStatTime() - start);
// bserverStats.incInt(destroyResponsesId, 1);
}
// isPrimary
}
use of org.apache.geode.internal.cache.tier.sockets.CacheClientProxy in project geode by apache.
the class RegisterInterest61 method cmdExecute.
@Override
public void cmdExecute(Message clientMessage, ServerConnection serverConnection, long start) throws IOException, InterruptedException {
Part regionNamePart = null, keyPart = null;
String regionName = null;
Object key = null;
CachedRegionHelper crHelper = serverConnection.getCachedRegionHelper();
serverConnection.setAsTrue(REQUIRES_RESPONSE);
serverConnection.setAsTrue(REQUIRES_CHUNKED_RESPONSE);
// bserverStats.incLong(readDestroyRequestTimeId,
// DistributionStats.getStatTime() - start);
// bserverStats.incInt(destroyRequestsId, 1);
// start = DistributionStats.getStatTime();
// Retrieve the data from the message parts
regionNamePart = clientMessage.getPart(0);
regionName = regionNamePart.getString();
InterestResultPolicy policy = null;
// Retrieve the interest type
int interestType = clientMessage.getPart(1).getInt();
// Retrieve the InterestResultPolicy
try {
policy = (InterestResultPolicy) clientMessage.getPart(2).getObject();
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean isDurable = false;
try {
Part durablePart = clientMessage.getPart(3);
byte[] durablePartBytes = (byte[]) durablePart.getObject();
isDurable = durablePartBytes[0] == 0x01;
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// region data policy
byte[] regionDataPolicyPartBytes;
boolean serializeValues = false;
try {
Part regionDataPolicyPart = clientMessage.getPart(clientMessage.getNumberOfParts() - 1);
regionDataPolicyPartBytes = (byte[]) regionDataPolicyPart.getObject();
if (serverConnection.getClientVersion().compareTo(Version.GFE_80) >= 0) {
// The second byte here is serializeValues
serializeValues = regionDataPolicyPartBytes[1] == (byte) 0x01;
}
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// Retrieve the key
keyPart = clientMessage.getPart(4);
regionName = regionNamePart.getString();
try {
key = keyPart.getStringOrObject();
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean sendUpdatesAsInvalidates = false;
// time being until refactoring into a new command version.
if (clientMessage.getNumberOfParts() > 5) {
try {
Part notifyPart = clientMessage.getPart(5);
byte[] notifyPartBytes = (byte[]) notifyPart.getObject();
sendUpdatesAsInvalidates = notifyPartBytes[0] == 0x01;
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
}
if (logger.isDebugEnabled()) {
logger.debug("{}: Received register interest 61 request ({} bytes) from {} for region {} key {}", serverConnection.getName(), clientMessage.getPayloadLength(), serverConnection.getSocketString(), regionName, key);
}
if (VMOTION_DURING_REGISTER_INTEREST_FLAG) {
VMotionObserver vmo = VMotionObserverHolder.getInstance();
vmo.vMotionBeforeRegisterInterest();
}
// Process the register interest request
if (key == null || regionName == null) {
StringId message = null;
if (key == null) {
message = LocalizedStrings.RegisterInterest_THE_INPUT_KEY_FOR_THE_REGISTER_INTEREST_REQUEST_IS_NULL;
}
if (regionName == null) {
message = LocalizedStrings.RegisterInterest_THE_INPUT_REGION_NAME_FOR_THE_REGISTER_INTEREST_REQUEST_IS_NULL;
}
logger.warn("{}: {}", serverConnection.getName(), message.toLocalizedString());
writeChunkedErrorResponse(clientMessage, MessageType.REGISTER_INTEREST_DATA_ERROR, message.toLocalizedString(), serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// input key not null
LocalRegion region = (LocalRegion) serverConnection.getCache().getRegion(regionName);
if (region == null) {
logger.info(LocalizedMessage.create(LocalizedStrings.RegisterInterest_0_REGION_NAMED_1_WAS_NOT_FOUND_DURING_REGISTER_INTEREST_REQUEST, new Object[] { serverConnection.getName(), regionName }));
// writeChunkedErrorResponse(msg,
// MessageType.REGISTER_INTEREST_DATA_ERROR, message);
// responded = true;
}
// Register interest
try {
if (interestType == InterestType.REGULAR_EXPRESSION) {
this.securityService.authorizeRegionRead(regionName);
} else {
this.securityService.authorizeRegionRead(regionName, key.toString());
}
AuthorizeRequest authzRequest = serverConnection.getAuthzRequest();
if (authzRequest != null) {
if (!DynamicRegionFactory.regionIsDynamicRegionList(regionName)) {
RegisterInterestOperationContext registerContext = authzRequest.registerInterestAuthorize(regionName, key, interestType, policy);
key = registerContext.getKey();
}
}
serverConnection.getAcceptor().getCacheClientNotifier().registerClientInterest(regionName, key, serverConnection.getProxyID(), interestType, isDurable, sendUpdatesAsInvalidates, true, regionDataPolicyPartBytes[0], true);
} catch (Exception e) {
// If an interrupted exception is thrown , rethrow it
checkForInterrupt(serverConnection, e);
// Otherwise, write an exception message and continue
writeChunkedException(clientMessage, e, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
// System.out.println("Received register interest for " + regionName);
// Update the statistics and write the reply
// bserverStats.incLong(processDestroyTimeId,
// DistributionStats.getStatTime() - start);
// start = DistributionStats.getStatTime();
CacheClientProxy ccp = serverConnection.getAcceptor().getCacheClientNotifier().getClientProxy(serverConnection.getProxyID());
if (ccp == null) {
// fix for 37593
IOException ioex = new IOException(LocalizedStrings.RegisterInterest_CACHECLIENTPROXY_FOR_THIS_CLIENT_IS_NO_LONGER_ON_THE_SERVER_SO_REGISTERINTEREST_OPERATION_IS_UNSUCCESSFUL.toLocalizedString());
writeChunkedException(clientMessage, ioex, serverConnection);
serverConnection.setAsTrue(RESPONDED);
return;
}
boolean isPrimary = ccp.isPrimary();
ChunkedMessage chunkedResponseMsg = serverConnection.getRegisterInterestResponseMessage();
if (!isPrimary) {
chunkedResponseMsg.setMessageType(MessageType.RESPONSE_FROM_SECONDARY);
chunkedResponseMsg.setTransactionId(clientMessage.getTransactionId());
chunkedResponseMsg.sendHeader();
chunkedResponseMsg.setLastChunk(true);
if (logger.isDebugEnabled()) {
logger.debug("{}: Sending register interest response chunk from secondary for region: {} for key: {} chunk=<{}>", serverConnection.getName(), regionName, key, chunkedResponseMsg);
}
chunkedResponseMsg.sendChunk(serverConnection);
} else // !isPrimary
{
// isPrimary
// Send header which describes how many chunks will follow
chunkedResponseMsg.setMessageType(MessageType.RESPONSE_FROM_PRIMARY);
chunkedResponseMsg.setTransactionId(clientMessage.getTransactionId());
chunkedResponseMsg.sendHeader();
// Send chunk response
try {
fillAndSendRegisterInterestResponseChunks(region, key, interestType, serializeValues, policy, serverConnection);
serverConnection.setAsTrue(RESPONDED);
} catch (Exception e) {
writeChunkedException(clientMessage, e, serverConnection, chunkedResponseMsg);
serverConnection.setAsTrue(RESPONDED);
return;
}
if (logger.isDebugEnabled()) {
// logger.debug(getName() + ": Sent chunk (1 of 1) of register interest
// response (" + chunkedResponseMsg.getBufferLength() + " bytes) for
// region " + regionName + " key " + key);
logger.debug("{}: Sent register interest response for region {} key {}", serverConnection.getName(), regionName, key);
}
// bserverStats.incLong(writeDestroyResponseTimeId,
// DistributionStats.getStatTime() - start);
// bserverStats.incInt(destroyResponsesId, 1);
}
// isPrimary
}
use of org.apache.geode.internal.cache.tier.sockets.CacheClientProxy in project geode by apache.
the class Bug38741DUnitTest method testCopyOnReadWithBridgeServer.
/**
* Test that CopyOnRead doesn't cause {@link HARegionQueue#peek()} to create a copy, assuming that
* creating copies performs a serialize and de-serialize operation.
*
* @throws Exception when there is a failure
* @since GemFire bugfix5.7
*/
@Test
public void testCopyOnReadWithBridgeServer() throws Exception {
final Host h = Host.getHost(0);
final VM client = h.getVM(2);
final VM server = h.getVM(3);
final String rName = getUniqueName();
final int[] ports = createUniquePorts(1);
final String k1 = "k1";
final String k2 = "k2";
final String k3 = "k3";
createBridgeServer(server, rName, ports[0]);
// Put an instance of SerializationCounter to assert copy-on-read behavior
// when notifyBySubscription is true
server.invoke(new CacheSerializableRunnable("Enable copy on read and assert server copy behavior") {
public void run2() throws CacheException {
final LocalRegion r = (LocalRegion) getRootRegion(rName);
// Using a key that counts serialization, the test captures
// any serialization of the key when it is a member of another object,
// specifically in this case ClientUpdateMessageImpl which is assume to be
// the value of a HARegion
SerializationCountingKey key = new SerializationCountingKey(k1);
byte[] val = new byte[1];
byte valIsObj = 0x01;
Integer cb = new Integer(0);
ClientProxyMembershipID cpmi = null;
EventID eid = null;
ClientUpdateMessageImpl cui = new ClientUpdateMessageImpl(EnumListenerEvent.AFTER_CREATE, r, key, val, valIsObj, cb, cpmi, eid);
ClientUpdateMessageImpl cuiCopy = (ClientUpdateMessageImpl) CopyHelper.copy(cui);
assertSame(key, cui.getKeyOfInterest());
assertEquals(1, key.count.get());
key = (SerializationCountingKey) cuiCopy.getKeyOfInterest();
assertEquals(cui.getKeyOfInterest(), cuiCopy.getKeyOfInterest());
assertEquals(1, key.count.get());
SerializationCountingKey ks1 = new SerializationCountingKey(k1);
{
// Make sure nothing (HARegion) has serialized/de-serialized this instance
SerializationCountingValue sc = new SerializationCountingValue();
r.put(ks1, sc);
assertEquals(0, sc.count.get());
assertEquals(0, ks1.count.get());
}
{
// No copy should be made upon get (assert standard no copy behavior)
SerializationCountingValue sc = (SerializationCountingValue) r.get(ks1);
assertEquals(0, sc.count.get());
assertEquals(0, ks1.count.get());
}
// enable copy on read
getCache().setCopyOnRead(true);
{
// Assert standard copy on read behavior
SerializationCountingValue sc = (SerializationCountingValue) r.get(ks1);
assertEquals(1, sc.count.get());
assertEquals(0, ks1.count.get());
}
{
// Put another counter with copy-on-read true
// Again check that nothing (HARegion) has performed serialization
SerializationCountingValue sc = new SerializationCountingValue();
SerializationCountingKey ks3 = new SerializationCountingKey(k3);
r.put(ks3, sc);
assertEquals(0, sc.count.get());
assertEquals(0, ks3.count.get());
}
}
});
// Setup a client which subscribes to the server region, registers (aka pulls)
// interest in keys which creates an assumed HARegionQueue on the server
// (in the event that the above code didn't already create a HARegion)
final String serverHostName = NetworkUtils.getServerHostName(server.getHost());
client.invoke(new CacheSerializableRunnable("Assert server copy behavior from client") {
public void run2() throws CacheException {
getCache();
AttributesFactory factory = new AttributesFactory();
ClientServerTestCase.configureConnectionPool(factory, serverHostName, ports, true, -1, 1, null);
factory.setScope(Scope.LOCAL);
Region r = createRootRegion(rName, factory.create());
SerializationCountingKey ks1 = new SerializationCountingKey(k1);
SerializationCountingKey ks3 = new SerializationCountingKey(k3);
r.registerInterest(ks1, InterestResultPolicy.KEYS_VALUES);
// entry
r.registerInterest(new SerializationCountingKey(k2), InterestResultPolicy.KEYS_VALUES);
// shouldn't
// exist
// yet
r.registerInterest(ks3, InterestResultPolicy.KEYS_VALUES);
{
// Once for the get on the server, once to send the value to this client
SerializationCountingValue sc = (SerializationCountingValue) r.get(ks1);
assertEquals(2, sc.count.get());
}
{
// Once to send the value to this client
SerializationCountingValue sc = (SerializationCountingValue) r.get(ks3);
assertEquals(1, sc.count.get());
}
}
});
// Put an instance of SerializationCounter to assert copy-on-read behavior
// once a client has registered interest
server.invoke(new CacheSerializableRunnable("Assert copy behavior after client is setup") {
public void run2() throws CacheException {
Region r = getRootRegion(rName);
CacheServerImpl bsi = (CacheServerImpl) getCache().getCacheServers().iterator().next();
Collection cp = bsi.getAcceptor().getCacheClientNotifier().getClientProxies();
// Should only be one because only one client is connected
assertEquals(1, cp.size());
final CacheClientProxy ccp = (CacheClientProxy) cp.iterator().next();
// Wait for messages to drain to capture a stable "processed message count"
WaitCriterion ev = new WaitCriterion() {
public boolean done() {
return ccp.getHARegionQueue().size() == 0;
}
public String description() {
return "region queue never became empty";
}
};
Wait.waitForCriterion(ev, 60 * 1000, 200, true);
// Capture the current processed message count to know
// when the next message has been serialized
final int currMesgCount = ccp.getStatistics().getMessagesProcessed();
SerializationCountingKey ks2 = new SerializationCountingKey(k2);
SerializationCountingValue sc = new SerializationCountingValue();
// Update a key upon which the client has expressed interest,
// expect it to send an update message to the client
r.put(ks2, sc);
// Wait to know that the data has been at least serialized (possibly sent)
ev = new WaitCriterion() {
public boolean done() {
return ccp.getStatistics().getMessagesProcessed() != currMesgCount;
}
public String description() {
return null;
}
};
Wait.waitForCriterion(ev, 60 * 1000, 200, true);
// assert one serialization to send value to interested client
// more than one implies copy-on-read behavior (bad)
assertEquals(1, sc.count.get());
assertEquals(1, ks2.count.get());
}
});
// Double-check the serialization count in the event that the previous check
// missed the copy due to race conditions
client.invoke(new CacheSerializableRunnable("Assert copy behavior from client after update") {
public void run2() throws CacheException {
Region r = getRootRegion(rName);
{
// Once to send the value to this client via the updater thread
SerializationCountingKey ks2 = new SerializationCountingKey(k2);
// Wait for the update to arrive on to the Cache Client Updater
long start = NanoTimer.getTime();
final int maxSecs = 30;
while (!r.containsKey(ks2)) {
Wait.pause(100);
if ((NanoTimer.getTime() - start) > TimeUnit.SECONDS.toNanos(maxSecs)) {
fail("Waited over " + maxSecs + "s");
}
}
SerializationCountingValue sc = (SerializationCountingValue) r.getEntry(ks2).getValue();
assertEquals(1, sc.count.get());
}
}
});
}
Aggregations