1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.memcache
;
5 import static com
.google
.common
.io
.BaseEncoding
.base64
;
6 import static java
.nio
.charset
.StandardCharsets
.US_ASCII
;
7 import static java
.nio
.charset
.StandardCharsets
.UTF_8
;
9 import com
.google
.common
.primitives
.Bytes
;
11 import java
.io
.ByteArrayInputStream
;
12 import java
.io
.ByteArrayOutputStream
;
13 import java
.io
.IOException
;
14 import java
.io
.ObjectInputStream
;
15 import java
.io
.ObjectOutputStream
;
16 import java
.io
.ObjectStreamClass
;
17 import java
.io
.Serializable
;
18 import java
.math
.BigInteger
;
19 import java
.security
.MessageDigest
;
20 import java
.security
.NoSuchAlgorithmException
;
21 import java
.util
.logging
.Level
;
22 import java
.util
.logging
.Logger
;
25 * Static serialization helpers shared by {@link MemcacheServiceImpl} and
26 * {@link com.google.appengine.api.memcache.dev.LocalMemcacheService}.
28 * This class is thread-safe.
32 public class MemcacheSerialization
{
35 * Values used as flags on the MemcacheService's values.
47 private static final Flag
[] VALUES
= Flag
.values();
50 * While the enum is convenient, the implementation wants {@code int}s...
51 * this factory converts {@code int} value to Flag value.
53 public static Flag
fromInt(int i
) {
54 if (i
< 0 || i
>= VALUES
.length
) {
55 throw new IllegalArgumentException();
62 * Tuple of a serialized byte array value and associated flags to interpret
63 * that value. Used as the return from {@link MemcacheSerialization#serialize}.
65 public static class ValueAndFlags
{
66 public final byte[] value
;
67 public final Flag flags
;
69 private ValueAndFlags(byte[] value
, Flag flags
) {
75 /** Limit determined by memcache backend. */
76 static final int MAX_KEY_BYTE_COUNT
= 250;
78 private static final byte FALSE_VALUE
= '0';
79 private static final byte TRUE_VALUE
= '1';
80 private static final String MYCLASSNAME
= MemcacheSerialization
.class.getName();
83 * The SHA1 checksum engine, cached for reuse. We did test the hashing time
84 * was negligible (17us/kb, linear); we don't "need" crypto-secure, but it's
85 * a good way to minimize collisions.
87 private static final MessageDigest SHA1
;
91 SHA1
= MessageDigest
.getInstance("SHA-1");
92 } catch (NoSuchAlgorithmException ex
) {
93 Logger
.getLogger(MYCLASSNAME
).log(Level
.SEVERE
,
94 "Can't load SHA-1 MessageDigest!", ex
);
95 throw new MemcacheServiceException("No SHA-1 algorithm, cannot hash keys for memcache", ex
);
99 public static final String USE_THREAD_CONTEXT_CLASSLOADER_PROPERTY
=
100 "appengine.api.memcache.useThreadContextClassLoader";
102 private static class UseThreadContextClassLoaderHolder
{
103 static final boolean INSTANCE
=
104 Boolean
.getBoolean(USE_THREAD_CONTEXT_CLASSLOADER_PROPERTY
);
107 private MemcacheSerialization() {
111 * Deserialize the object, according to its flags. This would have private
112 * visibility, but is also used by LocalMemcacheService for the increment
117 * @return the Object originally stored
118 * @throws ClassNotFoundException if the object can't be re-instantiated due
119 * to being an unlocatable type
120 * @throws IOException if the object can't be re-instantiated for some other
123 public static Object
deserialize(byte[] value
, int flags
)
124 throws ClassNotFoundException
, IOException
{
125 Flag flagval
= Flag
.fromInt(flags
);
132 if (value
.length
!= 1) {
133 throw new InvalidValueException("Cannot deserialize Boolean: bad length", null);
139 return Boolean
.FALSE
;
141 throw new InvalidValueException("Cannot deserialize Boolean: bad contents", null);
147 long val
= new BigInteger(new String(value
, US_ASCII
)).longValue();
158 throw new InvalidValueException("Cannot deserialize number: bad contents", null);
162 return new String(value
, UTF_8
);
165 if (value
.length
== 0) {
168 ByteArrayInputStream baos
= new ByteArrayInputStream(value
);
170 ObjectInputStream objIn
= null;
171 if (UseThreadContextClassLoaderHolder
.INSTANCE
) {
172 objIn
= new ObjectInputStream(baos
) {
174 protected Class
<?
> resolveClass(ObjectStreamClass objectStreamClass
)
175 throws ClassNotFoundException
, IOException
{
176 ClassLoader threadClassLoader
= Thread
.currentThread().getContextClassLoader();
177 if (threadClassLoader
== null) {
178 return super.resolveClass(objectStreamClass
);
182 return Class
.forName(objectStreamClass
.getName(), false, threadClassLoader
);
183 } catch (ClassNotFoundException ex
) {
184 return super.resolveClass(objectStreamClass
);
189 objIn
= new ObjectInputStream(baos
);
192 Object response
= objIn
.readObject();
202 private static boolean noEmbeddedNulls(byte[] key
) {
203 return !Bytes
.contains(key
, (byte) 0);
207 * Converts the user's key Object into a byte[] for the MemcacheGetRequest.
208 * Because the underlying service has a length limit, we actually use the
209 * SHA1 hash of the serialized object as its key if it's not a basic type.
210 * For the basic types (that is, {@code String}, {@code Boolean}, and the
211 * fixed-point numbers), we use a human-readable representation.
214 * @return hash result. For the key {@code null}, the hash is also
217 public static byte[] makePbKey(Object key
) throws IOException
{
222 if (key
instanceof String
&& ((String
) key
).length() <= MAX_KEY_BYTE_COUNT
- 2) {
223 byte[] bytes
= ("\"" + key
+ "\"").getBytes(UTF_8
);
225 if (bytes
.length
<= MAX_KEY_BYTE_COUNT
) {
229 } else if (key
instanceof byte[]) {
230 byte[] bytes
= (byte[]) key
;
232 if (bytes
.length
<= MAX_KEY_BYTE_COUNT
&& noEmbeddedNulls(bytes
)) {
236 } else if (key
instanceof Long
|| key
instanceof Integer
237 || key
instanceof Short
|| key
instanceof Byte
) {
238 return (key
.getClass().getName() + ":" + key
).getBytes(UTF_8
);
240 } else if (key
instanceof Boolean
) {
241 return (((Boolean
) key
) ?
"true" : "false").getBytes(UTF_8
);
248 private static final byte[] hash(Object key
) throws IOException
{
249 ValueAndFlags vaf
= serialize(key
);
251 synchronized (SHA1
) {
252 SHA1
.update(vaf
.value
);
253 sha1hash
= SHA1
.digest();
255 return base64().encode(sha1hash
).getBytes(UTF_8
);
260 * @return the ValueAndFlags containing a serialized representation of the
261 * Object and the flags to hint deserialization.
262 * @throws IOException for serialization errors, normally due to a
263 * non-serializable object type
265 public static ValueAndFlags
serialize(Object value
)
274 } else if (value
instanceof byte[]) {
276 bytes
= (byte[]) value
;
278 } else if (value
instanceof Boolean
) {
279 flags
= Flag
.BOOLEAN
;
281 bytes
[0] = ((Boolean
) value
) ? TRUE_VALUE
: FALSE_VALUE
;
283 } else if (value
instanceof Integer
|| value
instanceof Long
284 || value
instanceof Byte
|| value
instanceof Short
) {
285 bytes
= value
.toString().getBytes(US_ASCII
);
286 if (value
instanceof Integer
) {
287 flags
= Flag
.INTEGER
;
288 } else if (value
instanceof Long
) {
290 } else if (value
instanceof Byte
) {
296 } else if (value
instanceof String
) {
298 bytes
= ((String
) value
).getBytes(UTF_8
);
300 } else if (value
instanceof Serializable
) {
302 ByteArrayOutputStream baos
= new ByteArrayOutputStream();
303 ObjectOutputStream objOut
= new ObjectOutputStream(baos
);
304 objOut
.writeObject(value
);
306 bytes
= baos
.toByteArray();
309 throw new IllegalArgumentException("can't accept " + value
.getClass()
310 + " as a memcache entity");
312 return new ValueAndFlags(bytes
, flags
);