/** * This file is part of Graylog. * * Graylog is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Graylog is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Graylog. If not, see <http://www.gnu.org/licenses/>. */ package org.graylog2.lookup.caches; import com.google.auto.value.AutoValue; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import org.graylog.autovalue.WithBeanGetter; import org.graylog2.plugin.lookup.LookupCache; import org.graylog2.plugin.lookup.LookupCacheConfiguration; import org.graylog2.plugin.lookup.LookupDataAdapter; import org.graylog2.plugin.lookup.LookupResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Named; import javax.validation.constraints.Min; public class GuavaLookupCache extends LookupCache { private static final Logger LOG = LoggerFactory.getLogger(GuavaLookupCache.class); public static final String NAME = "guava_cache"; private final LoadingCache<Object, LookupResult> cache; @Inject public GuavaLookupCache(@Assisted LookupCacheConfiguration c, @Named("processbuffer_processors") int processorCount) { super(c); Config config = (Config) c; CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder(); // the theory is that typically only processors will affect the cache concurrency, whereas decorator usage is less critical builder.concurrencyLevel(processorCount) .recordStats(); builder.maximumSize(config.maxSize()); if (config.expireAfterAccess() > 0 && config.expireAfterAccessUnit() != null) { //noinspection ConstantConditions builder.expireAfterAccess(config.expireAfterAccess(), config.expireAfterAccessUnit()); } if (config.expireAfterWrite() > 0 && config.expireAfterWriteUnit() != null) { //noinspection ConstantConditions builder.expireAfterWrite(config.expireAfterWrite(), config.expireAfterWriteUnit()); } if (config.refreshAfterWrite() > 0 && config.refreshAfterWriteUnit() != null) { //noinspection ConstantConditions builder.refreshAfterWrite(config.refreshAfterWrite(), config.refreshAfterWriteUnit()); } cache = builder.build(new CacheLoader<Object, LookupResult>() { @Override public LookupResult load(@Nonnull Object key) throws Exception { return getLookupTable().dataAdapter().get(key); } }); } @Override public LookupResult get(Object key) { try { return cache.get(key); } catch (ExecutionException e) { LOG.warn("Loading value from data adapter failed, returning empty result", e); return LookupResult.empty(); } } @Override public void set(Object key, Object retrievedValue) { final LookupDataAdapter dataAdapter = getLookupTable().dataAdapter(); dataAdapter.set(key, retrievedValue); cache.put(key, dataAdapter.get(key)); } @Override public void purge() { cache.invalidateAll(); } @Override public void purge(Object key) { cache.invalidate(key); } public interface Factory extends LookupCache.Factory { @Override GuavaLookupCache create(LookupCacheConfiguration configuration); @Override Descriptor getDescriptor(); } public static class Descriptor extends LookupCache.Descriptor<GuavaLookupCache.Config> { public Descriptor() { super(NAME, GuavaLookupCache.Config.class); } @Override public Config defaultConfiguration() { return Config.builder() .type(NAME) .maxSize(1000) .expireAfterAccess(60) .expireAfterAccessUnit(TimeUnit.SECONDS) .expireAfterWrite(0) .refreshAfterWrite(0) .build(); } } @JsonAutoDetect @AutoValue @WithBeanGetter @JsonDeserialize(builder = AutoValue_GuavaLookupCache_Config.Builder.class) @JsonTypeName(NAME) public abstract static class Config implements LookupCacheConfiguration { @Min(0) @JsonProperty("max_size") public abstract int maxSize(); @Min(0) @JsonProperty("expire_after_access") public abstract long expireAfterAccess(); @Nullable @JsonProperty("expire_after_access_unit") public abstract TimeUnit expireAfterAccessUnit(); @Min(0) @JsonProperty("expire_after_write") public abstract long expireAfterWrite(); @Nullable @JsonProperty("expire_after_write_unit") public abstract TimeUnit expireAfterWriteUnit(); @Min(0) @JsonProperty("refresh_after_write") public abstract long refreshAfterWrite(); @Nullable @JsonProperty("refresh_after_write_unit") public abstract TimeUnit refreshAfterWriteUnit(); public static Builder builder() { return new AutoValue_GuavaLookupCache_Config.Builder(); } @AutoValue.Builder public abstract static class Builder { @JsonProperty("type") public abstract Builder type(String type); @JsonProperty("max_size") public abstract Builder maxSize(int maxSize); @JsonProperty("expire_after_access") public abstract Builder expireAfterAccess(long expireAfterAccess); @JsonProperty("expire_after_access_unit") public abstract Builder expireAfterAccessUnit(@Nullable TimeUnit expireAfterAccessUnit); @JsonProperty("expire_after_write") public abstract Builder expireAfterWrite(long expireAfterWrite); @JsonProperty("expire_after_write_unit") public abstract Builder expireAfterWriteUnit(@Nullable TimeUnit expireAfterWriteUnit); @JsonProperty("refresh_after_write") public abstract Builder refreshAfterWrite(long refreshAfterWrite); @JsonProperty("refresh_after_write_unit") public abstract Builder refreshAfterWriteUnit(@Nullable TimeUnit refreshAfterWriteUnit); public abstract Config build(); } } }