Determines the kind for a Class, as understood by the datastore. The first class in a
@@ -246,11 +250,15 @@ public static com.google.appengine.api.datastore.Key key(Key> typed) {
*
If no @Entity annotation is found, just uses the simplename as is.
*/
public static String getKind(Class> clazz) {
- String kind = getKindRecursive(clazz);
- if (kind == null)
- return clazz.getSimpleName();
- else
- return kind;
+ String kind = kindCache.get(clazz);
+ if (kind == null) {
+ kind = getKindRecursive(clazz);
+ if (kind == null) {
+ kind = clazz.getSimpleName();
+ }
+ kindCache.put(clazz, kind);
+ }
+ return kind;
}
/**
diff --git a/src/main/java/com/googlecode/objectify/ObjectifyFactory.java b/src/main/java/com/googlecode/objectify/ObjectifyFactory.java
index 77af0845c..f0af964a8 100644
--- a/src/main/java/com/googlecode/objectify/ObjectifyFactory.java
+++ b/src/main/java/com/googlecode/objectify/ObjectifyFactory.java
@@ -55,9 +55,16 @@ public class ObjectifyFactory implements Forge
/** Tracks stats */
protected EntityMemcacheStats memcacheStats = new EntityMemcacheStats();
- /** Manages caching of entities at a low level */
- protected EntityMemcache entityMemcache = new EntityMemcache(MEMCACHE_NAMESPACE, new CacheControlImpl(this), this.memcacheStats);
-
+ /**
+ * Manages caching of entities at a low level. Lazily instantiated on the first register() of a cacheable entity.
+ */
+ protected EntityMemcache entityMemcache;
+
+ /** Override this if you need special behavior from your EntityMemcache */
+ protected EntityMemcache createEntityMemcache() {
+ return new EntityMemcache(MEMCACHE_NAMESPACE, new CacheControlImpl(this), this.memcacheStats);
+ }
+
/**
*
Construct an instance of the specified type. Objectify uses this method whenever possible to create
* instances of entities, condition classes, or other types; by overriding this method you can substitute Guice or other
@@ -168,6 +175,9 @@ public Objectify begin() {
*/
public void register(Class clazz) {
this.registrar.register(clazz);
+
+ if (this.entityMemcache == null && this.registrar.isCacheEnabled())
+ this.entityMemcache = createEntityMemcache();
}
/**
diff --git a/src/main/java/com/googlecode/objectify/ObjectifyService.java b/src/main/java/com/googlecode/objectify/ObjectifyService.java
index 59c1cb75e..82ab4f08f 100644
--- a/src/main/java/com/googlecode/objectify/ObjectifyService.java
+++ b/src/main/java/com/googlecode/objectify/ObjectifyService.java
@@ -5,6 +5,7 @@
import com.googlecode.objectify.cache.PendingFutures;
import com.googlecode.objectify.util.Closeable;
+
import java.util.ArrayDeque;
import java.util.Deque;
@@ -109,17 +110,21 @@ public void close() {
if (stack.isEmpty())
throw new IllegalStateException("You have already destroyed the Objectify context.");
- // Same comment as above - we can't make claims about the state of the stack beacuse of dispatch forwarding
- //if (stack.size() > 1)
- // throw new IllegalStateException("You are trying to close the root session before all transactions have been unwound.");
-
- // The order of these three operations is significant
+ try {
+ // Same comment as above - we can't make claims about the state of the stack
+ // beacuse of dispatch forwarding
+ // if (stack.size() > 1)
+ // throw new IllegalStateException("You are trying to close the root session
+ // before all transactions have been unwound.");
- ofy.flush();
+ // The order of these three operations is significant
- PendingFutures.completeAllPendingFutures();
+ ofy.flush();
- stack.removeLast();
+ PendingFutures.completeAllPendingFutures();
+ } finally {
+ stack.removeLast();
+ }
}
};
}
diff --git a/src/main/java/com/googlecode/objectify/cache/CachingAsyncDatastoreService.java b/src/main/java/com/googlecode/objectify/cache/CachingAsyncDatastoreService.java
index 28621396e..fef910253 100644
--- a/src/main/java/com/googlecode/objectify/cache/CachingAsyncDatastoreService.java
+++ b/src/main/java/com/googlecode/objectify/cache/CachingAsyncDatastoreService.java
@@ -275,8 +275,14 @@ public void success(Map result)
if (value != null)
buck.setNext(value);
}
-
- memcache.putAll(uncached);
+ try{
+ memcache.putAll(uncached);
+ } catch(Exception ex){
+ // It is possible that memcache is unreachable. Continue if
+ // this happens as data was retrieved from datastore and
+ // will result in a cache miss on next access where we
+ // can try to cache again.
+ }
}
};
}
diff --git a/src/main/java/com/googlecode/objectify/cache/EntityMemcache.java b/src/main/java/com/googlecode/objectify/cache/EntityMemcache.java
index 00b00faa9..fc51a19fd 100644
--- a/src/main/java/com/googlecode/objectify/cache/EntityMemcache.java
+++ b/src/main/java/com/googlecode/objectify/cache/EntityMemcache.java
@@ -5,6 +5,7 @@
import com.google.appengine.api.memcache.ErrorHandlers;
import com.google.appengine.api.memcache.Expiration;
import com.google.appengine.api.memcache.IMemcacheServiceFactory;
+import com.google.appengine.api.memcache.MemcacheService;
import com.google.appengine.api.memcache.MemcacheService.CasValues;
import com.google.appengine.api.memcache.MemcacheService.IdentifiableValue;
import com.google.appengine.spi.ServiceFactoryFactory;
@@ -19,6 +20,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.logging.Level;
/**
@@ -175,10 +177,17 @@ public EntityMemcache(
MemcacheStats stats,
IMemcacheServiceFactory memcacheServiceFactory)
{
- this.memcache = new KeyMemcacheService(memcacheServiceFactory.getMemcacheService(namespace));
+ this(cacheControl, stats, memcacheServiceFactory.getMemcacheService(namespace));
+ }
+
+ public EntityMemcache(
+ CacheControl cacheControl,
+ MemcacheStats stats,
+ MemcacheService memcacheService)
+ {
+ this.memcache = new KeyMemcacheService(memcacheService);
this.memcache.setErrorHandler(ErrorHandlers.getConsistentLogAndContinue(Level.SEVERE));
- this.memcacheWithRetry = new KeyMemcacheService(
- MemcacheServiceRetryProxy.createProxy(memcacheServiceFactory.getMemcacheService(namespace)));
+ this.memcacheWithRetry = new KeyMemcacheService(MemcacheServiceRetryProxy.createProxy(memcacheService));
this.stats = stats;
this.cacheControl = cacheControl;
}
@@ -238,10 +247,11 @@ public Map getAll(Iterable keys)
if (!cold.isEmpty())
{
- // The cache is cold for those values, so start them out with nulls that we can make an IV for
- this.memcache.putAll(cold);
-
try {
+ // The cache is cold for those values, so start them out with nulls that we can make an IV for
+ // It is possible that memcache is unreachable, catch that failure.
+ this.memcache.putAll(cold);
+
Map ivs2 = this.memcache.getIdentifiables(cold.keySet());
ivs.putAll(ivs2);
} catch (Exception ex) {
diff --git a/src/main/java/com/googlecode/objectify/impl/TypeUtils.java b/src/main/java/com/googlecode/objectify/impl/TypeUtils.java
index 1008e1877..2cdae9446 100644
--- a/src/main/java/com/googlecode/objectify/impl/TypeUtils.java
+++ b/src/main/java/com/googlecode/objectify/impl/TypeUtils.java
@@ -10,6 +10,11 @@
import java.util.HashMap;
import java.util.Map;
+import com.google.common.base.Optional;
+import com.google.common.collect.Maps;
+
+import lombok.Value;
+
/**
*/
public class TypeUtils
@@ -30,13 +35,20 @@ public class TypeUtils
private TypeUtils() {
}
+ private static final Map, Constructor>> noArgConstructorCache = Maps.newConcurrentMap();
+
/**
* Throw an IllegalStateException if the class does not have a no-arg constructor.
*/
public static Constructor getNoArgConstructor(Class clazz) {
try {
- Constructor ctor = clazz.getDeclaredConstructor(new Class[0]);
- ctor.setAccessible(true);
+ @SuppressWarnings("unchecked")
+ Constructor ctor = (Constructor) noArgConstructorCache.get(clazz);
+ if (ctor == null) {
+ ctor = clazz.getDeclaredConstructor(new Class[0]);
+ ctor.setAccessible(true);
+ noArgConstructorCache.put(clazz, ctor);
+ }
return ctor;
}
catch (NoSuchMethodException e) {
@@ -113,11 +125,27 @@ public static A getAnnotation(Annotation[] annotations, C
return null;
}
+ @Value
+ private static final class DeclaredAnnotationCacheKey {
+ private final Class> onClass;
+ private final Class extends Annotation> annotationType;
+ }
+
+ private static final Map> declaredAnnotationCache = Maps.newConcurrentMap();
+
/**
* Get the declared annotation, ignoring any inherited annotations
*/
public static A getDeclaredAnnotation(Class> onClass, Class annotationType) {
- return getAnnotation(onClass.getDeclaredAnnotations(), annotationType);
+ final DeclaredAnnotationCacheKey key = new DeclaredAnnotationCacheKey(onClass, annotationType);
+
+ @SuppressWarnings("unchecked")
+ Optional value = (Optional) declaredAnnotationCache.get(key);
+ if (value == null) {
+ value = Optional.fromNullable(getAnnotation(onClass.getDeclaredAnnotations(), annotationType));
+ declaredAnnotationCache.put(key, value);
+ }
+ return value.orNull();
}
/**
diff --git a/src/main/java/com/googlecode/objectify/impl/WriteEngine.java b/src/main/java/com/googlecode/objectify/impl/WriteEngine.java
index ca16678fb..2294d9296 100644
--- a/src/main/java/com/googlecode/objectify/impl/WriteEngine.java
+++ b/src/main/java/com/googlecode/objectify/impl/WriteEngine.java
@@ -65,8 +65,11 @@ public Result