1 // Copyright 2009 Google Inc. All rights reserved.
3 package com
.google
.appengine
.api
.datastore
;
5 import static com
.google
.appengine
.api
.datastore
.DataTypeUtils
.CheckValueOption
.ALLOW_MULTI_VALUE
;
6 import static com
.google
.appengine
.api
.datastore
.DataTypeUtils
.CheckValueOption
.REQUIRE_MULTI_VALUE
;
7 import static com
.google
.appengine
.api
.datastore
.DataTypeUtils
.CheckValueOption
.VALUE_PRE_CHECKED_WITHOUT_NAME
;
9 import com
.google
.appengine
.api
.blobstore
.BlobKey
;
10 import com
.google
.appengine
.api
.users
.User
;
12 import java
.util
.Collection
;
13 import java
.util
.Collections
;
14 import java
.util
.Date
;
15 import java
.util
.EnumSet
;
16 import java
.util
.HashSet
;
18 import java
.util
.logging
.Logger
;
21 * {@code DataTypeUtils} presents a simpler interface that allows
22 * user-code to determine what Classes can safely be stored as
23 * properties in the data store.
25 * Currently this list includes:
27 * <li>{@link String} (but not {@link StringBuffer}),
29 * <li>All numeric primitive wrappers ({@link Byte} through {@link
30 * Long}, {@link Float} and {@link Double}, but not {@link
31 * java.math.BigInteger} or {@link java.math.BigDecimal}.
33 * <li>{@link Key}, for storing references to other {@link Entity}
36 * <li>{@link User}, for storing references to users.
38 * <li>{@link ShortBlob}, for storing binary data small enough to be indexed.
39 * This means properties of this type, unlike {@link Blob} properties, can be
40 * filtered and sorted on in queries.
42 * <li>{@link Blob}, for storing unindexed binary data less than 1MB.
44 * <li>{@link Text}, for storing unindexed String data less than 1MB.
46 * <li>{@link BlobKey}, for storing references to user uploaded
47 * blobs (which may exceed 1MB).
55 public final class DataTypeUtils
{
58 * Options for checking if a property value is valid for a property.
60 enum CheckValueOption
{
63 * Allow the value to be a collection of values.
68 * Require the value to be a collection of values.
73 * The value's validity has already been checked but not in conjunction with the property
76 VALUE_PRE_CHECKED_WITHOUT_NAME
79 private static final Logger logger
= Logger
.getLogger(DataTypeUtils
.class.getName());
82 * This is the maximum number of characters that a string property
83 * can contain. If your string has more characters, you need to
84 * wrap it in a {@link Text}.
86 public static final int MAX_STRING_PROPERTY_LENGTH
= 500;
89 * This is the maximum number of bytes that a {@code ShortBlob} property
90 * can contain. If your data is larger, you need to use a {@code Blob}.
92 public static final int MAX_SHORT_BLOB_PROPERTY_LENGTH
= 500;
94 public static final int MAX_LINK_PROPERTY_LENGTH
= 2083;
96 private static final Set
<Class
<?
>> SUPPORTED_TYPES
= new HashSet
<Class
<?
>>();
98 Collections
.addAll(SUPPORTED_TYPES
,
123 EmbeddedEntity
.class);
127 * If the specified object cannot be used as the value for a {@code
128 * Entity} property, throw an exception with the appropriate
131 * @throws NullPointerException if the specified value is null
132 * @throws IllegalArgumentException if the type is not supported, or
133 * if the object is in some other way invalid.
135 public static void checkSupportedValue(Object value
) {
136 checkSupportedValue(null, value
);
140 * If the specified object cannot be used as the value for a {@code
141 * Entity} property, throw an exception with the appropriate
144 * @throws NullPointerException if the specified value is null
145 * @throws IllegalArgumentException if the type is not supported, or
146 * if the object is in some other way invalid.
148 public static void checkSupportedValue(String name
, Object value
) {
149 checkSupportedValue(name
, value
, true, false);
153 * If the specified object cannot be used as the value for a {@code
154 * Entity} property, throw an exception with the appropriate
157 * @param name name of the property
158 * @param value value in question
159 * @param allowMultiValue if this property allows multivalue values
160 * @param requireMultiValue if this property requires multivalue values
162 * @throws IllegalArgumentException if the type is not supported, or
163 * if the object is in some other way invalid.
165 static void checkSupportedValue(String name
, Object value
,
166 boolean allowMultiValue
, boolean requireMultiValue
) {
167 EnumSet
<CheckValueOption
> options
= EnumSet
.noneOf(CheckValueOption
.class);
168 if (allowMultiValue
) {
169 options
.add(ALLOW_MULTI_VALUE
);
171 if (requireMultiValue
) {
172 options
.add(REQUIRE_MULTI_VALUE
);
174 checkSupportedValue(name
, value
, options
, SUPPORTED_TYPES
);
178 * If the specified object cannot be used as the value for a {@code
179 * Entity} property, throw an exception with the appropriate
182 * @param name name of the property
183 * @param value value in question
184 * @param options the options for this check invocation.
185 * @param supportedTypes the types considered to be valid types for the value.
187 * @throws IllegalArgumentException if the type is not supported, or
188 * if the object is in some other way invalid.
190 static void checkSupportedValue(String name
, Object value
, EnumSet
<CheckValueOption
> options
,
191 Set
<Class
<?
>> supportedTypes
) {
192 if (value
instanceof Collection
<?
>) {
193 if (!options
.contains(ALLOW_MULTI_VALUE
)) {
194 throw new IllegalArgumentException("A collection of values is not allowed.");
197 Collection
<?
> values
= (Collection
<?
>) value
;
198 if (!values
.isEmpty()) {
199 for (Object obj
: values
) {
200 checkSupportedSingleValue(name
, obj
, options
, supportedTypes
);
202 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
203 throw new IllegalArgumentException("A collection with at least one value is required.");
205 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
206 throw new IllegalArgumentException("A collection of values is required.");
208 checkSupportedSingleValue(name
, value
, options
, supportedTypes
);
212 private static void checkSupportedSingleValue(String name
, Object value
,
213 EnumSet
<CheckValueOption
> options
, Set
<Class
<?
>> supportedTypes
) {
218 if (Entity
.KEY_RESERVED_PROPERTY
.equals(name
)) {
219 if (!(value
instanceof Key
)) {
220 logger
.warning(Entity
.KEY_RESERVED_PROPERTY
+ " value should be of type Key");
224 if (options
.contains(VALUE_PRE_CHECKED_WITHOUT_NAME
)) {
232 prefix
= name
+ ": ";
235 if (!supportedTypes
.contains(value
.getClass())) {
236 throw new IllegalArgumentException(
237 prefix
+ value
.getClass().getName() + " is not a supported property type.");
240 if (value
instanceof String
) {
241 int length
= ((String
) value
).length();
242 if (length
> MAX_STRING_PROPERTY_LENGTH
) {
243 throw new IllegalArgumentException(
244 prefix
+ "String properties must be " + MAX_STRING_PROPERTY_LENGTH
245 + " characters or less. Instead, use " + Text
.class.getName() + ", which can store "
246 + "strings of any length.");
248 } else if (value
instanceof Link
) {
249 int length
= ((Link
) value
).getValue().length();
250 if (length
> MAX_LINK_PROPERTY_LENGTH
) {
251 throw new IllegalArgumentException(
252 prefix
+ "Link properties must be " + MAX_LINK_PROPERTY_LENGTH
253 + " characters or less. Instead, use " + Text
.class.getName() + ", which can store "
254 + "strings of any length.");
256 } else if (value
instanceof ShortBlob
) {
257 int length
= ((ShortBlob
) value
).getBytes().length
;
258 if (length
> MAX_SHORT_BLOB_PROPERTY_LENGTH
) {
259 throw new IllegalArgumentException(prefix
+ "byte[] properties must be "
260 + MAX_SHORT_BLOB_PROPERTY_LENGTH
261 + " bytes or less. Instead, use " + Blob
.class.getName() + ", which can store binary "
262 + "data of any size.");
268 * Returns true if and only if the supplied {@code Class} can be
269 * stored in the data store.
271 public static boolean isSupportedType(Class
<?
> clazz
) {
272 return SUPPORTED_TYPES
.contains(clazz
);
276 * Returns an unmodifiable {@code Set} of supported {@code Class}
279 public static Set
<Class
<?
>> getSupportedTypes() {
280 return Collections
.unmodifiableSet(SUPPORTED_TYPES
);
283 private DataTypeUtils() {