Skip to content

Commit 69175ab

Browse files
committed
Implement defer()
1 parent 8025d3d commit 69175ab

18 files changed

Lines changed: 381 additions & 78 deletions

pom.xml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,12 +220,26 @@
220220
</dependency>
221221

222222
<!-- Testing -->
223+
<dependency>
224+
<groupId>org.hamcrest</groupId>
225+
<artifactId>hamcrest-core</artifactId>
226+
<version>1.3</version>
227+
<scope>test</scope>
228+
</dependency>
229+
<dependency>
230+
<groupId>org.hamcrest</groupId>
231+
<artifactId>hamcrest-integration</artifactId>
232+
<version>1.3</version>
233+
<scope>test</scope>
234+
</dependency>
235+
223236
<dependency>
224237
<groupId>org.testng</groupId>
225238
<artifactId>testng</artifactId>
226239
<version>6.8</version>
227240
<scope>test</scope>
228241
</dependency>
242+
229243
<dependency>
230244
<groupId>org.mockito</groupId>
231245
<artifactId>mockito-core</artifactId>

src/main/java/com/googlecode/objectify/Objectify.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import com.google.appengine.api.datastore.ReadPolicy.Consistency;
44
import com.google.appengine.api.datastore.Transaction;
5-
import com.googlecode.objectify.cmd.Deferrer;
5+
import com.googlecode.objectify.cmd.Deferred;
66
import com.googlecode.objectify.cmd.Deleter;
77
import com.googlecode.objectify.cmd.Loader;
88
import com.googlecode.objectify.cmd.Saver;
@@ -82,7 +82,7 @@ public interface Objectify
8282
*
8383
* @return the next step in the immutable command chain.
8484
*/
85-
Deferrer defer();
85+
Deferred defer();
8686

8787
/**
8888
* Obtain the ObjectifyFactory from which this Objectify instance was created.
@@ -231,6 +231,14 @@ public interface Objectify
231231
*/
232232
<R> R execute(TxnType txnType, Work<R> work);
233233

234+
/**
235+
* Synchronously flushes any deferred operations to the datastore. Objectify does this for you at the end
236+
* of transactions and requests, but if you need data to be written immediately - say, you're about to perform
237+
* a strongly-consistent ancestor query and you need to see the updated indexes immediately - you can call this
238+
* method. If there are no deferred operations, this does nothing.
239+
*/
240+
void flush();
241+
234242
/**
235243
* <p>Clears the session; all subsequent requests (or Ref<?>.get() calls) will go to the datastore/memcache
236244
* to repopulate the session. This should rarely, if ever be necessary. Note that if you iterate query results

src/main/java/com/googlecode/objectify/ObjectifyService.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,27 +95,36 @@ public static Closeable begin() {
9595
if (!stack.isEmpty())
9696
throw new IllegalStateException("You already have an initial Objectify context. Perhaps you want to use the ofy() method?");
9797

98-
stack.add(factory.begin());
98+
final Objectify ofy = factory.begin();
99+
100+
stack.add(ofy);
99101

100102
return new Closeable() {
101103
@Override
102104
public void close() {
103105
if (stack.isEmpty())
104106
throw new IllegalStateException("You have already destroyed the Objectify context.");
105107

108+
if (stack.size() > 1)
109+
throw new IllegalStateException("You are trying to close the root session before all transactions have been unwound.");
110+
111+
// The order of these three operations is significant
112+
113+
ofy.flush();
114+
106115
PendingFutures.completeAllPendingFutures();
107116

108117
stack.removeLast();
109118
}
110119
};
111120
}
112121

113-
/** Pushes new context onto stack when a transaction starts */
122+
/** Pushes new context onto stack when a transaction starts. For internal housekeeping only. */
114123
public static void push(Objectify ofy) {
115124
STACK.get().add(ofy);
116125
}
117126

118-
/** Pops context off of stack after a transaction completes */
127+
/** Pops context off of stack after a transaction completes. For internal housekeeping only. */
119128
public static void pop() {
120129
STACK.get().removeLast();
121130
}

