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
= 2038;
96 private static final Set
<Class
<?
>> SUPPORTED_TYPES
= new HashSet
<Class
<?
>>();
98 SUPPORTED_TYPES
.add(RawValue
.class);
99 SUPPORTED_TYPES
.add(Boolean
.class);
100 SUPPORTED_TYPES
.add(String
.class);
101 SUPPORTED_TYPES
.add(Byte
.class);
102 SUPPORTED_TYPES
.add(Short
.class);
103 SUPPORTED_TYPES
.add(Integer
.class);
104 SUPPORTED_TYPES
.add(Long
.class);
105 SUPPORTED_TYPES
.add(Float
.class);
106 SUPPORTED_TYPES
.add(Double
.class);
107 SUPPORTED_TYPES
.add(User
.class);
108 SUPPORTED_TYPES
.add(Key
.class);
109 SUPPORTED_TYPES
.add(Blob
.class);
110 SUPPORTED_TYPES
.add(Text
.class);
111 SUPPORTED_TYPES
.add(Date
.class);
112 SUPPORTED_TYPES
.add(Link
.class);
113 SUPPORTED_TYPES
.add(ShortBlob
.class);
114 SUPPORTED_TYPES
.add(GeoPt
.class);
115 SUPPORTED_TYPES
.add(Category
.class);
116 SUPPORTED_TYPES
.add(Rating
.class);
117 SUPPORTED_TYPES
.add(PhoneNumber
.class);
118 SUPPORTED_TYPES
.add(PostalAddress
.class);
119 SUPPORTED_TYPES
.add(Email
.class);
120 SUPPORTED_TYPES
.add(IMHandle
.class);
121 SUPPORTED_TYPES
.add(BlobKey
.class);
122 SUPPORTED_TYPES
.add(EmbeddedEntity
.class);
126 * If the specified object cannot be used as the value for a {@code
127 * Entity} property, throw an exception with the appropriate
130 * @throws NullPointerException if the specified value is null
131 * @throws IllegalArgumentException if the type is not supported, or
132 * if the object is in some other way invalid.
134 public static void checkSupportedValue(Object value
) {
135 checkSupportedValue(null, value
);
139 * If the specified object cannot be used as the value for a {@code
140 * Entity} property, throw an exception with the appropriate
143 * @throws NullPointerException if the specified value is null
144 * @throws IllegalArgumentException if the type is not supported, or
145 * if the object is in some other way invalid.
147 public static void checkSupportedValue(String name
, Object value
) {
148 checkSupportedValue(name
, value
, true, false);
152 * If the specified object cannot be used as the value for a {@code
153 * Entity} property, throw an exception with the appropriate
156 * @param name name of the property
157 * @param value value in question
158 * @param allowMultiValue if this property allows multivalue values
159 * @param requireMultiValue if this property requires multivalue values
161 * @throws IllegalArgumentException if the type is not supported, or
162 * if the object is in some other way invalid.
164 static void checkSupportedValue(String name
, Object value
,
165 boolean allowMultiValue
, boolean requireMultiValue
) {
166 EnumSet
<CheckValueOption
> options
= EnumSet
.noneOf(CheckValueOption
.class);
167 if (allowMultiValue
) {
168 options
.add(ALLOW_MULTI_VALUE
);
170 if (requireMultiValue
) {
171 options
.add(REQUIRE_MULTI_VALUE
);
173 checkSupportedValue(name
, value
, options
, SUPPORTED_TYPES
);
177 * If the specified object cannot be used as the value for a {@code
178 * Entity} property, throw an exception with the appropriate
181 * @param name name of the property
182 * @param value value in question
183 * @param options the options for this check invocation.
184 * @param supportedTypes the types considered to be valid types for the value.
186 * @throws IllegalArgumentException if the type is not supported, or
187 * if the object is in some other way invalid.
189 static void checkSupportedValue(String name
, Object value
, EnumSet
<CheckValueOption
> options
,
190 Set
<Class
<?
>> supportedTypes
) {
191 if (value
instanceof Collection
<?
>) {
192 if (!options
.contains(ALLOW_MULTI_VALUE
)) {
193 throw new IllegalArgumentException("A collection of values is not allowed.");
196 Collection
<?
> values
= (Collection
<?
>) value
;
197 if (!values
.isEmpty()) {
198 for (Object obj
: values
) {
199 checkSupportedSingleValue(name
, obj
, options
, supportedTypes
);
201 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
202 throw new IllegalArgumentException("A collection with at least one value is required.");
204 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
205 throw new IllegalArgumentException("A collection of values is required.");
207 checkSupportedSingleValue(name
, value
, options
, supportedTypes
);
211 private static void checkSupportedSingleValue(String name
, Object value
,
212 EnumSet
<CheckValueOption
> options
, Set
<Class
<?
>> supportedTypes
) {
217 if (Entity
.KEY_RESERVED_PROPERTY
.equals(name
)) {
218 if (!(value
instanceof Key
)) {
219 logger
.warning(Entity
.KEY_RESERVED_PROPERTY
+ " value should be of type Key");
223 if (options
.contains(VALUE_PRE_CHECKED_WITHOUT_NAME
)) {
231 prefix
= name
+ ": ";
234 if (!supportedTypes
.contains(value
.getClass())) {
235 throw new IllegalArgumentException(
236 prefix
+ value
.getClass().getName() + " is not a supported property type.");
239 if (value
instanceof String
) {
240 int length
= ((String
) value
).length();
241 if (length
> MAX_STRING_PROPERTY_LENGTH
) {
242 throw new IllegalArgumentException(
243 prefix
+ "String properties must be " + MAX_STRING_PROPERTY_LENGTH
244 + " characters or less. Instead, use " + Text
.class.getName() + ", which can store "
245 + "strings of any length.");
247 } else if (value
instanceof Link
) {
248 int length
= ((Link
) value
).getValue().length();
249 if (length
> MAX_LINK_PROPERTY_LENGTH
) {
250 throw new IllegalArgumentException(
251 prefix
+ "Link properties must be " + MAX_LINK_PROPERTY_LENGTH
252 + " characters or less. Instead, use " + Text
.class.getName() + ", which can store "
253 + "strings of any length.");
255 } else if (value
instanceof ShortBlob
) {
256 int length
= ((ShortBlob
) value
).getBytes().length
;
257 if (length
> MAX_SHORT_BLOB_PROPERTY_LENGTH
) {
258 throw new IllegalArgumentException(prefix
+ "byte[] properties must be "
259 + MAX_SHORT_BLOB_PROPERTY_LENGTH
260 + " bytes or less. Instead, use " + Blob
.class.getName() + ", which can store binary "
261 + "data of any size.");
267 * Returns true if and only if the supplied {@code Class} can be
268 * stored in the data store.
270 public static boolean isSupportedType(Class
<?
> clazz
) {
271 return SUPPORTED_TYPES
.contains(clazz
);
275 * Returns an unmodifiable {@code Set} of supported {@code Class}
278 public static Set
<Class
<?
>> getSupportedTypes() {
279 return Collections
.unmodifiableSet(SUPPORTED_TYPES
);
282 private DataTypeUtils() {