package crmdna.counter; import com.googlecode.objectify.VoidWork; import crmdna.common.Utils; import crmdna.common.api.APIException; import crmdna.common.api.APIResponse.Status; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Random; import static crmdna.common.OfyService.ofy; class CounterCore { Random random = new Random(); int numShards; // once set for a counter cannot be changed protected CounterCore(int numShards) { if (numShards < 0) Utils.throwIncorrectSpecException("numShards cannot be negative"); final int MAX_SHARDS = 100; if (numShards > MAX_SHARDS) Utils.throwIncorrectSpecException("Only [" + MAX_SHARDS + "] allowed"); this.numShards = numShards; } private String getShardKey(String counterName, int shardIndex) { return counterName + "_shard" + shardIndex; } public void increment(final String namespace, String counterName, final int n) { int shardIndex = random.nextInt(numShards); final String key = getShardKey(counterName, shardIndex); final int MAX_KEY_LENGTH = 100; if (key.length() > MAX_KEY_LENGTH) throw new APIException().status(Status.ERROR_RESOURCE_INCORRECT).message( "Counter key [" + key + "] is more than [" + MAX_KEY_LENGTH + "] chars"); ofy(namespace).transact(new VoidWork() { @Override public void vrun() { ShardEntity se = ofy(namespace).load().type(ShardEntity.class).id(key).now(); if (se == null) { se = new ShardEntity(); se.key = key; se.count = n; } else { se.count += n; } ofy(namespace).save().entity(se); } }); } public long getCount(String namespace, String counterName) { List<String> keys = new ArrayList<>(); for (int shardIndex = 0; shardIndex < numShards; shardIndex++) { keys.add(getShardKey(counterName, shardIndex)); } Map<String, ShardEntity> map = ofy(namespace).load().type(ShardEntity.class).ids(keys); long count = 0; for (String key : map.keySet()) { ShardEntity se = map.get(key); if (se != null) count += se.count; } return count; } }