src/main/java/com/googlecode/objectify/cmd/Deferrer.java renamed to src/main/java/com/googlecode/objectify/cmd/Deferred.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
* @author Jeff Schnitzer <jeff@infohazard.org>
77
*/
8-
public interface Deferrer {
8+
public interface Deferred {
99
/**
1010
* <p>Start a deferred save command chain. Allows you to save (or re-save) entity objects. Note that all command
1111
* chain objects are immutable.</p>

src/main/java/com/googlecode/objectify/impl/DeferredDeleterImpl.java

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@
33
import com.googlecode.objectify.Key;
44
import com.googlecode.objectify.cmd.DeferredDeleteType;
55
import com.googlecode.objectify.cmd.DeferredDeleter;
6-
import java.util.ArrayList;
7-
import java.util.Arrays;
8-
import java.util.List;
96

107

118
/**
@@ -30,35 +27,35 @@ public DeferredDeleteType type(Class<?> type) {
3027

3128
@Override
3229
public void key(Key<?> key) {
33-
this.keys(key);
30+
ofy.deferDelete(key);
3431
}
3532

3633
@Override
3734
public void keys(Key<?>... keys) {
38-
this.keys(Arrays.asList(keys));
35+
for (Key<?> key: keys)
36+
key(key);
3937
}
4038

4139
@Override
4240
public void keys(Iterable<? extends Key<?>> keys) {
43-
this.entities(keys);
41+
for (Key<?> key: keys)
42+
key(key);
4443
}
4544

4645
@Override
4746
public void entity(Object entity) {
48-
this.entities(entity);
47+
key(ofy.factory().keys().anythingToKey(entity));
4948
}
5049

5150
@Override
5251
public void entities(Iterable<?> entities) {
53-
List<com.google.appengine.api.datastore.Key> keys = new ArrayList<>();
54-
for (Object obj: entities)
55-
keys.add(ofy.factory().keys().anythingToRawKey(obj));
56-
57-
//return ofy.createWriteEngine().delete(keys);
52+
for (Object entity: entities)
53+
entity(entity);
5854
}
5955

6056
@Override
6157
public void entities(Object... entities) {
62-
this.entities(Arrays.asList(entities));
58+
for (Object entity: entities)
59+
entity(entity);
6360
}
6461
}

src/main/java/com/googlecode/objectify/impl/DeferrerImpl.java renamed to src/main/java/com/googlecode/objectify/impl/DeferredImpl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
package com.googlecode.objectify.impl;
22

3+
import com.googlecode.objectify.cmd.Deferred;
34
import com.googlecode.objectify.cmd.DeferredDeleter;
45
import com.googlecode.objectify.cmd.DeferredSaver;
5-
import com.googlecode.objectify.cmd.Deferrer;
66

77

88
/**
99
* Implementation of the Deferrer interface.
1010
*
1111
* @author Jeff Schnitzer <jeff@infohazard.org>
1212
*/
13-
public class DeferrerImpl implements Deferrer
13+
public class DeferredImpl implements Deferred
1414
{
1515
/** */
1616
ObjectifyImpl<?> ofy;
1717

1818
/** */
19-
public DeferrerImpl(ObjectifyImpl<?> ofy) {
19+
public DeferredImpl(ObjectifyImpl<?> ofy) {
2020
this.ofy = ofy;
2121
}
2222

src/main/java/com/googlecode/objectify/impl/DeferredSaverImpl.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package com.googlecode.objectify.impl;
22

33
import com.googlecode.objectify.cmd.DeferredSaver;
4-
import java.util.Arrays;
5-
import java.util.Collections;
64

75

86
/**
@@ -22,16 +20,18 @@ public DeferredSaverImpl(ObjectifyImpl<?> ofy) {
2220

2321
@Override
2422
public void entity(Object entity) {
25-
this.entities(Collections.singleton(entity));
23+
ofy.deferSave(entity);
2624
}
2725

2826
@Override
2927
public void entities(Object... entities) {
30-
this.entities(Arrays.asList(entities));
28+
for (Object entity: entities)
29+
entity(entity);
3130
}
3231

3332
@Override
3433
public void entities(final Iterable<?> entities) {
35-
//return ofy.createWriteEngine().<E>save(entities);
34+
for (Object entity: entities)
35+
entity(entity);
3636
}
3737
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.googlecode.objectify.impl;
2+
3+
import com.googlecode.objectify.Key;
4+
import com.googlecode.objectify.Objectify;
5+
import com.googlecode.objectify.Result;
6+
import com.googlecode.objectify.util.ResultNow;
7+
import java.util.ArrayList;
8+
import java.util.HashMap;
9+
import java.util.List;
10+
import java.util.Map;
11+
12+
/**
13+
* Manages all the logic of deferring operations
14+
*/
15+
public class Deferrer {
16+
17+
/** */
18+
private final Objectify ofy;
19+
20+
/** */
21+
private final Session session;
22+
23+
/** Values of null mean "delete" */
24+
private Map<Key<?>, Object> operations = new HashMap<>();
25+
26+
public Deferrer(Objectify ofy, Session session) {
27+
this.ofy = ofy;
28+
this.session = session;
29+
}
30+
31+
public void deferSave(Key<Object> key, Object entity) {
32+
session.addValue(key, entity);
33+
operations.put(key, entity);
34+
}
35+
36+
public void deferDelete(Key<?> key) {
37+
session.addValue(key, null);
38+
operations.put(key, null);
39+
}
40+
41+
public void flush() {
42+
if (operations.isEmpty())
43+
return;
44+
45+
// Sort into two batch operations: one for save, one for delete.
46+
List<Object> saves = new ArrayList<>();
47+
List<Key<?>> deletes = new ArrayList<>();
48+
49+
for (Map.Entry<Key<?>, Object> entry : operations.entrySet()) {
50+
if (entry.getValue() == null)
51+
deletes.add(entry.getKey());
52+
else
53+
saves.add(entry.getValue());
54+
}
55+
56+
// Do this async so we get parallelism, but force it to be complete in the end.
57+
58+
Result<?> savesFuture = new ResultNow<>(null);
59+
Result<?> deletesFuture = new ResultNow<>(null);
60+
61+
if (!saves.isEmpty())
62+
savesFuture = ofy.save().entities(saves);
63+
64+
if (!deletes.isEmpty())
65+
deletesFuture = ofy.delete().keys(deletes);
66+
67+
savesFuture.now();
68+
deletesFuture.now();
69+
}
70+
}

src/main/java/com/googlecode/objectify/impl/ObjectifyImpl.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import com.googlecode.objectify.TxnType;
1111
import com.googlecode.objectify.Work;
1212
import com.googlecode.objectify.annotation.Entity;
13-
import com.googlecode.objectify.cmd.Deferrer;
13+
import com.googlecode.objectify.cmd.Deferred;
1414
import com.googlecode.objectify.cmd.Deleter;
1515
import com.googlecode.objectify.cmd.Loader;
1616
import com.googlecode.objectify.cmd.Saver;
@@ -42,7 +42,7 @@ public class ObjectifyImpl<O extends Objectify> implements Objectify, Cloneable
4242
protected Double deadline;
4343

4444
/** */
45-
protected Transactor<O> transactor = new TransactorNo<>();
45+
protected Transactor<O> transactor = new TransactorNo<>(this);
4646

4747
/**
4848
*/
@@ -94,8 +94,8 @@ public Deleter delete() {
9494
* @see com.googlecode.objectify.Objectify#defer()
9595
*/
9696
@Override
97-
public Deferrer defer() {
98-
return new DeferrerImpl(this);
97+
public Deferred defer() {
98+
return new DeferredImpl(this);
9999
}
100100

101101
/* (non-Javadoc)
@@ -302,11 +302,23 @@ public boolean isLoaded(Key<?> key) {
302302
return transactor.getSession().contains(key);
303303
}
304304

305+
@Override
306+
public void flush() {
307+
transactor.getDeferrer().flush();
308+
}
309+
305310
/**
306-
* Flush any deferred operations
311+
* Defer the saving of one entity. Updates the session cache with this new value.
307312
*/
308-
void flush() {
313+
void deferSave(Object entity) {
314+
transactor.getDeferrer().deferSave(factory().keys().keyOf(entity), entity);
315+
}
309316

317+
/**
318+
* Defer the deletion of one entity. Updates the session cache with this new value.
319+
*/
320+
void deferDelete(Key<?> key) {
321+
transactor.getDeferrer().deferDelete(key);
310322
}
311323

312324
}

src/main/java/com/googlecode/objectify/impl/Session.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.googlecode.objectify.impl;
22

33
import com.googlecode.objectify.Key;
4-
4+
import com.googlecode.objectify.util.ResultNow;
55
import java.util.HashMap;
66
import java.util.Map;
77
import java.util.logging.Level;
@@ -30,6 +30,13 @@ public void add(Key<?> key, SessionValue<?> value) {
3030
map.put(key, value);
3131
}
3232

33+
/**
34+
* Convenience method
35+
*/
36+
public void addValue(Key<?> key, Object value) {
37+
add(key, new SessionValue<>(new ResultNow<Object>(value)));
38+
}
39+
3340
/** Add all entries in the other session to this one */
3441
public void addAll(Session other) {
3542
if (log.isLoggable(Level.FINEST))

0 commit comments

Comments
 (0)