Skip to content

Commit 0f2fb7d

Browse files
author
Yaniv Inbar
committed
1 parent ed94823 commit 0f2fb7d

File tree

36 files changed

+3054
-0
lines changed

36 files changed

+3054
-0
lines changed

google-http-client-appengine/pom.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,35 @@
4141
<groupId>com.google.http-client</groupId>
4242
<artifactId>google-http-client</artifactId>
4343
</dependency>
44+
<dependency>
45+
<groupId>com.google.http-client</groupId>
46+
<artifactId>google-http-client-test</artifactId>
47+
<scope>test</scope>
48+
</dependency>
4449
<dependency>
4550
<groupId>com.google.appengine</groupId>
4651
<artifactId>appengine-api-1.0-sdk</artifactId>
4752
<scope>provided</scope>
4853
</dependency>
54+
<dependency>
55+
<groupId>com.google.appengine</groupId>
56+
<artifactId>appengine-testing</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>com.google.appengine</groupId>
61+
<artifactId>appengine-api-stubs</artifactId>
62+
<scope>test</scope>
63+
</dependency>
4964
<dependency>
5065
<groupId>junit</groupId>
5166
<artifactId>junit</artifactId>
5267
<scope>test</scope>
5368
</dependency>
69+
<dependency>
70+
<groupId>com.google.guava</groupId>
71+
<artifactId>guava-jdk5</artifactId>
72+
<scope>test</scope>
73+
</dependency>
5474
</dependencies>
5575
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
/*
2+
* Copyright (c) 2013 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
package com.google.api.client.extensions.appengine.datastore;
16+
17+
import com.google.api.client.util.Beta;
18+
import com.google.api.client.util.IOUtils;
19+
import com.google.api.client.util.Lists;
20+
import com.google.api.client.util.Maps;
21+
import com.google.api.client.util.Preconditions;
22+
import com.google.api.client.util.Sets;
23+
import com.google.api.client.util.store.AbstractDataStore;
24+
import com.google.api.client.util.store.AbstractDataStoreFactory;
25+
import com.google.api.client.util.store.DataStore;
26+
import com.google.api.client.util.store.DataStoreUtils;
27+
import com.google.appengine.api.datastore.Blob;
28+
import com.google.appengine.api.datastore.DatastoreService;
29+
import com.google.appengine.api.datastore.DatastoreServiceFactory;
30+
import com.google.appengine.api.datastore.Entity;
31+
import com.google.appengine.api.datastore.EntityNotFoundException;
32+
import com.google.appengine.api.datastore.Key;
33+
import com.google.appengine.api.datastore.KeyFactory;
34+
import com.google.appengine.api.datastore.Query;
35+
import com.google.appengine.api.memcache.Expiration;
36+
import com.google.appengine.api.memcache.MemcacheService;
37+
import com.google.appengine.api.memcache.MemcacheServiceFactory;
38+
39+
import java.io.IOException;
40+
import java.io.ObjectInputStream;
41+
import java.io.Serializable;
42+
import java.util.Collection;
43+
import java.util.Collections;
44+
import java.util.List;
45+
import java.util.Map;
46+
import java.util.Set;
47+
import java.util.concurrent.locks.Lock;
48+
import java.util.concurrent.locks.ReentrantLock;
49+
50+
/**
51+
* {@link Beta} <br/>
52+
* Thread-safe Google App Engine implementation of a data store factory that directly uses the App
53+
* Engine Data Store API.
54+
*
55+
* <p>
56+
* By default, it uses the Memcache API as an in-memory data cache. To disable it, call
57+
* {@link Builder#setDisableMemcache(boolean)}. The Memcache is only read to check if a key already
58+
* has a value inside {@link DataStore#get(String)}. The values in the Memcache are updated in the
59+
* {@link DataStore#get(String)}, {@link DataStore#set(String, Serializable)},
60+
* {@link DataStore#delete(String)}, {@link DataStore#values()}, and {@link DataStore#clear()}
61+
* methods.
62+
* </p>
63+
*
64+
* @since 1.16
65+
* @author Yaniv Inbar
66+
*/
67+
@Beta
68+
public class AppEngineDataStoreFactory extends AbstractDataStoreFactory {
69+
70+
/** Whether to disable the memcache (which is enabled by default). */
71+
final boolean disableMemcache;
72+
73+
/** Memcache expiration policy on puts. */
74+
final Expiration memcacheExpiration;
75+
76+
@Override
77+
protected <V extends Serializable> DataStore<V> createDataStore(String id) throws IOException {
78+
return new AppEngineDataStore<V>(this, id);
79+
}
80+
81+
public AppEngineDataStoreFactory() {
82+
this(new Builder());
83+
}
84+
85+
/**
86+
* @param builder builder
87+
*/
88+
public AppEngineDataStoreFactory(Builder builder) {
89+
disableMemcache = builder.disableMemcache;
90+
memcacheExpiration = builder.memcacheExpiration;
91+
}
92+
93+
/** Returns whether to disable the memcache (which is enabled by default). */
94+
public boolean getDisableMemcache() {
95+
return disableMemcache;
96+
}
97+
98+
/**
99+
* Returns a global thread-safe instance based on the default constructor
100+
* {@link #AppEngineDataStoreFactory()}.
101+
*/
102+
public static AppEngineDataStoreFactory getDefaultInstance() {
103+
return InstanceHolder.INSTANCE;
104+
}
105+
106+
/** Holder for the result of {@link #getDefaultInstance()}. */
107+
@Beta
108+
static class InstanceHolder {
109+
static final AppEngineDataStoreFactory INSTANCE = new AppEngineDataStoreFactory();
110+
}
111+
112+
@Beta
113+
static class AppEngineDataStore<V extends Serializable> extends AbstractDataStore<V> {
114+
115+
/** Lock on access to the store. */
116+
private final Lock lock = new ReentrantLock();
117+
118+
/** Name of the field in which the value is stored. */
119+
private static final String FIELD_VALUE = "value";
120+
121+
/** The service instance used to access the Memcache API. */
122+
private final MemcacheService memcache;
123+
124+
/** Data store service. */
125+
private final DatastoreService dataStoreService;
126+
127+
/** Memcache expiration policy on puts. */
128+
final Expiration memcacheExpiration;
129+
130+
AppEngineDataStore(AppEngineDataStoreFactory dataStoreFactory, String id) {
131+
super(dataStoreFactory, id);
132+
memcache =
133+
dataStoreFactory.disableMemcache ? null : MemcacheServiceFactory.getMemcacheService(id);
134+
memcacheExpiration = dataStoreFactory.memcacheExpiration;
135+
dataStoreService = DatastoreServiceFactory.getDatastoreService();
136+
}
137+
138+
/** Deserializes the specified object from a Blob using an {@link ObjectInputStream}. */
139+
private V deserialize(Entity entity) throws IOException {
140+
Blob blob = (Blob) entity.getProperty(FIELD_VALUE);
141+
return IOUtils.deserialize(blob.getBytes());
142+
}
143+
144+
@Override
145+
public Set<String> keySet() throws IOException {
146+
lock.lock();
147+
try {
148+
// NOTE: not possible with memcache
149+
Set<String> result = Sets.newHashSet();
150+
for (Entity entity : query(true)) {
151+
result.add(entity.getKey().getName());
152+
}
153+
return Collections.unmodifiableSet(result);
154+
} finally {
155+
lock.unlock();
156+
}
157+
}
158+
159+
@Override
160+
public Collection<V> values() throws IOException {
161+
lock.lock();
162+
try {
163+
// Unfortunately no getKeys() method on MemcacheService, so the only option is to clear all
164+
// and re-populate the memcache from scratch. This is clearly inefficient.
165+
if (memcache != null) {
166+
memcache.clearAll();
167+
}
168+
List<V> result = Lists.newArrayList();
169+
Map<String, V> map = memcache != null ? Maps.<String, V>newHashMap() : null;
170+
for (Entity entity : query(false)) {
171+
V value = deserialize(entity);
172+
result.add(value);
173+
if (map != null) {
174+
map.put(entity.getKey().getName(), value);
175+
}
176+
}
177+
if (memcache != null) {
178+
memcache.putAll(map, memcacheExpiration);
179+
}
180+
return Collections.unmodifiableList(result);
181+
} finally {
182+
lock.unlock();
183+
}
184+
}
185+
186+
@Override
187+
public V get(String key) throws IOException {
188+
if (key == null) {
189+
return null;
190+
}
191+
lock.lock();
192+
try {
193+
if (memcache != null && memcache.contains(key)) {
194+
@SuppressWarnings("unchecked")
195+
V result = (V) memcache.get(key);
196+
return result;
197+
}
198+
Key dataKey = KeyFactory.createKey(getId(), key);
199+
Entity entity;
200+
try {
201+
entity = dataStoreService.get(dataKey);
202+
} catch (EntityNotFoundException exception) {
203+
if (memcache != null) {
204+
memcache.delete(key);
205+
}
206+
return null;
207+
}
208+
V result = deserialize(entity);
209+
if (memcache != null) {
210+
memcache.put(key, result, memcacheExpiration);
211+
}
212+
return result;
213+
} finally {
214+
lock.unlock();
215+
}
216+
}
217+
218+
@Override
219+
public AppEngineDataStore<V> set(String key, V value) throws IOException {
220+
Preconditions.checkNotNull(key);
221+
Preconditions.checkNotNull(value);
222+
lock.lock();
223+
try {
224+
Entity entity = new Entity(getId(), key);
225+
entity.setUnindexedProperty(FIELD_VALUE, new Blob(IOUtils.serialize(value)));
226+
dataStoreService.put(entity);
227+
if (memcache != null) {
228+
memcache.put(key, value, memcacheExpiration);
229+
}
230+
} finally {
231+
lock.unlock();
232+
}
233+
return this;
234+
}
235+
236+
@Override
237+
public DataStore<V> delete(String key) throws IOException {
238+
if (key == null) {
239+
return this;
240+
}
241+
lock.lock();
242+
try {
243+
dataStoreService.delete(KeyFactory.createKey(getId(), key));
244+
if (memcache != null) {
245+
memcache.delete(key);
246+
}
247+
} finally {
248+
lock.unlock();
249+
}
250+
return this;
251+
}
252+
253+
@Override
254+
public AppEngineDataStore<V> clear() throws IOException {
255+
lock.lock();
256+
try {
257+
if (memcache != null) {
258+
memcache.clearAll();
259+
}
260+
// no clearAll() method on DataStoreService so have to query all keys & delete them
261+
List<Key> keys = Lists.newArrayList();
262+
for (Entity entity : query(true)) {
263+
keys.add(entity.getKey());
264+
}
265+
dataStoreService.delete(keys);
266+
} finally {
267+
lock.unlock();
268+
}
269+
return this;
270+
}
271+
272+
@Override
273+
public AppEngineDataStoreFactory getDataStoreFactory() {
274+
return (AppEngineDataStoreFactory) super.getDataStoreFactory();
275+
}
276+
277+
@Override
278+
public String toString() {
279+
return DataStoreUtils.toString(this);
280+
}
281+
282+
/**
283+
* Query on all of the keys for the data store ID.
284+
*
285+
* @param keysOnly whether to call {@link Query#setKeysOnly()}
286+
* @return iterable over the entities in the query result
287+
*/
288+
private Iterable<Entity> query(boolean keysOnly) {
289+
Query query = new Query(getId());
290+
if (keysOnly) {
291+
query.setKeysOnly();
292+
}
293+
return dataStoreService.prepare(query).asIterable();
294+
}
295+
}
296+
297+
/**
298+
* {@link Beta} <br/>
299+
* App Engine data store factory builder.
300+
*
301+
* <p>
302+
* Implementation is not thread-safe.
303+
* </p>
304+
*
305+
* @since 1.16
306+
*/
307+
@Beta
308+
public static class Builder {
309+
310+
/** Whether to disable the memcache. */
311+
boolean disableMemcache;
312+
313+
/** Memcache expiration policy on puts. */
314+
Expiration memcacheExpiration;
315+
316+
/** Returns whether to disable the memcache. */
317+
public final boolean getDisableMemcache() {
318+
return disableMemcache;
319+
}
320+
321+
/**
322+
* Sets whether to disable the memcache ({@code false} by default).
323+
*
324+
* <p>
325+
* Overriding is only supported for the purpose of calling the super implementation and changing
326+
* the return type, but nothing else.
327+
* </p>
328+
*/
329+
public Builder setDisableMemcache(boolean disableMemcache) {
330+
this.disableMemcache = disableMemcache;
331+
return this;
332+
}
333+
334+
/** Returns the Memcache expiration policy on puts. */
335+
public final Expiration getMemcacheExpiration() {
336+
return memcacheExpiration;
337+
}
338+
339+
/**
340+
* Sets the Memcache expiration policy on puts.
341+
*
342+
* <p>
343+
* Overriding is only supported for the purpose of calling the super implementation and changing
344+
* the return type, but nothing else.
345+
* </p>
346+
*/
347+
public Builder setMemcacheExpiration(Expiration memcacheExpiration) {
348+
this.memcacheExpiration = memcacheExpiration;
349+
return this;
350+
}
351+
352+
/** Returns a new App Engine data store factory instance. */
353+
public AppEngineDataStoreFactory build() {
354+
return new AppEngineDataStoreFactory();
355+
}
356+
}
357+
}

0 commit comments

Comments
 (0)