1 // Copyright 2008 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.api
.memcache
;
5 import static com
.google
.common
.base
.Charsets
.US_ASCII
;
6 import static com
.google
.common
.base
.Charsets
.UTF_8
;
7 import static com
.google
.common
.io
.BaseEncoding
.base64
;
9 import java
.io
.ByteArrayInputStream
;
10 import java
.io
.ByteArrayOutputStream
;
11 import java
.io
.IOException
;
12 import java
.io
.ObjectInputStream
;
13 import java
.io
.ObjectOutputStream
;
14 import java
.io
.Serializable
;
15 import java
.math
.BigInteger
;
16 import java
.security
.MessageDigest
;
17 import java
.security
.NoSuchAlgorithmException
;
18 import java
.util
.logging
.Level
;
19 import java
.util
.logging
.Logger
;
22 * Static serialization helpers shared by {@link MemcacheServiceImpl} and
23 * {@link com.google.appengine.api.memcache.dev.LocalMemcacheService}.
25 * This class is thread-safe.
29 public class MemcacheSerialization
{
32 * Values used as flags on the MemcacheService's values.
44 private static final Flag
[] VALUES
= Flag
.values();
47 * While the enum is convenient, the implementation wants {@code int}s...
48 * this factory converts {@code int} value to Flag value.
50 public static Flag
fromInt(int i
) {
51 if (i
< 0 || i
>= VALUES
.length
) {
52 throw new IllegalArgumentException();
59 * Tuple of a serialized byte array value and associated flags to interpret
60 * that value. Used as the return from {@link MemcacheSerialization#serialize}.
62 public static class ValueAndFlags
{
63 public final byte[] value
;
64 public final Flag flags
;
66 private ValueAndFlags(byte[] value
, Flag flags
) {
72 private static final byte FALSE_VALUE
= '0';
73 private static final byte TRUE_VALUE
= '1';
74 private static final String MYCLASSNAME
= MemcacheSerialization
.class.getName();
77 * The SHA1 checksum engine, cached for reuse. We did test the hashing time
78 * was negligible (17us/kb, linear); we don't "need" crypto-secure, but it's
79 * a good way to minimize collisions.
81 private static final MessageDigest SHA1
;
85 SHA1
= MessageDigest
.getInstance("SHA-1");
86 } catch (NoSuchAlgorithmException ex
) {
87 Logger
.getLogger(MYCLASSNAME
).log(Level
.SEVERE
,
88 "Can't load SHA-1 MessageDigest!", ex
);
89 throw new MemcacheServiceException("No SHA-1 algorithm, cannot hash keys for memcache", ex
);
93 private MemcacheSerialization() {
97 * Deserialize the object, according to its flags. This would have private
98 * visibility, but is also used by LocalMemcacheService for the increment
103 * @return the Object originally stored
104 * @throws ClassNotFoundException if the object can't be re-instantiated due
105 * to being an unlocatable type
106 * @throws IOException if the object can't be re-instantiated for some other
109 public static Object
deserialize(byte[] value
, int flags
)
110 throws ClassNotFoundException
, IOException
{
111 Flag flagval
= Flag
.fromInt(flags
);
118 if (value
.length
!= 1) {
119 throw new InvalidValueException("Cannot deserialize Boolean: bad length", null);
125 return Boolean
.FALSE
;
127 throw new InvalidValueException("Cannot deserialize Boolean: bad contents", null);
133 long val
= new BigInteger(new String(value
, US_ASCII
)).longValue();
144 throw new InvalidValueException("Cannot deserialize number: bad contents", null);
148 return new String(value
, UTF_8
);
151 if (value
.length
== 0) {
154 ByteArrayInputStream baos
= new ByteArrayInputStream(value
);
155 ObjectInputStream objIn
= new ObjectInputStream(baos
);
156 Object response
= objIn
.readObject();
167 * Converts the user's key Object into a byte[] for the MemcacheGetRequest.
168 * Because the underlying service has a length limit, we actually use the
169 * SHA1 hash of the serialized object as its key if it's not a basic type.
170 * For the basic types (that is, {@code String}, {@code Boolean}, and the
171 * fixed-point numbers), we use a human-readable representation.
174 * @return hash result. For the key {@code null}, the hash is also
177 public static byte[] makePbKey(Object key
) throws IOException
{
181 if (key
instanceof String
&& ((String
) key
).length() < 250) {
182 return ("\"" + key
+ "\"").getBytes(UTF_8
);
184 } else if (key
instanceof Long
|| key
instanceof Integer
185 || key
instanceof Short
|| key
instanceof Byte
) {
186 return (key
.getClass().getName() + ":" + key
).getBytes(UTF_8
);
188 } else if (key
instanceof Boolean
) {
189 return (((Boolean
) key
) ?
"true" : "false").getBytes(UTF_8
);
192 ValueAndFlags vaf
= serialize(key
);
194 synchronized (SHA1
) {
195 SHA1
.update(vaf
.value
);
196 sha1hash
= SHA1
.digest();
198 return base64().encode(sha1hash
).getBytes(UTF_8
);
204 * @return the ValueAndFlags containing a serialized representation of the
205 * Object and the flags to hint deserialization.
206 * @throws IOException for serialization errors, normally due to a
207 * non-serializable object type
209 public static ValueAndFlags
serialize(Object value
)
218 } else if (value
instanceof byte[]) {
220 bytes
= (byte[]) value
;
222 } else if (value
instanceof Boolean
) {
223 flags
= Flag
.BOOLEAN
;
225 bytes
[0] = ((Boolean
) value
) ? TRUE_VALUE
: FALSE_VALUE
;
227 } else if (value
instanceof Integer
|| value
instanceof Long
228 || value
instanceof Byte
|| value
instanceof Short
) {
229 bytes
= value
.toString().getBytes(US_ASCII
);
230 if (value
instanceof Integer
) {
231 flags
= Flag
.INTEGER
;
232 } else if (value
instanceof Long
) {
234 } else if (value
instanceof Byte
) {
240 } else if (value
instanceof String
) {
242 bytes
= ((String
) value
).getBytes(UTF_8
);
244 } else if (value
instanceof Serializable
) {
246 ByteArrayOutputStream baos
= new ByteArrayOutputStream();
247 ObjectOutputStream objOut
= new ObjectOutputStream(baos
);
248 objOut
.writeObject(value
);
250 bytes
= baos
.toByteArray();
253 throw new IllegalArgumentException("can't accept " + value
.getClass()
254 + " as a memcache entity");
256 return new ValueAndFlags(bytes
, flags
);