Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / memcache / MemcacheSerialization.java
blob2552845d7db17f433b63e5c0d62b507631a69e7d
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;
21 /**
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 {
31 /**
32 * Values used as flags on the MemcacheService's values.
34 public enum Flag {
35 BYTES,
36 UTF8,
37 OBJECT,
38 INTEGER,
39 LONG,
40 BOOLEAN,
41 BYTE,
42 SHORT;
44 private static final Flag[] VALUES = Flag.values();
46 /**
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();
54 return VALUES[i];
58 /**
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) {
67 this.value = value;
68 this.flags = 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();
76 /**
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;
83 static {
84 try {
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() {
96 /**
97 * Deserialize the object, according to its flags. This would have private
98 * visibility, but is also used by LocalMemcacheService for the increment
99 * operation.
101 * @param value
102 * @param flags
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
107 * reason
109 public static Object deserialize(byte[] value, int flags)
110 throws ClassNotFoundException, IOException {
111 Flag flagval = Flag.fromInt(flags);
113 switch (flagval) {
114 case BYTES:
115 return value;
117 case BOOLEAN:
118 if (value.length != 1) {
119 throw new InvalidValueException("Cannot deserialize Boolean: bad length", null);
121 switch (value[0]) {
122 case TRUE_VALUE:
123 return Boolean.TRUE;
124 case FALSE_VALUE:
125 return Boolean.FALSE;
127 throw new InvalidValueException("Cannot deserialize Boolean: bad contents", null);
129 case BYTE:
130 case SHORT:
131 case INTEGER:
132 case LONG:
133 long val = new BigInteger(new String(value, US_ASCII)).longValue();
134 switch (flagval) {
135 case BYTE:
136 return (byte) val;
137 case SHORT:
138 return (short) val;
139 case INTEGER:
140 return (int) val;
141 case LONG:
142 return val;
143 default:
144 throw new InvalidValueException("Cannot deserialize number: bad contents", null);
147 case UTF8:
148 return new String(value, UTF_8);
150 case OBJECT:
151 if (value.length == 0) {
152 return null;
154 ByteArrayInputStream baos = new ByteArrayInputStream(value);
155 ObjectInputStream objIn = new ObjectInputStream(baos);
156 Object response = objIn.readObject();
157 objIn.close();
158 return response;
160 default:
161 assert false;
163 return null;
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.
173 * @param key
174 * @return hash result. For the key {@code null}, the hash is also
175 * {@code null}.
177 public static byte[] makePbKey(Object key) throws IOException {
178 if (key == null) {
179 return new byte[0];
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);
191 } else {
192 ValueAndFlags vaf = serialize(key);
193 byte sha1hash[];
194 synchronized (SHA1) {
195 SHA1.update(vaf.value);
196 sha1hash = SHA1.digest();
198 return base64().encode(sha1hash).getBytes(UTF_8);
203 * @param value
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)
210 throws IOException {
211 Flag flags;
212 byte[] bytes;
214 if (value == null) {
215 bytes = new byte[0];
216 flags = Flag.OBJECT;
218 } else if (value instanceof byte[]) {
219 flags = Flag.BYTES;
220 bytes = (byte[]) value;
222 } else if (value instanceof Boolean) {
223 flags = Flag.BOOLEAN;
224 bytes = new byte[1];
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) {
233 flags = Flag.LONG;
234 } else if (value instanceof Byte) {
235 flags = Flag.BYTE;
236 } else {
237 flags = Flag.SHORT;
240 } else if (value instanceof String) {
241 flags = Flag.UTF8;
242 bytes = ((String) value).getBytes(UTF_8);
244 } else if (value instanceof Serializable) {
245 flags = Flag.OBJECT;
246 ByteArrayOutputStream baos = new ByteArrayOutputStream();
247 ObjectOutputStream objOut = new ObjectOutputStream(baos);
248 objOut.writeObject(value);
249 objOut.close();
250 bytes = baos.toByteArray();
252 } else {
253 throw new IllegalArgumentException("can't accept " + value.getClass()
254 + " as a memcache entity");
256 return new ValueAndFlags(bytes, flags);