package com.anjlab.gae;
import java.util.Calendar;
import java.util.TimeZone;
import java.util.regex.Pattern;
import javax.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.anjlab.ping.services.Utils;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.MemcacheService;
import com.google.apphosting.api.ApiProxy.OverQuotaException;
public class QuotaDetails {
private static final Logger logger = LoggerFactory.getLogger(QuotaDetails.class);
public static final String DATASTORE_WRITE_EXCEPTION =
"The API call datastore_v\\d+\\.Put\\(\\) required more quota than is available.*";
public enum Quota {
DatastoreWrite
}
private Cache cache;
private MemcacheService memcache;
// Cache is a wrapper for MemcacheService, but cache instance may also
// be LocalMemorySoftCache instance, and we need both of them here
// to synchronize local cache with MemcacheService
public QuotaDetails(Cache cache, MemcacheService memcache) {
this.cache = cache;
this.memcache = memcache;
}
public boolean isQuotaLimited(Quota quota) {
Object marker = cache.get(quota);
if (marker == null || !(marker instanceof Long)) {
return false;
}
return true;
}
public void setQuotaLimited(Quota quota, boolean isLimited) {
if (isLimited) {
int minutesInMillis = 1000 * 60;
// Daily quotas are refreshed daily at midnight Pacific time.
// http://code.google.com/intl/ru/appengine/docs/quotas.html#Safety_Quotas_and_Billable_Quotas
TimeZone US_PACIFIC_TIME = TimeZone.getTimeZone("America/Los_Angeles");
Calendar cal = Calendar.getInstance(US_PACIFIC_TIME);
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH);
int date = cal.get(Calendar.DATE);
cal.clear();
cal.set(year, month, date);
long previousQuotaRefreshTimeMillis = cal.getTimeInMillis();
cal.add(Calendar.DATE, 1);
cal.add(Calendar.MINUTE, 5); // Wait 5 minutes after quota refresh
long nextQuotaRefreshTimeMillis = cal.getTimeInMillis();
long currentTimeMillis = System.currentTimeMillis();
Integer expirationDeltaMillis = (currentTimeMillis - previousQuotaRefreshTimeMillis) > (60 * minutesInMillis)
? (int) (nextQuotaRefreshTimeMillis - currentTimeMillis)
: (int) (5 * minutesInMillis);
logger.warn("Setting 'quota limited' marker for DatastoreWrite operations with expiration delta = {}",
Utils.formatMillisecondsToWordsUpToMinutes(expirationDeltaMillis));
Long expirationTimeMillis = currentTimeMillis + expirationDeltaMillis;
// Remove object from local cache
cache.remove(quota);
memcache.put(quota, expirationTimeMillis, Expiration.byDeltaMillis(expirationDeltaMillis));
} else {
// this will remove object from local cache, and from memcache
cache.remove(quota);
}
}
public void checkOverQuotaException(Exception e) {
if (e == null) {
return;
}
if (Utils.isCause(e, OverQuotaException.class)) {
if (Pattern.matches(DATASTORE_WRITE_EXCEPTION, e.getMessage())) {
setQuotaLimited(Quota.DatastoreWrite, true);
}
}
}
public boolean isQuotaLimited() {
return isQuotaLimited(Quota.DatastoreWrite);
}
public long getQuotaLimitExpirationMillis() {
Object marker = cache.get(Quota.DatastoreWrite);
if (marker == null || !(marker instanceof Long)) {
return 0;
}
return (Long) marker;
}
}