
dev.fuxing.airtable.cache.AirtableCacheGuava Maven / Gradle / Ivy
Show all versions of airtable-cache Show documentation
package dev.fuxing.airtable.cache;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import dev.fuxing.airtable.AirtableApi;
import dev.fuxing.airtable.AirtableExecutor;
import dev.fuxing.airtable.AirtableRecord;
import dev.fuxing.airtable.AirtableTable;
import dev.fuxing.airtable.exceptions.AirtableApiException;
import org.apache.commons.lang3.StringUtils;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* Created by: Fuxing
* Date: 2019-04-23
* Time: 18:08
*/
public class AirtableCacheGuava implements AirtableCache {
private final Cache getCache;
private final Cache> queryCache;
private final AirtableTable table;
/**
* Use builder. This is a fallback for those that don't know how to use builder.
*
* AirtableCacheGuava need to construct it's own AirtableTable interface because it need to control how 429 auto try works.
* Default max items is 1200 for get & query because that's the limit for the free Plan.
*
* @param apiKey Airtable ApiKey
* @param base id of the app. (prefixed with 'app')
* @param table table name of the table in Airtable
*/
public AirtableCacheGuava(String apiKey, String base, String table) {
this((BuilderImpl) builder().apiKey(apiKey).base(base).table(table));
}
AirtableCacheGuava(BuilderImpl builder) {
AirtableApi api = new AirtableApi(builder.apiKey, AirtableExecutor.newInstanceTurbo());
this.table = api.base(builder.base).table(builder.table);
this.getCache = CacheBuilder.newBuilder()
.maximumSize(builder.getCacheSize)
.expireAfterAccess(builder.getDuration, builder.getTimeUnit)
.build();
this.queryCache = CacheBuilder.newBuilder()
.maximumSize(builder.queryCacheSize)
.expireAfterAccess(builder.queryDuration, builder.queryTimeUnit)
.build();
}
@Nullable
@Override
public AirtableRecord get(String recordId) throws AirtableApiException {
try {
AirtableRecord record = table.get(recordId);
if (record != null) {
getCache.put(recordId, record);
}
return record;
} catch (AirtableApiException e) {
AirtableRecord record = getCache.getIfPresent(recordId);
// if record exist and error is ignorable
if (record != null && isIgnorable(e)) {
return record;
}
throw e;
}
}
@Nonnull
public List query(AirtableTable.QuerySpec querySpec) throws AirtableApiException {
String cacheKey = StringUtils.trimToEmpty(querySpec.build().getQuery());
try {
AirtableTable.PaginationList list = table.list(querySpec);
if (!list.isEmpty()) {
queryCache.put(cacheKey, list);
}
return list;
} catch (AirtableApiException e) {
List list = queryCache.getIfPresent(cacheKey);
// if record exist and error is ignorable
if (list != null && isIgnorable(e)) {
return list;
}
throw e;
}
}
/**
*
* create(builder -> builder
* .apiKey("key...")
* .app("app...")
* .table("Table Name")
* )
*
*
* @param consumer for building, requires user to return at step Builder
* @return AirtableCache
*/
public static AirtableCache create(Function consumer) {
BuilderImpl builder = (BuilderImpl) consumer.apply(builder());
return new AirtableCacheGuava(builder);
}
/**
* @return AirtableCacheGuava builder
*/
public static Builder.StepKey builder() {
return new BuilderImpl();
}
static class BuilderImpl implements Builder, Builder.StepKey, Builder.StepBase, Builder.StepTable {
String apiKey;
String base;
String table;
int getCacheSize = 1200;
int getDuration = 12;
TimeUnit getTimeUnit = TimeUnit.HOURS;
int queryCacheSize = 1200;
int queryDuration = 12;
TimeUnit queryTimeUnit = TimeUnit.HOURS;
@Override
public StepBase apiKey(String apiKey) {
this.apiKey = Objects.requireNonNull(apiKey);
return this;
}
@Override
public StepTable app(String id) {
this.base = Objects.requireNonNull(id);
return this;
}
@Override
public Builder table(String table) {
this.table = Objects.requireNonNull(table);
return this;
}
@Override
public Builder withGet(int size, int duration, TimeUnit timeUnit) {
this.getCacheSize = size;
this.getDuration = duration;
this.getTimeUnit = timeUnit;
return this;
}
@Override
public Builder withQuery(int size, int duration, TimeUnit timeUnit) {
this.queryCacheSize = size;
this.queryDuration = duration;
this.queryTimeUnit = timeUnit;
return this;
}
}
/**
* AirtableCache with Guava builder.
*
* I saw somewhere someone used a fluent Step builder for force a configuration.
* I thought it's quite cool I wanted to give it a shot for this module.
*/
interface Builder {
interface StepKey {
/**
* @param apiKey Airtable ApiKey
* @return The next step, StepBase
*/
StepBase apiKey(String apiKey);
}
interface StepBase {
/**
* @param id of the app. (prefixed with 'app')
* @return The next step, StepTable
*/
default StepTable base(String id) {
return app(id);
}
/**
* @param id of the app. (prefixed with 'app')
* @return The next step, StepTable
*/
StepTable app(String id);
}
interface StepTable {
/**
* @param table table name of the table in Airtable
* @return the final step, Builder
*/
Builder table(String table);
}
/**
* @param size size of get cache
* @param duration duration of staying in get cache
* @param timeUnit timeUnit of staying in get cache
* @return the same Builder instance for fluent chaining
*/
Builder withGet(int size, int duration, TimeUnit timeUnit);
/**
* @param size size of query cache
* @param duration duration of staying in query cache
* @param timeUnit timeUnit of staying in query cache
* @return the same Builder instance for fluent chaining
*/
Builder withQuery(int size, int duration, TimeUnit timeUnit);
}
}