Declare caches in Guice rather than hardcoded in CacheManagerProvider
This refactoring makes caches easier to declare, as we now define them as Guice injections just like any other entity. Each of the Cache<K,V> instances is actually a proxy to the real Ehcache, thus allowing us to construct our object graph, discover the full set of caches we need, and then start the cache provider with only what we discovered was loaded. Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.pgm;
|
||||
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.config.GerritGlobalModule;
|
||||
import com.google.gerrit.server.ssh.SshDaemon;
|
||||
import com.google.gerrit.server.ssh.SshModule;
|
||||
@@ -25,6 +26,7 @@ public class Daemon extends AbstractProgram {
|
||||
public int run() throws Exception {
|
||||
Injector sysInjector = GerritGlobalModule.createInjector();
|
||||
Injector sshInjector = sysInjector.createChildInjector(new SshModule());
|
||||
sysInjector.getInstance(CachePool.class).start();
|
||||
sshInjector.getInstance(SshDaemon.class).start();
|
||||
return never();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.git.PatchSetImporter;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.config.GerritGlobalModule;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
@@ -66,6 +67,7 @@ public class ReimportPatchSets extends AbstractProgram {
|
||||
@Override
|
||||
public int run() throws Exception {
|
||||
final Injector injector = GerritGlobalModule.createInjector();
|
||||
injector.getInstance(CachePool.class).start();
|
||||
injector.injectMembers(this);
|
||||
|
||||
final ArrayList<PatchSet.Id> todo = new ArrayList<PatchSet.Id>();
|
||||
|
||||
@@ -17,20 +17,16 @@ package com.google.gerrit.server.account;
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountExternalId;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -39,24 +35,38 @@ import java.util.Set;
|
||||
/** Translates an email address to a set of matching accounts. */
|
||||
@Singleton
|
||||
public class AccountByEmailCache {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(AccountByEmailCache.class);
|
||||
private static final String CACHE_NAME = "accounts_byemail";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<String, Set<Account.Id>>> type =
|
||||
new TypeLiteral<Cache<String, Set<Account.Id>>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(AccountByEmailCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final SelfPopulatingCache self;
|
||||
private final SelfPopulatingCache<String, Set<Account.Id>> self;
|
||||
|
||||
@Inject
|
||||
AccountByEmailCache(final SchemaFactory<ReviewDb> sf, final CacheManager mgr) {
|
||||
schema = sf;
|
||||
|
||||
final Cache dc = mgr.getCache("accounts_byemail");
|
||||
self = new SelfPopulatingCache(dc, new CacheEntryFactory() {
|
||||
AccountByEmailCache(final SchemaFactory<ReviewDb> schema,
|
||||
@Named(CACHE_NAME) final Cache<String, Set<Account.Id>> rawCache) {
|
||||
this.schema = schema;
|
||||
this.self = new SelfPopulatingCache<String, Set<Account.Id>>(rawCache) {
|
||||
@Override
|
||||
public Object createEntry(final Object key) throws Exception {
|
||||
return lookup((String) key);
|
||||
protected Set<Account.Id> createEntry(final String key) throws Exception {
|
||||
return lookup(key);
|
||||
}
|
||||
});
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
|
||||
@Override
|
||||
protected Set<Account.Id> missing(final String key) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Set<Account.Id> lookup(final String email) throws OrmException {
|
||||
@@ -75,33 +85,12 @@ public class AccountByEmailCache {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<Account.Id> get(final String email) {
|
||||
if (email == null || email.isEmpty()) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
final Element m;
|
||||
try {
|
||||
m = self.get(email);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Cannot lookup email " + email, e);
|
||||
return Collections.emptySet();
|
||||
} catch (CacheException e) {
|
||||
log.error("Cannot lookup email " + email, e);
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
if (m == null || m.getObjectValue() == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
return (Set<Account.Id>) m.getObjectValue();
|
||||
return self.get(email);
|
||||
}
|
||||
|
||||
public void evict(final String email) {
|
||||
if (email != null && !email.isEmpty()) {
|
||||
self.remove(email);
|
||||
}
|
||||
self.remove(email);
|
||||
}
|
||||
|
||||
private static Set<Account.Id> pack(final Set<Account.Id> c) {
|
||||
|
||||
@@ -20,21 +20,17 @@ import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroupMember;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.client.reviewdb.SystemConfig;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -44,19 +40,31 @@ import java.util.Set;
|
||||
/** Caches important (but small) account state to avoid database hits. */
|
||||
@Singleton
|
||||
public class AccountCache {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(AccountCache.class);
|
||||
private static final String CACHE_NAME = "accounts";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<Account.Id, AccountState>> type =
|
||||
new TypeLiteral<Cache<Account.Id, AccountState>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(AccountCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final AuthConfig authConfig;
|
||||
private final SelfPopulatingCache self;
|
||||
private final SelfPopulatingCache<Account.Id, AccountState> self;
|
||||
|
||||
private final Set<AccountGroup.Id> registered;
|
||||
private final Set<AccountGroup.Id> anonymous;
|
||||
|
||||
@Inject
|
||||
AccountCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg,
|
||||
final AuthConfig ac, final CacheManager mgr) {
|
||||
final AuthConfig ac,
|
||||
@Named(CACHE_NAME) final Cache<Account.Id, AccountState> rawCache) {
|
||||
schema = sf;
|
||||
authConfig = ac;
|
||||
|
||||
@@ -66,14 +74,17 @@ public class AccountCache {
|
||||
registered = Collections.unmodifiableSet(r);
|
||||
anonymous = Collections.singleton(cfg.anonymousGroupId);
|
||||
|
||||
final Cache dc = mgr.getCache("accounts");
|
||||
self = new SelfPopulatingCache(dc, new CacheEntryFactory() {
|
||||
self = new SelfPopulatingCache<Account.Id, AccountState>(rawCache) {
|
||||
@Override
|
||||
public Object createEntry(final Object key) throws Exception {
|
||||
return lookup((Account.Id) key);
|
||||
protected AccountState createEntry(Account.Id key) throws Exception {
|
||||
return lookup(key);
|
||||
}
|
||||
});
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
|
||||
@Override
|
||||
protected AccountState missing(final Account.Id key) {
|
||||
return missingAccount(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private AccountState lookup(final Account.Id who) throws OrmException {
|
||||
@@ -83,7 +94,7 @@ public class AccountCache {
|
||||
if (account == null) {
|
||||
// Account no longer exists? They are anonymous.
|
||||
//
|
||||
return missing(who);
|
||||
return missingAccount(who);
|
||||
}
|
||||
|
||||
final List<AccountExternalId> ids =
|
||||
@@ -120,38 +131,17 @@ public class AccountCache {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AccountState get(final Account.Id accountId) {
|
||||
if (accountId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Element m;
|
||||
try {
|
||||
m = self.get(accountId);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Cannot lookup account for " + accountId, e);
|
||||
return missing(accountId);
|
||||
} catch (CacheException e) {
|
||||
log.error("Cannot lookup account for " + accountId, e);
|
||||
return missing(accountId);
|
||||
}
|
||||
|
||||
if (m == null || m.getObjectValue() == null) {
|
||||
return missing(accountId);
|
||||
}
|
||||
return (AccountState) m.getObjectValue();
|
||||
}
|
||||
|
||||
private AccountState missing(final Account.Id accountId) {
|
||||
private AccountState missingAccount(final Account.Id accountId) {
|
||||
final Account account = new Account(accountId);
|
||||
final Set<String> emails = Collections.emptySet();
|
||||
return new AccountState(account, anonymous, anonymous, emails);
|
||||
}
|
||||
|
||||
public AccountState get(final Account.Id accountId) {
|
||||
return self.get(accountId);
|
||||
}
|
||||
|
||||
public void evict(final Account.Id accountId) {
|
||||
if (accountId != null) {
|
||||
self.remove(accountId);
|
||||
}
|
||||
self.remove(accountId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,45 +17,57 @@ package com.google.gerrit.server.account;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.client.reviewdb.SystemConfig;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/** Tracks group objects in memory for effecient access. */
|
||||
/** Tracks group objects in memory for efficient access. */
|
||||
@Singleton
|
||||
public class GroupCache {
|
||||
private static final Logger log = LoggerFactory.getLogger(GroupCache.class);
|
||||
private static final String CACHE_NAME = "groups";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<AccountGroup.Id, AccountGroup>> type =
|
||||
new TypeLiteral<Cache<AccountGroup.Id, AccountGroup>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(GroupCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final SelfPopulatingCache self;
|
||||
private final SelfPopulatingCache<AccountGroup.Id, AccountGroup> byId;
|
||||
|
||||
private final AccountGroup.Id administrators;
|
||||
|
||||
@Inject
|
||||
GroupCache(final SchemaFactory<ReviewDb> sf, final SystemConfig cfg,
|
||||
final CacheManager mgr) {
|
||||
@Named(CACHE_NAME) final Cache<AccountGroup.Id, AccountGroup> rawCache) {
|
||||
schema = sf;
|
||||
administrators = cfg.adminGroupId;
|
||||
|
||||
final Cache dc = mgr.getCache("groups");
|
||||
self = new SelfPopulatingCache(dc, new CacheEntryFactory() {
|
||||
byId = new SelfPopulatingCache<AccountGroup.Id, AccountGroup>(rawCache) {
|
||||
@Override
|
||||
public Object createEntry(final Object key) throws Exception {
|
||||
return lookup((AccountGroup.Id) key);
|
||||
public AccountGroup createEntry(final AccountGroup.Id key)
|
||||
throws Exception {
|
||||
return lookup(key);
|
||||
}
|
||||
});
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
|
||||
@Override
|
||||
protected AccountGroup missing(final AccountGroup.Id key) {
|
||||
return missingGroup(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final AccountGroup.Id getAdministrators() {
|
||||
@@ -77,29 +89,6 @@ public class GroupCache {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AccountGroup get(final AccountGroup.Id groupId) {
|
||||
if (groupId == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Element m;
|
||||
try {
|
||||
m = self.get(groupId);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Cannot lookup group " + groupId, e);
|
||||
return missingGroup(groupId);
|
||||
} catch (CacheException e) {
|
||||
log.error("Cannot lookup effective groups for " + groupId, e);
|
||||
return missingGroup(groupId);
|
||||
}
|
||||
|
||||
if (m == null || m.getObjectValue() == null) {
|
||||
return missingGroup(groupId);
|
||||
}
|
||||
return (AccountGroup) m.getObjectValue();
|
||||
}
|
||||
|
||||
private AccountGroup missingGroup(final AccountGroup.Id groupId) {
|
||||
final AccountGroup.NameKey name =
|
||||
new AccountGroup.NameKey("Deleted Group" + groupId.toString());
|
||||
@@ -109,17 +98,19 @@ public class GroupCache {
|
||||
return g;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public AccountGroup get(final AccountGroup.Id groupId) {
|
||||
return byId.get(groupId);
|
||||
}
|
||||
|
||||
public void evict(final AccountGroup.Id groupId) {
|
||||
if (groupId != null) {
|
||||
self.remove(groupId);
|
||||
}
|
||||
byId.remove(groupId);
|
||||
}
|
||||
|
||||
public AccountGroup lookup(final String groupName) throws OrmException {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
final AccountGroup.NameKey nameKey =
|
||||
new AccountGroup.NameKey(groupName);
|
||||
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
|
||||
|
||||
final AccountGroup group = db.accountGroups().get(nameKey);
|
||||
if (group != null) {
|
||||
|
||||
43
src/main/java/com/google/gerrit/server/cache/Cache.java
vendored
Normal file
43
src/main/java/com/google/gerrit/server/cache/Cache.java
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
||||
/**
|
||||
* A fast in-memory and/or on-disk based cache.
|
||||
*
|
||||
* @type <K> type of key used to lookup entries in the cache.
|
||||
* @type <V> type of value stored within each cache entry.
|
||||
*/
|
||||
public interface Cache<K, V> {
|
||||
/** Get the element from the cache, or null if not stored in the cache. */
|
||||
public V get(K key);
|
||||
|
||||
/** Put one element into the cache, replacing any existing value. */
|
||||
public void put(K key, V value);
|
||||
|
||||
/** Remove any existing value from the cache, no-op if not present. */
|
||||
public void remove(K key);
|
||||
|
||||
/**
|
||||
* Get the time an idle (not accessed) element will survive in the cache.
|
||||
*
|
||||
* @param unit desired units of the return value.
|
||||
* @return time an item can live without being accessed before being purged.
|
||||
*/
|
||||
public long getTimeToIdle(TimeUnit unit);
|
||||
}
|
||||
101
src/main/java/com/google/gerrit/server/cache/CacheModule.java
vendored
Normal file
101
src/main/java/com/google/gerrit/server/cache/CacheModule.java
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Key;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Names;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Miniature DSL to support binding {@link Cache} instances in Guice.
|
||||
*/
|
||||
public abstract class CacheModule extends AbstractModule {
|
||||
/**
|
||||
* Declare an unnamed in-memory cache.
|
||||
*
|
||||
* @param <K> type of key used to lookup entries.
|
||||
* @param <V> type of value stored by the cache.
|
||||
* @param type type literal for the cache, this literal will be used to match
|
||||
* injection sites.
|
||||
* @return binding to describe the cache. Caller must set at least the name on
|
||||
* the returned binding.
|
||||
*/
|
||||
protected <K, V> UnnamedCacheBinding core(final TypeLiteral<Cache<K, V>> type) {
|
||||
return core(Key.get(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a named in-memory cache.
|
||||
*
|
||||
* @param <K> type of key used to lookup entries.
|
||||
* @param <V> type of value stored by the cache.
|
||||
* @param type type literal for the cache, this literal will be used to match
|
||||
* injection sites. Injection sites are matched by this type literal
|
||||
* and with {@code @Named} annotations.
|
||||
* @return binding to describe the cache.
|
||||
*/
|
||||
protected <K, V> NamedCacheBinding core(final TypeLiteral<Cache<K, V>> type,
|
||||
final String name) {
|
||||
return core(Key.get(type, Names.named(name))).name(name);
|
||||
}
|
||||
|
||||
private <K, V> UnnamedCacheBinding core(final Key<Cache<K, V>> key) {
|
||||
final boolean disk = false;
|
||||
final CacheProvider<K, V> b = new CacheProvider<K, V>(disk);
|
||||
bind(key).toProvider(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare an unnamed in-memory/on-disk cache.
|
||||
*
|
||||
* @param <K> type of key used to find entries, must be {@link Serializable}.
|
||||
* @param <V> type of value stored by the cache, must be {@link Serializable}.
|
||||
* @param type type literal for the cache, this literal will be used to match
|
||||
* injection sites. Injection sites are matched by this type literal
|
||||
* and with {@code @Named} annotations.
|
||||
* @return binding to describe the cache. Caller must set at least the name on
|
||||
* the returned binding.
|
||||
*/
|
||||
protected <K extends Serializable, V extends Serializable> UnnamedCacheBinding disk(
|
||||
final TypeLiteral<Cache<K, V>> type) {
|
||||
return disk(Key.get(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declare a named in-memory/on-disk cache.
|
||||
*
|
||||
* @param <K> type of key used to find entries, must be {@link Serializable}.
|
||||
* @param <V> type of value stored by the cache, must be {@link Serializable}.
|
||||
* @param type type literal for the cache, this literal will be used to match
|
||||
* injection sites. Injection sites are matched by this type literal
|
||||
* and with {@code @Named} annotations.
|
||||
* @return binding to describe the cache.
|
||||
*/
|
||||
protected <K extends Serializable, V extends Serializable> NamedCacheBinding disk(
|
||||
final TypeLiteral<Cache<K, V>> type, final String name) {
|
||||
return disk(Key.get(type, Names.named(name))).name(name);
|
||||
}
|
||||
|
||||
private <K, V> UnnamedCacheBinding disk(final Key<Cache<K, V>> key) {
|
||||
final boolean disk = true;
|
||||
final CacheProvider<K, V> b = new CacheProvider<K, V>(disk);
|
||||
bind(key).toProvider(b);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
229
src/main/java/com/google/gerrit/server/cache/CachePool.java
vendored
Normal file
229
src/main/java/com/google/gerrit/server/cache/CachePool.java
vendored
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gerrit.server.config.SitePath;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.ProvisionException;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.config.CacheConfiguration;
|
||||
import net.sf.ehcache.config.Configuration;
|
||||
import net.sf.ehcache.config.DiskStoreConfiguration;
|
||||
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spearce.jgit.lib.Config;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Pool of all declared caches created by {@link CacheModule}s. */
|
||||
@Singleton
|
||||
public class CachePool {
|
||||
private static final Logger log = LoggerFactory.getLogger(CachePool.class);
|
||||
|
||||
private final Config config;
|
||||
private final File sitePath;
|
||||
|
||||
private final Object lock = new Object();
|
||||
private final Map<String, CacheProvider<?, ?>> caches;
|
||||
private CacheManager manager;
|
||||
|
||||
@Inject
|
||||
CachePool(@GerritServerConfig final Config cfg, @SitePath final File sitePath) {
|
||||
this.config = cfg;
|
||||
this.sitePath = sitePath;
|
||||
this.caches = new HashMap<String, CacheProvider<?, ?>>();
|
||||
}
|
||||
|
||||
/** Start the cache pool. The pool must be started before any access occurs. */
|
||||
public void start() {
|
||||
synchronized (lock) {
|
||||
if (manager != null) {
|
||||
throw new IllegalStateException("Cache pool has already been started");
|
||||
}
|
||||
|
||||
manager = new CacheManager(new Factory().toConfiguration());
|
||||
for (CacheProvider<?, ?> p : caches.values()) {
|
||||
p.bind(manager.getEhcache(p.getName()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Stop the cache pool. The pool should be stopped before terminating. */
|
||||
public void stop() {
|
||||
synchronized (lock) {
|
||||
if (manager != null) {
|
||||
manager.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** <i>Discouraged</i> Get the underlying cache descriptions, for statistics. */
|
||||
public CacheManager getCacheManager() {
|
||||
synchronized (lock) {
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
|
||||
<K, V> ProxyEhcache register(final CacheProvider<K, V> provider) {
|
||||
synchronized (lock) {
|
||||
if (manager != null) {
|
||||
throw new IllegalStateException("Cache pool has already been started");
|
||||
}
|
||||
|
||||
final String n = provider.getName();
|
||||
if (caches.containsKey(n) && caches.get(n) != provider) {
|
||||
throw new IllegalStateException("Cache \"" + n + "\" already defined");
|
||||
}
|
||||
caches.put(n, provider);
|
||||
return new ProxyEhcache(n);
|
||||
}
|
||||
}
|
||||
|
||||
private class Factory {
|
||||
private static final int MB = 1024 * 1024;
|
||||
private final Configuration mgr = new Configuration();
|
||||
|
||||
Configuration toConfiguration() {
|
||||
configureDiskStore();
|
||||
configureDefaultCache();
|
||||
|
||||
for (CacheProvider<?, ?> p : caches.values()) {
|
||||
final String name = p.getName();
|
||||
final CacheConfiguration c = newCache(name);
|
||||
|
||||
{
|
||||
int v = c.getMaxElementsInMemory();
|
||||
c.setMaxElementsInMemory(getInt(name, "memorylimit", v));
|
||||
}
|
||||
|
||||
{
|
||||
long v;
|
||||
v = p.timeToIdle() < 0 ? c.getTimeToIdleSeconds() : p.timeToIdle();
|
||||
c.setTimeToIdleSeconds(getLong(name, "maxage", v / 60) * 60);
|
||||
|
||||
v = p.timeToLive() < 0 ? c.getTimeToIdleSeconds() : p.timeToLive();
|
||||
c.setTimeToLiveSeconds(v);
|
||||
c.setEternal(c.getTimeToIdleSeconds() == 0
|
||||
&& c.getTimeToLiveSeconds() == 0);
|
||||
}
|
||||
|
||||
if (p.disk() && mgr.getDiskStoreConfiguration() != null) {
|
||||
int v = c.getMaxElementsOnDisk();
|
||||
c.setMaxElementsOnDisk(getInt(name, "disklimit", v));
|
||||
|
||||
v = c.getDiskSpoolBufferSizeMB() * MB;
|
||||
v = getInt(name, "diskbuffer", v) / MB;
|
||||
c.setDiskSpoolBufferSizeMB(Math.max(1, v));
|
||||
c.setOverflowToDisk(c.getMaxElementsOnDisk() > 0);
|
||||
c.setDiskPersistent(c.getMaxElementsOnDisk() > 0);
|
||||
}
|
||||
|
||||
mgr.addCache(c);
|
||||
}
|
||||
|
||||
// mgr.addCache(disk(named("diff")));
|
||||
// mgr.addCache(disk(tti(named("web_sessions"), D_SESSIONAGE)));
|
||||
// mgr.addCache(ttl(named("openid"), 5));
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
private int getInt(String name, String setting, int def) {
|
||||
return config.getInt("cache", name, setting, def);
|
||||
}
|
||||
|
||||
private long getLong(String name, String setting, long def) {
|
||||
return config.getLong("cache", name, setting, def);
|
||||
}
|
||||
|
||||
private void configureDiskStore() {
|
||||
boolean needDisk = false;
|
||||
for (CacheProvider<?, ?> p : caches.values()) {
|
||||
if (p.disk()) {
|
||||
needDisk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!needDisk) {
|
||||
return;
|
||||
}
|
||||
|
||||
String path = config.getString("cache", null, "directory");
|
||||
if (path == null || path.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
File loc = new File(path);
|
||||
if (!loc.isAbsolute()) {
|
||||
loc = new File(sitePath, path);
|
||||
}
|
||||
if (loc.exists() || loc.mkdirs()) {
|
||||
if (loc.canWrite()) {
|
||||
final DiskStoreConfiguration c = new DiskStoreConfiguration();
|
||||
c.setPath(loc.getAbsolutePath());
|
||||
mgr.addDiskStore(c);
|
||||
log.info("Enabling disk cache " + loc.getAbsolutePath());
|
||||
} else {
|
||||
log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
log.warn("Can't create disk cache: " + loc.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private void configureDefaultCache() {
|
||||
final CacheConfiguration c = new CacheConfiguration();
|
||||
|
||||
c.setMaxElementsInMemory(getInt(null, "memorylimit", 1024));
|
||||
c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
|
||||
|
||||
final long defaultAge = TimeUnit.MINUTES.convert(90, TimeUnit.DAYS);
|
||||
c.setTimeToIdleSeconds(getLong(null, "maxage", defaultAge) * 60);
|
||||
c.setTimeToLiveSeconds(c.getTimeToIdleSeconds());
|
||||
c.setEternal(c.getTimeToIdleSeconds() == 0);
|
||||
|
||||
if (mgr.getDiskStoreConfiguration() != null) {
|
||||
c.setMaxElementsOnDisk(getInt(null, "disklimit", 16384));
|
||||
c.setOverflowToDisk(false);
|
||||
c.setDiskPersistent(false);
|
||||
|
||||
final int diskbuffer = getInt(null, "diskbuffer", 5 * MB);
|
||||
c.setDiskSpoolBufferSizeMB(Math.max(1, diskbuffer / MB));
|
||||
c.setDiskExpiryThreadIntervalSeconds(60 * 60);
|
||||
}
|
||||
|
||||
mgr.setDefaultCacheConfiguration(c);
|
||||
}
|
||||
|
||||
private CacheConfiguration newCache(final String name) {
|
||||
try {
|
||||
final CacheConfiguration c;
|
||||
c = mgr.getDefaultCacheConfiguration().clone();
|
||||
c.setName(name);
|
||||
return c;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new ProvisionException("Cannot configure cache " + name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
95
src/main/java/com/google/gerrit/server/cache/CacheProvider.java
vendored
Normal file
95
src/main/java/com/google/gerrit/server/cache/CacheProvider.java
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
|
||||
import net.sf.ehcache.Ehcache;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
final class CacheProvider<K, V> implements Provider<Cache<K, V>>,
|
||||
NamedCacheBinding, UnnamedCacheBinding {
|
||||
private final boolean disk;
|
||||
private long timeToIdle = -1;
|
||||
private long timeToLive = -1;
|
||||
private String cacheName;
|
||||
private ProxyEhcache cache;
|
||||
|
||||
CacheProvider(final boolean disk) {
|
||||
this.disk = disk;
|
||||
}
|
||||
|
||||
@Inject
|
||||
void setCachePool(final CachePool pool) {
|
||||
this.cache = pool.register(this);
|
||||
}
|
||||
|
||||
void bind(final Ehcache ehcache) {
|
||||
cache.bind(ehcache);
|
||||
}
|
||||
|
||||
String getName() {
|
||||
if (cacheName == null) {
|
||||
throw new ProvisionException("Cache has no name");
|
||||
}
|
||||
return cacheName;
|
||||
}
|
||||
|
||||
boolean disk() {
|
||||
return disk;
|
||||
}
|
||||
|
||||
long timeToIdle() {
|
||||
return timeToIdle;
|
||||
}
|
||||
|
||||
long timeToLive() {
|
||||
return timeToLive;
|
||||
}
|
||||
|
||||
public NamedCacheBinding name(final String name) {
|
||||
if (cacheName != null) {
|
||||
throw new IllegalStateException("Cache name already set");
|
||||
}
|
||||
cacheName = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public NamedCacheBinding timeToIdle(final int duration, final TimeUnit unit) {
|
||||
if (timeToIdle >= 0) {
|
||||
throw new IllegalStateException("Cache timeToIdle already set");
|
||||
}
|
||||
timeToIdle = TimeUnit.SECONDS.convert(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
public NamedCacheBinding timeToLive(final int duration, final TimeUnit unit) {
|
||||
if (timeToLive >= 0) {
|
||||
throw new IllegalStateException("Cache timeToLive already set");
|
||||
}
|
||||
timeToLive = TimeUnit.SECONDS.convert(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Cache<K, V> get() {
|
||||
if (cache == null) {
|
||||
throw new ProvisionException("Cache \"" + cacheName + "\" not available");
|
||||
}
|
||||
return new SimpleCache<K, V>(cache);
|
||||
}
|
||||
}
|
||||
26
src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
vendored
Normal file
26
src/main/java/com/google/gerrit/server/cache/NamedCacheBinding.java
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/** Configure a cache declared within a {@link CacheModule} instance. */
|
||||
public interface NamedCacheBinding {
|
||||
/** Set the time an element lives without access before being expired. */
|
||||
public NamedCacheBinding timeToIdle(int duration, TimeUnit durationUnits);
|
||||
|
||||
/** Set the time an element lives since creation, before being expired. */
|
||||
public NamedCacheBinding timeToLive(int duration, TimeUnit durationUnits);
|
||||
}
|
||||
339
src/main/java/com/google/gerrit/server/cache/ProxyEhcache.java
vendored
Normal file
339
src/main/java/com/google/gerrit/server/cache/ProxyEhcache.java
vendored
Normal file
@@ -0,0 +1,339 @@
|
||||
// Copyright (C) 2008 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Ehcache;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.Statistics;
|
||||
import net.sf.ehcache.Status;
|
||||
import net.sf.ehcache.bootstrap.BootstrapCacheLoader;
|
||||
import net.sf.ehcache.config.CacheConfiguration;
|
||||
import net.sf.ehcache.event.RegisteredEventListeners;
|
||||
import net.sf.ehcache.exceptionhandler.CacheExceptionHandler;
|
||||
import net.sf.ehcache.extension.CacheExtension;
|
||||
import net.sf.ehcache.loader.CacheLoader;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Proxy around a cache which has not yet been created. */
|
||||
final class ProxyEhcache implements Ehcache {
|
||||
private final String cacheName;
|
||||
private volatile Ehcache self;
|
||||
|
||||
ProxyEhcache(final String cacheName) {
|
||||
this.cacheName = cacheName;
|
||||
}
|
||||
|
||||
void bind(final Ehcache self) {
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
private Ehcache self() {
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() throws CloneNotSupportedException {
|
||||
throw new CloneNotSupportedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return cacheName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
//
|
||||
// Everything else delegates through self.
|
||||
//
|
||||
|
||||
public void bootstrap() {
|
||||
self().bootstrap();
|
||||
}
|
||||
|
||||
public long calculateInMemorySize() throws IllegalStateException,
|
||||
CacheException {
|
||||
return self().calculateInMemorySize();
|
||||
}
|
||||
|
||||
public void clearStatistics() {
|
||||
self().clearStatistics();
|
||||
}
|
||||
|
||||
public void dispose() throws IllegalStateException {
|
||||
self().dispose();
|
||||
}
|
||||
|
||||
public void evictExpiredElements() {
|
||||
self().evictExpiredElements();
|
||||
}
|
||||
|
||||
public void flush() throws IllegalStateException, CacheException {
|
||||
self().flush();
|
||||
}
|
||||
|
||||
public Element get(Object key) throws IllegalStateException, CacheException {
|
||||
return self().get(key);
|
||||
}
|
||||
|
||||
public Element get(Serializable key) throws IllegalStateException,
|
||||
CacheException {
|
||||
return self().get(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map getAllWithLoader(Collection keys, Object loaderArgument)
|
||||
throws CacheException {
|
||||
return self().getAllWithLoader(keys, loaderArgument);
|
||||
}
|
||||
|
||||
public float getAverageGetTime() {
|
||||
return self().getAverageGetTime();
|
||||
}
|
||||
|
||||
public BootstrapCacheLoader getBootstrapCacheLoader() {
|
||||
return self().getBootstrapCacheLoader();
|
||||
}
|
||||
|
||||
public CacheConfiguration getCacheConfiguration() {
|
||||
return self().getCacheConfiguration();
|
||||
}
|
||||
|
||||
public RegisteredEventListeners getCacheEventNotificationService() {
|
||||
return self().getCacheEventNotificationService();
|
||||
}
|
||||
|
||||
public CacheExceptionHandler getCacheExceptionHandler() {
|
||||
return self().getCacheExceptionHandler();
|
||||
}
|
||||
|
||||
public CacheManager getCacheManager() {
|
||||
return self().getCacheManager();
|
||||
}
|
||||
|
||||
public int getDiskStoreSize() throws IllegalStateException {
|
||||
return self().getDiskStoreSize();
|
||||
}
|
||||
|
||||
public String getGuid() {
|
||||
return self().getGuid();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List getKeys() throws IllegalStateException, CacheException {
|
||||
return self().getKeys();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List getKeysNoDuplicateCheck() throws IllegalStateException {
|
||||
return self().getKeysNoDuplicateCheck();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public List getKeysWithExpiryCheck() throws IllegalStateException,
|
||||
CacheException {
|
||||
return self().getKeysWithExpiryCheck();
|
||||
}
|
||||
|
||||
public long getMemoryStoreSize() throws IllegalStateException {
|
||||
return self().getMemoryStoreSize();
|
||||
}
|
||||
|
||||
public Element getQuiet(Object key) throws IllegalStateException,
|
||||
CacheException {
|
||||
return self().getQuiet(key);
|
||||
}
|
||||
|
||||
public Element getQuiet(Serializable key) throws IllegalStateException,
|
||||
CacheException {
|
||||
return self().getQuiet(key);
|
||||
}
|
||||
|
||||
public List<CacheExtension> getRegisteredCacheExtensions() {
|
||||
return self().getRegisteredCacheExtensions();
|
||||
}
|
||||
|
||||
public List<CacheLoader> getRegisteredCacheLoaders() {
|
||||
return self().getRegisteredCacheLoaders();
|
||||
}
|
||||
|
||||
public int getSize() throws IllegalStateException, CacheException {
|
||||
return self().getSize();
|
||||
}
|
||||
|
||||
public Statistics getStatistics() throws IllegalStateException {
|
||||
return self().getStatistics();
|
||||
}
|
||||
|
||||
public int getStatisticsAccuracy() {
|
||||
return self().getStatisticsAccuracy();
|
||||
}
|
||||
|
||||
public Status getStatus() {
|
||||
return self().getStatus();
|
||||
}
|
||||
|
||||
public Element getWithLoader(Object key, CacheLoader loader,
|
||||
Object loaderArgument) throws CacheException {
|
||||
return self().getWithLoader(key, loader, loaderArgument);
|
||||
}
|
||||
|
||||
public void initialise() {
|
||||
self().initialise();
|
||||
}
|
||||
|
||||
public boolean isDisabled() {
|
||||
return self().isDisabled();
|
||||
}
|
||||
|
||||
public boolean isElementInMemory(Object key) {
|
||||
return self().isElementInMemory(key);
|
||||
}
|
||||
|
||||
public boolean isElementInMemory(Serializable key) {
|
||||
return self().isElementInMemory(key);
|
||||
}
|
||||
|
||||
public boolean isElementOnDisk(Object key) {
|
||||
return self().isElementOnDisk(key);
|
||||
}
|
||||
|
||||
public boolean isElementOnDisk(Serializable key) {
|
||||
return self().isElementOnDisk(key);
|
||||
}
|
||||
|
||||
public boolean isExpired(Element element) throws IllegalStateException,
|
||||
NullPointerException {
|
||||
return self().isExpired(element);
|
||||
}
|
||||
|
||||
public boolean isKeyInCache(Object key) {
|
||||
return self().isKeyInCache(key);
|
||||
}
|
||||
|
||||
public boolean isValueInCache(Object value) {
|
||||
return self().isValueInCache(value);
|
||||
}
|
||||
|
||||
public void load(Object key) throws CacheException {
|
||||
self().load(key);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void loadAll(Collection keys, Object argument) throws CacheException {
|
||||
self().loadAll(keys, argument);
|
||||
}
|
||||
|
||||
public void put(Element element, boolean doNotNotifyCacheReplicators)
|
||||
throws IllegalArgumentException, IllegalStateException, CacheException {
|
||||
self().put(element, doNotNotifyCacheReplicators);
|
||||
}
|
||||
|
||||
public void put(Element element) throws IllegalArgumentException,
|
||||
IllegalStateException, CacheException {
|
||||
self().put(element);
|
||||
}
|
||||
|
||||
public void putQuiet(Element element) throws IllegalArgumentException,
|
||||
IllegalStateException, CacheException {
|
||||
self().putQuiet(element);
|
||||
}
|
||||
|
||||
public void registerCacheExtension(CacheExtension cacheExtension) {
|
||||
self().registerCacheExtension(cacheExtension);
|
||||
}
|
||||
|
||||
public void registerCacheLoader(CacheLoader cacheLoader) {
|
||||
self().registerCacheLoader(cacheLoader);
|
||||
}
|
||||
|
||||
public boolean remove(Object key, boolean doNotNotifyCacheReplicators)
|
||||
throws IllegalStateException {
|
||||
return self().remove(key, doNotNotifyCacheReplicators);
|
||||
}
|
||||
|
||||
public boolean remove(Object key) throws IllegalStateException {
|
||||
return self().remove(key);
|
||||
}
|
||||
|
||||
public boolean remove(Serializable key, boolean doNotNotifyCacheReplicators)
|
||||
throws IllegalStateException {
|
||||
return self().remove(key, doNotNotifyCacheReplicators);
|
||||
}
|
||||
|
||||
public boolean remove(Serializable key) throws IllegalStateException {
|
||||
return self().remove(key);
|
||||
}
|
||||
|
||||
public void removeAll() throws IllegalStateException, CacheException {
|
||||
self().removeAll();
|
||||
}
|
||||
|
||||
public void removeAll(boolean doNotNotifyCacheReplicators)
|
||||
throws IllegalStateException, CacheException {
|
||||
self().removeAll(doNotNotifyCacheReplicators);
|
||||
}
|
||||
|
||||
public boolean removeQuiet(Object key) throws IllegalStateException {
|
||||
return self().removeQuiet(key);
|
||||
}
|
||||
|
||||
public boolean removeQuiet(Serializable key) throws IllegalStateException {
|
||||
return self().removeQuiet(key);
|
||||
}
|
||||
|
||||
public void setBootstrapCacheLoader(BootstrapCacheLoader bootstrapCacheLoader)
|
||||
throws CacheException {
|
||||
self().setBootstrapCacheLoader(bootstrapCacheLoader);
|
||||
}
|
||||
|
||||
public void setCacheExceptionHandler(
|
||||
CacheExceptionHandler cacheExceptionHandler) {
|
||||
self().setCacheExceptionHandler(cacheExceptionHandler);
|
||||
}
|
||||
|
||||
public void setCacheManager(CacheManager cacheManager) {
|
||||
self().setCacheManager(cacheManager);
|
||||
}
|
||||
|
||||
public void setDisabled(boolean disabled) {
|
||||
self().setDisabled(disabled);
|
||||
}
|
||||
|
||||
public void setDiskStorePath(String diskStorePath) throws CacheException {
|
||||
self().setDiskStorePath(diskStorePath);
|
||||
}
|
||||
|
||||
public void setStatisticsAccuracy(int statisticsAccuracy) {
|
||||
self().setStatisticsAccuracy(statisticsAccuracy);
|
||||
}
|
||||
|
||||
public void unregisterCacheExtension(CacheExtension cacheExtension) {
|
||||
self().unregisterCacheExtension(cacheExtension);
|
||||
}
|
||||
|
||||
public void unregisterCacheLoader(CacheLoader cacheLoader) {
|
||||
self().unregisterCacheLoader(cacheLoader);
|
||||
}
|
||||
}
|
||||
136
src/main/java/com/google/gerrit/server/cache/SelfPopulatingCache.java
vendored
Normal file
136
src/main/java/com/google/gerrit/server/cache/SelfPopulatingCache.java
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright (C) 2008 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.Ehcache;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A decorator for {@link Cache} which automatically constructs missing entries.
|
||||
* <p>
|
||||
* On a cache miss {@link #createEntry(Object)} is invoked, allowing the
|
||||
* application specific subclass to compute the entry and return it for caching.
|
||||
* During a miss the cache takes a lock related to the missing key, ensuring
|
||||
* that at most one thread performs the creation work, and other threads wait
|
||||
* for the result. Concurrent creations are possible if two different keys miss
|
||||
* and hash to different locks in the internal lock table.
|
||||
*
|
||||
* @param <K> type of key used to name cache entries.
|
||||
* @param <V> type of value stored within a cache entry.
|
||||
*/
|
||||
public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(SelfPopulatingCache.class);
|
||||
|
||||
private final net.sf.ehcache.constructs.blocking.SelfPopulatingCache self;
|
||||
|
||||
/**
|
||||
* Create a new cache which uses another cache to store entries.
|
||||
*
|
||||
* @param backingStore cache which will store the entries for this cache.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public SelfPopulatingCache(final Cache<K, V> backingStore) {
|
||||
final Ehcache s = ((SimpleCache) backingStore).getEhcache();
|
||||
final CacheEntryFactory f = new CacheEntryFactory() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntry(Object key) throws Exception {
|
||||
return SelfPopulatingCache.this.createEntry((K) key);
|
||||
}
|
||||
};
|
||||
self = new net.sf.ehcache.constructs.blocking.SelfPopulatingCache(s, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoked on a cache miss, to compute the cache entry.
|
||||
*
|
||||
* @param key entry whose content needs to be obtained.
|
||||
* @return new cache content. The caller will automatically put this object
|
||||
* into the cache.
|
||||
* @throws Exception the cache content cannot be computed. No entry will be
|
||||
* stored in the cache, and {@link #missing(Object)} will be invoked
|
||||
* instead. Future requests for the same key will retry this method.
|
||||
*/
|
||||
protected abstract V createEntry(K key) throws Exception;
|
||||
|
||||
/** Invoked when {@link #createEntry(Object)} fails, by default return null. */
|
||||
protected V missing(K key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the element from the cache, or {@link #missing(Object)} if not found.
|
||||
* <p>
|
||||
* The {@link #missing(Object)} method is only invoked if:
|
||||
* <ul>
|
||||
* <li>{@code key == null}, in which case the application should return a
|
||||
* suitable return value that callers can accept, or throw a RuntimeException.
|
||||
* <li>{@code createEntry(key)} threw an exception, in which case the entry
|
||||
* was not stored in the cache. An entry was recorded in the application log,
|
||||
* but a return value is still required.
|
||||
* <li>The cache has been shutdown, and access is forbidden.
|
||||
* </ul>
|
||||
*
|
||||
* @param key key to locate.
|
||||
* @return either the cached entry, or {@code missing(key)} if not found.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public V get(final K key) {
|
||||
if (key == null) {
|
||||
return missing(key);
|
||||
}
|
||||
|
||||
final Element m;
|
||||
try {
|
||||
m = self.get(key);
|
||||
} catch (IllegalStateException err) {
|
||||
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
|
||||
return missing(key);
|
||||
} catch (CacheException err) {
|
||||
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
|
||||
return missing(key);
|
||||
}
|
||||
return m != null ? (V) m.getObjectValue() : missing(key);
|
||||
}
|
||||
|
||||
public void remove(final K key) {
|
||||
if (key != null) {
|
||||
self.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public void put(K key, V value) {
|
||||
self.put(new Element(key, value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeToIdle(final TimeUnit unit) {
|
||||
final long idle = self.getCacheConfiguration().getTimeToIdleSeconds();
|
||||
return unit.convert(idle, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cache[" + self.getName() + "]";
|
||||
}
|
||||
}
|
||||
83
src/main/java/com/google/gerrit/server/cache/SimpleCache.java
vendored
Normal file
83
src/main/java/com/google/gerrit/server/cache/SimpleCache.java
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.Ehcache;
|
||||
import net.sf.ehcache.Element;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* A fast in-memory and/or on-disk based cache.
|
||||
*
|
||||
* @type <K> type of key used to lookup entries in the cache.
|
||||
* @type <V> type of value stored within each cache entry.
|
||||
*/
|
||||
final class SimpleCache<K, V> implements Cache<K, V> {
|
||||
private static final Logger log = LoggerFactory.getLogger(SimpleCache.class);
|
||||
|
||||
private final Ehcache self;
|
||||
|
||||
SimpleCache(final Ehcache self) {
|
||||
this.self = self;
|
||||
}
|
||||
|
||||
Ehcache getEhcache() {
|
||||
return self;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public V get(final K key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
final Element m;
|
||||
try {
|
||||
m = self.get(key);
|
||||
} catch (IllegalStateException err) {
|
||||
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
|
||||
return null;
|
||||
} catch (CacheException err) {
|
||||
log.error("Cannot lookup " + key + " in \"" + self.getName() + "\"", err);
|
||||
return null;
|
||||
}
|
||||
return m != null ? (V) m.getObjectValue() : null;
|
||||
}
|
||||
|
||||
public void put(final K key, final V value) {
|
||||
self.put(new Element(key, value));
|
||||
}
|
||||
|
||||
public void remove(final K key) {
|
||||
if (key != null) {
|
||||
self.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimeToIdle(final TimeUnit unit) {
|
||||
final long idle = self.getCacheConfiguration().getTimeToIdleSeconds();
|
||||
return unit.convert(idle, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Cache[" + self.getName() + "]";
|
||||
}
|
||||
}
|
||||
22
src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
vendored
Normal file
22
src/main/java/com/google/gerrit/server/cache/UnnamedCacheBinding.java
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.cache;
|
||||
|
||||
|
||||
/** Configure a cache declared within a {@link CacheModule} instance. */
|
||||
public interface UnnamedCacheBinding {
|
||||
/** Set the name of the cache. */
|
||||
public NamedCacheBinding name(String cacheName);
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package com.google.gerrit.server.config;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.ProvisionException;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.config.CacheConfiguration;
|
||||
import net.sf.ehcache.config.Configuration;
|
||||
import net.sf.ehcache.config.DiskStoreConfiguration;
|
||||
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spearce.jgit.lib.Config;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class CacheManagerProvider implements Provider<CacheManager> {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(CacheManagerProvider.class);
|
||||
|
||||
private final Config config;
|
||||
private final File sitePath;
|
||||
private final AuthConfig authConfig;
|
||||
|
||||
@Inject
|
||||
CacheManagerProvider(@GerritServerConfig final Config cfg,
|
||||
@SitePath final File sitePath, final AuthConfig authConfig) {
|
||||
this.config = cfg;
|
||||
this.sitePath = sitePath;
|
||||
this.authConfig = authConfig;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CacheManager get() {
|
||||
return new CacheManager(new Factory().toConfiguration());
|
||||
}
|
||||
|
||||
private class Factory {
|
||||
private static final int MB = 1024 * 1024;
|
||||
private static final int ONE_DAY = 24 * 60;
|
||||
private static final int D_MAXAGE = 3 * 30 * ONE_DAY;
|
||||
private static final int D_SESSIONAGE = ONE_DAY / 2;
|
||||
private final Configuration mgr = new Configuration();
|
||||
|
||||
Configuration toConfiguration() {
|
||||
configureDiskStore();
|
||||
configureDefaultCache();
|
||||
|
||||
switch (authConfig.getLoginType()) {
|
||||
case OPENID:
|
||||
mgr.addCache(ttl(named("openid"), 5));
|
||||
break;
|
||||
}
|
||||
|
||||
mgr.addCache(named("accounts"));
|
||||
mgr.addCache(named("accounts_byemail"));
|
||||
mgr.addCache(disk(named("diff")));
|
||||
mgr.addCache(named("groups"));
|
||||
mgr.addCache(named("projects"));
|
||||
mgr.addCache(named("sshkeys"));
|
||||
mgr.addCache(disk(tti(named("web_sessions"), D_SESSIONAGE)));
|
||||
|
||||
return mgr;
|
||||
}
|
||||
|
||||
private void configureDiskStore() {
|
||||
String path = config.getString("cache", null, "directory");
|
||||
if (path == null || path.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
File loc = new File(path);
|
||||
if (!loc.isAbsolute()) {
|
||||
loc = new File(sitePath, path);
|
||||
}
|
||||
if (loc.exists() || loc.mkdirs()) {
|
||||
if (loc.canWrite()) {
|
||||
final DiskStoreConfiguration c = new DiskStoreConfiguration();
|
||||
c.setPath(loc.getAbsolutePath());
|
||||
mgr.addDiskStore(c);
|
||||
log.info("Enabling disk cache " + loc.getAbsolutePath());
|
||||
} else {
|
||||
log.warn("Can't write to disk cache: " + loc.getAbsolutePath());
|
||||
}
|
||||
} else {
|
||||
log.warn("Can't create disk cache: " + loc.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
|
||||
private void configureDefaultCache() {
|
||||
final CacheConfiguration c = new CacheConfiguration();
|
||||
|
||||
c.setMaxElementsInMemory(config.getInt("cache", "memorylimit", 1024));
|
||||
c.setMemoryStoreEvictionPolicyFromObject(MemoryStoreEvictionPolicy.LFU);
|
||||
|
||||
c.setTimeToIdleSeconds(config.getInt("cache", "maxage", D_MAXAGE) * 60);
|
||||
c.setTimeToLiveSeconds(c.getTimeToIdleSeconds());
|
||||
c.setEternal(c.getTimeToIdleSeconds() == 0);
|
||||
|
||||
if (mgr.getDiskStoreConfiguration() != null) {
|
||||
c.setMaxElementsOnDisk(config.getInt("cache", "disklimit", 16384));
|
||||
c.setOverflowToDisk(false);
|
||||
c.setDiskPersistent(false);
|
||||
|
||||
final int diskbuffer = config.getInt("cache", "diskbuffer", 5 * MB);
|
||||
c.setDiskSpoolBufferSizeMB(Math.max(1, diskbuffer / MB));
|
||||
c.setDiskExpiryThreadIntervalSeconds(60 * 60);
|
||||
}
|
||||
|
||||
mgr.setDefaultCacheConfiguration(c);
|
||||
}
|
||||
|
||||
private CacheConfiguration named(final String name) {
|
||||
final CacheConfiguration c = newCache(name);
|
||||
|
||||
int e = c.getMaxElementsInMemory();
|
||||
c.setMaxElementsInMemory(config.getInt("cache", name, "memorylimit", e));
|
||||
|
||||
ttl(c, (int) c.getTimeToIdleSeconds() / 60);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private CacheConfiguration newCache(final String name) {
|
||||
try {
|
||||
final CacheConfiguration c;
|
||||
c = mgr.getDefaultCacheConfiguration().clone();
|
||||
c.setName(name);
|
||||
return c;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new ProvisionException("Cannot configure cache " + name, e);
|
||||
}
|
||||
}
|
||||
|
||||
private CacheConfiguration ttl(final CacheConfiguration c, final int age) {
|
||||
final String name = c.getName();
|
||||
c.setTimeToIdleSeconds(config.getInt("cache", name, "maxage", age) * 60);
|
||||
c.setTimeToLiveSeconds(c.getTimeToIdleSeconds());
|
||||
c.setEternal(c.getTimeToIdleSeconds() == 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
private CacheConfiguration tti(final CacheConfiguration c, final int age) {
|
||||
final String name = c.getName();
|
||||
c.setTimeToIdleSeconds(config.getInt("cache", name, "maxage", age) * 60);
|
||||
c.setTimeToLiveSeconds(0 /* until idle out, or removed */);
|
||||
c.setEternal(c.getTimeToIdleSeconds() == 0);
|
||||
return c;
|
||||
}
|
||||
|
||||
private CacheConfiguration disk(final CacheConfiguration c) {
|
||||
final String name = c.getName();
|
||||
if (mgr.getDiskStoreConfiguration() != null) {
|
||||
int e = c.getMaxElementsOnDisk();
|
||||
c.setMaxElementsOnDisk(config.getInt("cache", name, "disklimit", e));
|
||||
|
||||
int buffer = c.getDiskSpoolBufferSizeMB() * MB;
|
||||
buffer = config.getInt("cache", name, "diskbuffer", buffer) / MB;
|
||||
c.setDiskSpoolBufferSizeMB(Math.max(1, buffer));
|
||||
c.setOverflowToDisk(c.getMaxElementsOnDisk() > 0);
|
||||
c.setDiskPersistent(c.getMaxElementsOnDisk() > 0);
|
||||
}
|
||||
return c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public abstract class FactoryModule extends AbstractModule {
|
||||
* Register an assisted injection factory.
|
||||
* <p>
|
||||
* This function provides an automatic way to define a factory that creates a
|
||||
* concrete type through assited injection. For example to configure the
|
||||
* concrete type through assisted injection. For example to configure the
|
||||
* following assisted injection case:
|
||||
*
|
||||
* <pre>
|
||||
|
||||
@@ -37,6 +37,7 @@ import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountInfoCacheFactory;
|
||||
import com.google.gerrit.server.account.EmailExpander;
|
||||
import com.google.gerrit.server.account.GroupCache;
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.mail.AbandonedSender;
|
||||
import com.google.gerrit.server.mail.AddReviewerSender;
|
||||
import com.google.gerrit.server.mail.CommentSender;
|
||||
@@ -56,8 +57,6 @@ import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.Module;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -94,15 +93,15 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
bind(String.class).annotatedWith(CanonicalWebUrl.class).toProvider(
|
||||
CanonicalWebUrlProvider.class);
|
||||
|
||||
bind(CacheManager.class).toProvider(CacheManagerProvider.class).in(
|
||||
SINGLETON);
|
||||
bind(AccountByEmailCache.class);
|
||||
bind(AccountCache.class);
|
||||
bind(CachePool.class);
|
||||
install(AccountByEmailCache.module());
|
||||
install(AccountCache.module());
|
||||
install(DiffCache.module());
|
||||
install(GroupCache.module());
|
||||
install(ProjectCache.module());
|
||||
install(SshKeyCache.module());
|
||||
|
||||
factory(AccountInfoCacheFactory.Factory.class);
|
||||
bind(DiffCache.class);
|
||||
bind(GroupCache.class);
|
||||
bind(ProjectCache.class);
|
||||
bind(SshKeyCache.class);
|
||||
|
||||
bind(GerritServer.class);
|
||||
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
|
||||
|
||||
@@ -19,6 +19,7 @@ import static com.google.inject.Stage.PRODUCTION;
|
||||
import com.google.gerrit.git.PushAllProjectsOp;
|
||||
import com.google.gerrit.git.ReloadSubmitQueueOp;
|
||||
import com.google.gerrit.git.WorkQueue;
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrlProvider;
|
||||
import com.google.gerrit.server.config.DatabaseModule;
|
||||
import com.google.gerrit.server.config.GerritGlobalModule;
|
||||
@@ -33,8 +34,6 @@ import com.google.inject.ProvisionException;
|
||||
import com.google.inject.servlet.GuiceServletContextListener;
|
||||
import com.google.inject.spi.Message;
|
||||
|
||||
import net.sf.ehcache.CacheManager;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@@ -121,6 +120,14 @@ public class GerritServletConfig extends GuiceServletContextListener {
|
||||
super.contextInitialized(event);
|
||||
init();
|
||||
|
||||
try {
|
||||
sysInjector.getInstance(CachePool.class).start();
|
||||
} catch (ConfigurationException e) {
|
||||
log.error("Unable to start CachePool", e);
|
||||
} catch (ProvisionException e) {
|
||||
log.error("Unable to start CachePool", e);
|
||||
}
|
||||
|
||||
try {
|
||||
sysInjector.getInstance(PushAllProjectsOp.Factory.class).create(null)
|
||||
.start(30, TimeUnit.SECONDS);
|
||||
@@ -170,7 +177,7 @@ public class GerritServletConfig extends GuiceServletContextListener {
|
||||
|
||||
try {
|
||||
if (sysInjector != null) {
|
||||
sysInjector.getInstance(CacheManager.class).shutdown();
|
||||
sysInjector.getInstance(CachePool.class).stop();
|
||||
}
|
||||
} catch (ConfigurationException e) {
|
||||
} catch (ProvisionException e) {
|
||||
|
||||
@@ -114,8 +114,7 @@ class WebModule extends FactoryModule {
|
||||
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
||||
HttpRemotePeerProvider.class).in(RequestScoped.class);
|
||||
|
||||
bind(WebSession.class).in(RequestScoped.class);
|
||||
bind(WebSessionManager.class).in(SINGLETON);
|
||||
install(WebSession.module());
|
||||
|
||||
bind(CurrentUser.class).toProvider(HttpCurrentUserProvider.class).in(
|
||||
RequestScoped.class);
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
package com.google.gerrit.server.http;
|
||||
|
||||
import static java.util.concurrent.TimeUnit.HOURS;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.server.AnonymousUser;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.http.WebSessionManager.Key;
|
||||
import com.google.gerrit.server.http.WebSessionManager.Val;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.servlet.RequestScoped;
|
||||
|
||||
import net.sf.ehcache.Element;
|
||||
|
||||
import javax.servlet.http.Cookie;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
@@ -19,13 +24,29 @@ import javax.servlet.http.HttpServletResponse;
|
||||
public final class WebSession {
|
||||
private static final String ACCOUNT_COOKIE = "GerritAccount";
|
||||
|
||||
static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final String cacheName = WebSessionManager.CACHE_NAME;
|
||||
final TypeLiteral<Cache<Key, Val>> type =
|
||||
new TypeLiteral<Cache<Key, Val>>() {};
|
||||
disk(type, cacheName).timeToIdle(12, HOURS).timeToLive(0, SECONDS);
|
||||
bind(WebSessionManager.class);
|
||||
bind(WebSession.class).in(RequestScoped.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final HttpServletRequest request;
|
||||
private final HttpServletResponse response;
|
||||
private final WebSessionManager manager;
|
||||
private final AnonymousUser anonymous;
|
||||
private final IdentifiedUser.RequestFactory identified;
|
||||
private Cookie outCookie;
|
||||
private Element element;
|
||||
|
||||
private Key key;
|
||||
private Val val;
|
||||
|
||||
@Inject
|
||||
WebSession(final HttpServletRequest request,
|
||||
@@ -37,16 +58,22 @@ public final class WebSession {
|
||||
this.manager = manager;
|
||||
this.anonymous = anonymous;
|
||||
this.identified = identified;
|
||||
this.element = manager.get(readCookie());
|
||||
|
||||
if (isSignedIn() && val().refreshCookieAt <= System.currentTimeMillis()) {
|
||||
final String cookie = readCookie();
|
||||
if (cookie != null) {
|
||||
key = new Key(cookie);
|
||||
val = manager.get(key);
|
||||
} else {
|
||||
key = null;
|
||||
val = null;
|
||||
}
|
||||
|
||||
if (isSignedIn() && val.refreshCookieAt <= System.currentTimeMillis()) {
|
||||
// Cookie is more than half old. Send it again to the client with a
|
||||
// fresh expiration date.
|
||||
//
|
||||
final int age;
|
||||
age = element.getTimeToIdle();
|
||||
val().refreshCookieAt = System.currentTimeMillis() + (age / 2 * 1000L);
|
||||
saveCookie(key().token, age);
|
||||
manager.updateRefreshCookieAt(val);
|
||||
saveCookie(key.token, val.getCookieAge());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,51 +91,47 @@ public final class WebSession {
|
||||
}
|
||||
|
||||
public boolean isSignedIn() {
|
||||
return element != null;
|
||||
return val != null;
|
||||
}
|
||||
|
||||
String getToken() {
|
||||
return isSignedIn() ? key().token : null;
|
||||
return isSignedIn() ? key.token : null;
|
||||
}
|
||||
|
||||
boolean isTokenValid(final String keyIn) {
|
||||
return isSignedIn() && key().token.equals(keyIn);
|
||||
return isSignedIn() && key.token.equals(keyIn);
|
||||
}
|
||||
|
||||
CurrentUser getCurrentUser() {
|
||||
return isSignedIn() ? identified.create(val().accountId) : anonymous;
|
||||
return isSignedIn() ? identified.create(val.accountId) : anonymous;
|
||||
}
|
||||
|
||||
public void login(final Account.Id id, final boolean rememberMe) {
|
||||
logout();
|
||||
element = manager.create(id);
|
||||
|
||||
key = manager.createKey(id);
|
||||
val = manager.createVal(key, id);
|
||||
|
||||
final int age;
|
||||
if (rememberMe) {
|
||||
age = element.getTimeToIdle();
|
||||
val().refreshCookieAt = System.currentTimeMillis() + (age / 2 * 1000L);
|
||||
manager.updateRefreshCookieAt(val);
|
||||
age = val.getCookieAge();
|
||||
} else {
|
||||
val.refreshCookieAt = Long.MAX_VALUE;
|
||||
age = -1 /* don't store on client disk */;
|
||||
val().refreshCookieAt = Long.MAX_VALUE;
|
||||
}
|
||||
saveCookie(key().token, age);
|
||||
saveCookie(key.token, age);
|
||||
}
|
||||
|
||||
public void logout() {
|
||||
if (element != null) {
|
||||
manager.destroy(element);
|
||||
element = null;
|
||||
if (val != null) {
|
||||
manager.destroy(key);
|
||||
key = null;
|
||||
val = null;
|
||||
saveCookie("", 0 /* erase at client */);
|
||||
}
|
||||
}
|
||||
|
||||
private Key key() {
|
||||
return ((Key) element.getKey());
|
||||
}
|
||||
|
||||
private Val val() {
|
||||
return ((Val) element.getObjectValue());
|
||||
}
|
||||
|
||||
private void saveCookie(final String val, final int age) {
|
||||
if (outCookie == null) {
|
||||
String path = request.getContextPath();
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
package com.google.gerrit.server.http;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import org.spearce.jgit.util.Base64;
|
||||
import org.spearce.jgit.util.NB;
|
||||
@@ -31,21 +28,29 @@ import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Singleton
|
||||
class WebSessionManager {
|
||||
static final String CACHE_NAME = "web_sessions";
|
||||
|
||||
private final int tokenLen;
|
||||
private final SecureRandom prng;
|
||||
private final Cache self;
|
||||
private final Cache<Key, Val> self;
|
||||
|
||||
@Inject
|
||||
WebSessionManager(final CacheManager mgr) {
|
||||
WebSessionManager(@Named(CACHE_NAME) final Cache<Key, Val> cache) {
|
||||
tokenLen = 40 - 4;
|
||||
prng = new SecureRandom();
|
||||
self = mgr.getCache("web_sessions");
|
||||
self = cache;
|
||||
}
|
||||
|
||||
Element create(final Account.Id who) {
|
||||
void updateRefreshCookieAt(final Val val) {
|
||||
final long now = System.currentTimeMillis();
|
||||
val.refreshCookieAt = now + self.getTimeToIdle(TimeUnit.MILLISECONDS) / 2;
|
||||
}
|
||||
|
||||
Key createKey(final Account.Id who) {
|
||||
final int accountId = who.get();
|
||||
final byte[] rnd = new byte[tokenLen];
|
||||
prng.nextBytes(rnd);
|
||||
@@ -54,29 +59,21 @@ class WebSessionManager {
|
||||
NB.encodeInt32(buf, 0, accountId);
|
||||
System.arraycopy(rnd, 0, buf, 4, rnd.length);
|
||||
|
||||
final String token = Base64.encodeBytes(rnd, Base64.DONT_BREAK_LINES);
|
||||
final Val v = new Val(who);
|
||||
final Element m = new Element(new Key(token), v);
|
||||
self.put(m);
|
||||
return m;
|
||||
return new Key(Base64.encodeBytes(rnd, Base64.DONT_BREAK_LINES));
|
||||
}
|
||||
|
||||
Element get(final String token) {
|
||||
if (token != null && !"".equals(token)) {
|
||||
try {
|
||||
return self.get(new Key(token));
|
||||
} catch (IllegalStateException e) {
|
||||
return null;
|
||||
} catch (CacheException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
Val createVal(final Key key, final Account.Id who) {
|
||||
final Val val = new Val(who);
|
||||
self.put(key, val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void destroy(final Element cacheEntry) {
|
||||
self.remove(cacheEntry.getKey());
|
||||
Val get(final Key key) {
|
||||
return self.get(key);
|
||||
}
|
||||
|
||||
void destroy(final Key key) {
|
||||
self.remove(key);
|
||||
}
|
||||
|
||||
static final class Key implements Serializable {
|
||||
@@ -117,6 +114,10 @@ class WebSessionManager {
|
||||
this.accountId = accountId;
|
||||
}
|
||||
|
||||
int getCookieAge() {
|
||||
return (int) ((refreshCookieAt - System.currentTimeMillis()) / 1000L);
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
out.writeInt(accountId.get());
|
||||
out.writeLong(refreshCookieAt);
|
||||
|
||||
@@ -14,14 +14,29 @@
|
||||
|
||||
package com.google.gerrit.server.openid;
|
||||
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.http.RpcServletModule;
|
||||
import com.google.gerrit.server.rpc.UiRpcModule;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
|
||||
import java.util.List;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
|
||||
/** Servlets and RPC support related to OpenID authentication. */
|
||||
public class OpenIdModule extends ServletModule {
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
install(new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<String, List>> type =
|
||||
new TypeLiteral<Cache<String, List>>() {};
|
||||
core(type, "openid").timeToIdle(5, MINUTES).timeToLive(5, MINUTES);
|
||||
}
|
||||
});
|
||||
|
||||
serve("/" + OpenIdServiceImpl.RETURN_URL).with(OpenIdLoginServlet.class);
|
||||
|
||||
install(new RpcServletModule(UiRpcModule.PREFIX) {
|
||||
|
||||
@@ -24,6 +24,8 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.UrlEncoded;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
import com.google.gerrit.server.http.WebSession;
|
||||
@@ -32,12 +34,7 @@ import com.google.gwtorm.client.KeyUtil;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import org.openid4java.consumer.ConsumerException;
|
||||
import org.openid4java.consumer.ConsumerManager;
|
||||
@@ -93,32 +90,31 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
private final Provider<String> urlProvider;
|
||||
private final AccountManager accountManager;
|
||||
private final ConsumerManager manager;
|
||||
private final SelfPopulatingCache discoveryCache;
|
||||
private final SelfPopulatingCache<String, List> discoveryCache;
|
||||
|
||||
@Inject
|
||||
OpenIdServiceImpl(final Provider<WebSession> cf,
|
||||
final Provider<IdentifiedUser> iu,
|
||||
@CanonicalWebUrl @Nullable final Provider<String> up,
|
||||
final CacheManager cacheMgr, final AccountManager am)
|
||||
throws ConsumerException {
|
||||
@Named("openid") final Cache<String, List> openidCache,
|
||||
final AccountManager am) throws ConsumerException {
|
||||
webSession = cf;
|
||||
identifiedUser = iu;
|
||||
urlProvider = up;
|
||||
accountManager = am;
|
||||
manager = new ConsumerManager();
|
||||
|
||||
final Cache base = cacheMgr.getCache("openid");
|
||||
discoveryCache = new SelfPopulatingCache(base, new CacheEntryFactory() {
|
||||
public Object createEntry(final Object objKey) throws Exception {
|
||||
discoveryCache = new SelfPopulatingCache<String, List>(openidCache) {
|
||||
@Override
|
||||
protected List createEntry(final String url) throws Exception {
|
||||
try {
|
||||
final List<?> list = manager.discover((String) objKey);
|
||||
final List<?> list = manager.discover(url);
|
||||
return list != null && !list.isEmpty() ? list : null;
|
||||
} catch (DiscoveryException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
});
|
||||
cacheMgr.replaceCacheWithDecoratedCache(base, discoveryCache);
|
||||
};
|
||||
}
|
||||
|
||||
public void discover(final String openidIdentifier,
|
||||
@@ -376,12 +372,7 @@ class OpenIdServiceImpl implements OpenIdService {
|
||||
private State init(final String openidIdentifier,
|
||||
final SignInDialog.Mode mode, final boolean remember,
|
||||
final String returnToken) {
|
||||
final Element serverCache = discoveryCache.get(openidIdentifier);
|
||||
if (serverCache == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final List<?> list = (List<?>) serverCache.getObjectValue();
|
||||
final List<?> list = discoveryCache.get(openidIdentifier);
|
||||
if (list == null || list.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -15,40 +15,53 @@
|
||||
package com.google.gerrit.server.patch;
|
||||
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
|
||||
/** Provides the {@link DiffCacheContent}. */
|
||||
@Singleton
|
||||
public class DiffCache {
|
||||
private final SelfPopulatingCache self;
|
||||
private static final String CACHE_NAME = "diff";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<DiffCacheKey, DiffCacheContent>> type =
|
||||
new TypeLiteral<Cache<DiffCacheKey, DiffCacheContent>>() {};
|
||||
disk(type, CACHE_NAME);
|
||||
bind(DiffCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SelfPopulatingCache<DiffCacheKey, DiffCacheContent> self;
|
||||
|
||||
@Inject
|
||||
DiffCache(final CacheManager mgr, final GerritServer gs) {
|
||||
final Cache dc = mgr.getCache("diff");
|
||||
self = new SelfPopulatingCache(dc, new DiffCacheEntryFactory(gs));
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
DiffCache(final GerritServer gs,
|
||||
@Named(CACHE_NAME) final Cache<DiffCacheKey, DiffCacheContent> raw) {
|
||||
final DiffCacheEntryFactory f = new DiffCacheEntryFactory(gs);
|
||||
self = new SelfPopulatingCache<DiffCacheKey, DiffCacheContent>(raw) {
|
||||
@Override
|
||||
protected DiffCacheContent createEntry(final DiffCacheKey key)
|
||||
throws Exception {
|
||||
return f.createEntry(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return self.getName();
|
||||
}
|
||||
|
||||
public Element get(final DiffCacheKey key) {
|
||||
public DiffCacheContent get(final DiffCacheKey key) {
|
||||
return self.get(key);
|
||||
}
|
||||
|
||||
public void put(final DiffCacheKey k, final DiffCacheContent c) {
|
||||
self.put(new Element(k, c));
|
||||
}
|
||||
|
||||
public void flush() throws IllegalStateException, CacheException {
|
||||
self.flush();
|
||||
self.put(k, c);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,6 @@ package com.google.gerrit.server.patch;
|
||||
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
import org.spearce.jgit.patch.FileHeader;
|
||||
@@ -26,15 +24,14 @@ import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
class DiffCacheEntryFactory implements CacheEntryFactory {
|
||||
final class DiffCacheEntryFactory {
|
||||
private final GerritServer server;
|
||||
|
||||
DiffCacheEntryFactory(final GerritServer gs) {
|
||||
server = gs;
|
||||
}
|
||||
|
||||
public Object createEntry(Object genericKey) throws Exception {
|
||||
final DiffCacheKey key = (DiffCacheKey) genericKey;
|
||||
DiffCacheContent createEntry(final DiffCacheKey key) throws Exception {
|
||||
final Repository db = server.openRepository(key.getProjectKey().get());
|
||||
try {
|
||||
return createEntry(key, db);
|
||||
|
||||
@@ -18,50 +18,58 @@ import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.ProjectRight;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.AnonymousUser;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gerrit.server.config.WildProjectName;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/** Cache of project information, including access rights. */
|
||||
@Singleton
|
||||
public class ProjectCache {
|
||||
private static final Logger log = LoggerFactory.getLogger(ProjectCache.class);
|
||||
private static final String CACHE_NAME = "projects";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
|
||||
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(ProjectCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
final AnonymousUser anonymousUser;
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final Project.NameKey wildProject;
|
||||
private final Cache raw;
|
||||
private final SelfPopulatingCache auto;
|
||||
private final SelfPopulatingCache<Project.NameKey, ProjectState> byName;
|
||||
|
||||
@Inject
|
||||
ProjectCache(final AnonymousUser au, final SchemaFactory<ReviewDb> sf,
|
||||
@WildProjectName final Project.NameKey wp, final CacheManager mgr) {
|
||||
@WildProjectName final Project.NameKey wp,
|
||||
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName) {
|
||||
anonymousUser = au;
|
||||
schema = sf;
|
||||
wildProject = wp;
|
||||
|
||||
raw = mgr.getCache("projects");
|
||||
auto = new SelfPopulatingCache(raw, new CacheEntryFactory() {
|
||||
@Override
|
||||
public Object createEntry(final Object key) throws Exception {
|
||||
return lookup((Project.NameKey) key);
|
||||
}
|
||||
});
|
||||
mgr.replaceCacheWithDecoratedCache(raw, auto);
|
||||
this.byName =
|
||||
new SelfPopulatingCache<Project.NameKey, ProjectState>(byName) {
|
||||
@Override
|
||||
public ProjectState createEntry(final Project.NameKey key)
|
||||
throws Exception {
|
||||
return lookup(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private ProjectState lookup(final Project.NameKey key) throws OrmException {
|
||||
@@ -79,13 +87,6 @@ public class ProjectCache {
|
||||
return get(wildProject).getRights();
|
||||
}
|
||||
|
||||
/** Invalidate the cached information about the given project. */
|
||||
public void evict(final Project p) {
|
||||
if (p != null) {
|
||||
auto.remove(p.getNameKey());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the cached data for a project by its unique name.
|
||||
*
|
||||
@@ -93,28 +94,13 @@ public class ProjectCache {
|
||||
* @return the cached data; null if no such project exists.
|
||||
*/
|
||||
public ProjectState get(final Project.NameKey projectName) {
|
||||
return get0(projectName);
|
||||
return byName.get(projectName);
|
||||
}
|
||||
|
||||
private ProjectState get0(final Object key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
/** Invalidate the cached information about the given project. */
|
||||
public void evict(final Project p) {
|
||||
if (p != null) {
|
||||
byName.remove(p.getNameKey());
|
||||
}
|
||||
|
||||
final Element m;
|
||||
try {
|
||||
m = auto.get(key);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Cannot lookup project " + key, e);
|
||||
return null;
|
||||
} catch (CacheException e) {
|
||||
log.error("Cannot lookup project " + key, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (m == null || m.getObjectValue() == null) {
|
||||
return null;
|
||||
}
|
||||
return (ProjectState) m.getObjectValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import net.sf.ehcache.CacheException;
|
||||
import net.sf.ehcache.Element;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spearce.jgit.errors.RepositoryNotFoundException;
|
||||
@@ -171,21 +168,12 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
|
||||
private DiffCacheContent get(final DiffCacheKey key)
|
||||
throws NoSuchChangeException {
|
||||
final Element cacheElem;
|
||||
try {
|
||||
cacheElem = diffCache.get(key);
|
||||
} catch (IllegalStateException e) {
|
||||
log.error("Cache get failed for " + key, e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
} catch (CacheException e) {
|
||||
log.error("Cache get failed for " + key, e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
}
|
||||
if (cacheElem == null || cacheElem.getObjectValue() == null) {
|
||||
final DiffCacheContent r = diffCache.get(key);
|
||||
if (r == null) {
|
||||
log.error("Cache get failed for " + key);
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
return (DiffCacheContent) cacheElem.getObjectValue();
|
||||
return r;
|
||||
}
|
||||
|
||||
private PatchScriptBuilder newBuilder() throws NoSuchChangeException {
|
||||
|
||||
@@ -14,52 +14,116 @@
|
||||
|
||||
package com.google.gerrit.server.ssh;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountSshKey;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import net.sf.ehcache.Cache;
|
||||
import net.sf.ehcache.CacheManager;
|
||||
import net.sf.ehcache.Element;
|
||||
import net.sf.ehcache.constructs.blocking.SelfPopulatingCache;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Provides the {@link SshKeyCacheEntry}. */
|
||||
@Singleton
|
||||
public class SshKeyCache {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
private final SelfPopulatingCache self;
|
||||
private static final Logger log = LoggerFactory.getLogger(SshKeyCache.class);
|
||||
private static final String CACHE_NAME = "sshkeys";
|
||||
|
||||
@Inject
|
||||
SshKeyCache(final CacheManager mgr, final SchemaFactory<ReviewDb> db) {
|
||||
final Cache dc = mgr.getCache("sshkeys");
|
||||
self = new SelfPopulatingCache(dc, new SshKeyCacheEntryFactory(db));
|
||||
mgr.replaceCacheWithDecoratedCache(dc, self);
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>> type =
|
||||
new TypeLiteral<Cache<String, Iterable<SshKeyCacheEntry>>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(SshKeyCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public Iterable<SshKeyCacheEntry> get(String username) {
|
||||
try {
|
||||
final Element e = self.get(username);
|
||||
if (e == null || e.getObjectValue() == null) {
|
||||
log.warn("Can't get SSH keys for \"" + username + "\" from cache.");
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final SelfPopulatingCache<String, Iterable<SshKeyCacheEntry>> self;
|
||||
|
||||
@Inject
|
||||
SshKeyCache(final SchemaFactory<ReviewDb> schema,
|
||||
@Named(CACHE_NAME) final Cache<String, Iterable<SshKeyCacheEntry>> raw) {
|
||||
this.schema = schema;
|
||||
self = new SelfPopulatingCache<String, Iterable<SshKeyCacheEntry>>(raw) {
|
||||
@Override
|
||||
protected Iterable<SshKeyCacheEntry> createEntry(final String username)
|
||||
throws Exception {
|
||||
return lookup(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Iterable<SshKeyCacheEntry> missing(final String username) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return (Iterable<SshKeyCacheEntry>) e.getObjectValue();
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Can't get SSH keys for \"" + username + "\" from cache.", e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Iterable<SshKeyCacheEntry> get(String username) {
|
||||
return self.get(username);
|
||||
}
|
||||
|
||||
public void evict(String username) {
|
||||
if (username != null) {
|
||||
self.remove(username);
|
||||
self.remove(username);
|
||||
}
|
||||
|
||||
private Iterable<SshKeyCacheEntry> lookup(final String username)
|
||||
throws Exception {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
final Account user = db.accounts().bySshUserName(username);
|
||||
if (user == null) {
|
||||
return Collections.<SshKeyCacheEntry> emptyList();
|
||||
}
|
||||
|
||||
final List<SshKeyCacheEntry> kl = new ArrayList<SshKeyCacheEntry>(4);
|
||||
for (final AccountSshKey k : db.accountSshKeys().valid(user.getId())) {
|
||||
add(db, kl, k);
|
||||
}
|
||||
if (kl.isEmpty()) {
|
||||
return Collections.<SshKeyCacheEntry> emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(kl);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
|
||||
try {
|
||||
kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
|
||||
} catch (OutOfMemoryError e) {
|
||||
// This is the only case where we assume the problem has nothing
|
||||
// to do with the key object, and instead we must abort this load.
|
||||
//
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
markInvalid(db, k);
|
||||
}
|
||||
}
|
||||
|
||||
private void markInvalid(final ReviewDb db, final AccountSshKey k) {
|
||||
try {
|
||||
log.info("Flagging SSH key " + k.getKey() + " invalid");
|
||||
k.setInvalid();
|
||||
db.accountSshKeys().update(Collections.singleton(k));
|
||||
} catch (OrmException e) {
|
||||
log.error("Failed to mark SSH key" + k.getKey() + " invalid", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
// Copyright (C) 2009 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.ssh;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountSshKey;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
|
||||
import net.sf.ehcache.constructs.blocking.CacheEntryFactory;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
class SshKeyCacheEntryFactory implements CacheEntryFactory {
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
|
||||
SshKeyCacheEntryFactory(final SchemaFactory<ReviewDb> sf) {
|
||||
schema = sf;
|
||||
}
|
||||
|
||||
public Object createEntry(final Object genericKey) throws Exception {
|
||||
final String username = (String) genericKey;
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
final Account user = db.accounts().bySshUserName(username);
|
||||
if (user == null) {
|
||||
return Collections.<SshKeyCacheEntry> emptyList();
|
||||
}
|
||||
|
||||
final List<SshKeyCacheEntry> kl = new ArrayList<SshKeyCacheEntry>(4);
|
||||
for (final AccountSshKey k : db.accountSshKeys().valid(user.getId())) {
|
||||
add(db, kl, k);
|
||||
}
|
||||
if (kl.isEmpty()) {
|
||||
return Collections.<SshKeyCacheEntry> emptyList();
|
||||
}
|
||||
return Collections.unmodifiableList(kl);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void add(ReviewDb db, List<SshKeyCacheEntry> kl, AccountSshKey k) {
|
||||
try {
|
||||
kl.add(new SshKeyCacheEntry(k.getKey(), SshUtil.parse(k)));
|
||||
} catch (OutOfMemoryError e) {
|
||||
// This is the only case where we assume the problem has nothing
|
||||
// to do with the key object, and instead we must abort this load.
|
||||
//
|
||||
throw e;
|
||||
} catch (Throwable e) {
|
||||
markInvalid(db, k);
|
||||
}
|
||||
}
|
||||
|
||||
private void markInvalid(final ReviewDb db, final AccountSshKey k) {
|
||||
try {
|
||||
log.info("Flagging SSH key " + k.getKey() + " invalid");
|
||||
k.setInvalid();
|
||||
db.accountSshKeys().update(Collections.singleton(k));
|
||||
} catch (OrmException e) {
|
||||
log.error("Failed to mark SSH key" + k.getKey() + " invalid", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.ssh.commands;
|
||||
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.ssh.BaseCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -26,7 +27,7 @@ import java.util.TreeSet;
|
||||
|
||||
abstract class CacheCommand extends BaseCommand {
|
||||
@Inject
|
||||
protected CacheManager cacheMgr;
|
||||
protected CachePool cachePool;
|
||||
|
||||
protected SortedSet<String> cacheNames() {
|
||||
final SortedSet<String> names = new TreeSet<String>();
|
||||
@@ -37,6 +38,7 @@ abstract class CacheCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
protected Ehcache[] getAllCaches() {
|
||||
final CacheManager cacheMgr = cachePool.getCacheManager();
|
||||
final String[] cacheNames = cacheMgr.getCacheNames();
|
||||
Arrays.sort(cacheNames);
|
||||
final Ehcache[] r = new Ehcache[cacheNames.length];
|
||||
|
||||
Reference in New Issue
Block a user