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_INDEXABLE
;
7 import static com
.google
.appengine
.api
.datastore
.DataTypeUtils
.CheckValueOption
.REQUIRE_MULTI_VALUE
;
8 import static com
.google
.appengine
.api
.datastore
.DataTypeUtils
.CheckValueOption
.VALUE_PRE_CHECKED_WITHOUT_NAME
;
9 import static java
.nio
.charset
.StandardCharsets
.UTF_8
;
11 import com
.google
.appengine
.api
.blobstore
.BlobKey
;
12 import com
.google
.appengine
.api
.users
.User
;
13 import com
.google
.common
.collect
.ImmutableSet
;
15 import java
.util
.Collection
;
16 import java
.util
.Collections
;
17 import java
.util
.Date
;
18 import java
.util
.EnumSet
;
19 import java
.util
.HashSet
;
21 import java
.util
.logging
.Logger
;
24 * {@code DataTypeUtils} presents a simpler interface that allows
25 * user-code to determine what Classes can safely be stored as
26 * properties in the data store.
28 * Currently this list includes:
30 * <li>{@link String} (but not {@link StringBuffer}),
32 * <li>All numeric primitive wrappers ({@link Byte} through {@link
33 * Long}, {@link Float} and {@link Double}, but not {@link
34 * java.math.BigInteger} or {@link java.math.BigDecimal}.
36 * <li>{@link Key}, for storing references to other {@link Entity}
39 * <li>{@link User}, for storing references to users.
41 * <li>{@link ShortBlob}, for storing binary data small enough to be indexed.
42 * This means properties of this type, unlike {@link Blob} properties, can be
43 * filtered and sorted on in queries.
45 * <li>{@link Blob}, for storing unindexed binary data less than 1MB.
47 * <li>{@link Text}, for storing unindexed String data less than 1MB.
49 * <li>{@link BlobKey}, for storing references to user uploaded
50 * blobs (which may exceed 1MB).
58 public final class DataTypeUtils
{
61 * Options for checking if a property value is valid for a property.
63 enum CheckValueOption
{
66 * Allow the value to be a collection of values.
71 * Require the value to be a collection of values.
76 * The value's validity has already been checked but not in conjunction with the property
79 VALUE_PRE_CHECKED_WITHOUT_NAME
,
82 * Require the value to be indexable or a collection of indexable values.
87 private static final Logger logger
= Logger
.getLogger(DataTypeUtils
.class.getName());
90 * This is the maximum number of bytes that a string property
91 * can contain. If your string has more bytes, you need to
92 * wrap it in a {@link Text}.
94 public static final int MAX_STRING_PROPERTY_LENGTH
= 1500;
97 * This is the maximum number of bytes that a {@code ShortBlob} property
98 * can contain. If your data is larger, you need to use a {@code Blob}.
100 public static final int MAX_SHORT_BLOB_PROPERTY_LENGTH
= 1500;
102 public static final int MAX_LINK_PROPERTY_LENGTH
= 2083;
104 private static final Set
<Class
<?
>> SUPPORTED_TYPES
= new HashSet
<Class
<?
>>();
106 Collections
.addAll(SUPPORTED_TYPES
,
131 EmbeddedEntity
.class);
134 private static final ImmutableSet
<Class
<?
>> UNINDEXABLE_TYPES
=
135 ImmutableSet
.<Class
<?
>>of(Blob
.class, Text
.class);
138 * If the specified object cannot be used as the value for a {@code
139 * Entity} property, throw an exception with the appropriate
142 * @throws NullPointerException if the specified value is null
143 * @throws IllegalArgumentException if the type is not supported, or
144 * if the object is in some other way invalid.
146 public static void checkSupportedValue(Object value
) {
147 checkSupportedValue(null, value
);
151 * If the specified object cannot be used as the value for a {@code
152 * Entity} property, throw an exception with the appropriate
155 * @throws NullPointerException if the specified value is null
156 * @throws IllegalArgumentException if the type is not supported, or
157 * if the object is in some other way invalid.
159 public static void checkSupportedValue(String name
, Object value
) {
160 checkSupportedValue(name
, value
, true, false, false);
164 * If the specified object cannot be used as the value for a {@code
165 * Entity} property, throw an exception with the appropriate
168 * @param name name of the property
169 * @param value value in question
170 * @param allowMultiValue if this property allows multivalue values
171 * @param requireMultiValue if this property requires multivalue values
172 * @param requireIndexable if this property is required to be indexable
174 * @throws IllegalArgumentException if the type is not supported, or
175 * if the object is in some other way invalid.
177 static void checkSupportedValue(String name
, Object value
,
178 boolean allowMultiValue
, boolean requireMultiValue
, boolean requireIndexable
) {
179 EnumSet
<CheckValueOption
> options
= EnumSet
.noneOf(CheckValueOption
.class);
180 if (allowMultiValue
) {
181 options
.add(ALLOW_MULTI_VALUE
);
183 if (requireMultiValue
) {
184 options
.add(REQUIRE_MULTI_VALUE
);
186 if (requireIndexable
) {
187 options
.add(REQUIRE_INDEXABLE
);
189 checkSupportedValue(name
, value
, options
, SUPPORTED_TYPES
);
193 * If the specified object cannot be used as the value for a {@code
194 * Entity} property, throw an exception with the appropriate
197 * @param name name of the property
198 * @param value value in question
199 * @param options the options for this check invocation.
200 * @param supportedTypes the types considered to be valid types for the value.
202 * @throws IllegalArgumentException if the type is not supported, or
203 * if the object is in some other way invalid.
205 static void checkSupportedValue(String name
, Object value
, EnumSet
<CheckValueOption
> options
,
206 Set
<Class
<?
>> supportedTypes
) {
207 if (value
instanceof Collection
<?
>) {
208 if (!options
.contains(ALLOW_MULTI_VALUE
)) {
209 throw new IllegalArgumentException("A collection of values is not allowed.");
212 Collection
<?
> values
= (Collection
<?
>) value
;
213 if (!values
.isEmpty()) {
214 for (Object obj
: values
) {
215 checkSupportedSingleValue(name
, obj
, options
, supportedTypes
);
217 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
218 throw new IllegalArgumentException("A collection with at least one value is required.");
220 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
221 throw new IllegalArgumentException("A collection of values is required.");
223 checkSupportedSingleValue(name
, value
, options
, supportedTypes
);
227 private static void checkSupportedSingleValue(String name
, Object value
,
228 EnumSet
<CheckValueOption
> options
, Set
<Class
<?
>> supportedTypes
) {
233 if (Entity
.KEY_RESERVED_PROPERTY
.equals(name
)) {
234 if (!(value
instanceof Key
)) {
235 logger
.warning(Entity
.KEY_RESERVED_PROPERTY
+ " value should be of type Key");
239 if (options
.contains(VALUE_PRE_CHECKED_WITHOUT_NAME
)) {
247 prefix
= name
+ ": ";
250 if (!supportedTypes
.contains(value
.getClass())) {
251 throw new IllegalArgumentException(
252 prefix
+ value
.getClass().getName() + " is not a supported property type.");
255 if (options
.contains(REQUIRE_INDEXABLE
) && isUnindexableType(value
.getClass())) {
256 throw new IllegalArgumentException(
257 prefix
+ value
.getClass().getName() + " is not indexable.");
260 if (value
instanceof String
) {
261 int length
= ((String
) value
).getBytes(UTF_8
).length
;
262 if (length
> MAX_STRING_PROPERTY_LENGTH
) {
263 throw new IllegalArgumentException(
264 prefix
+ "String properties must be " + MAX_STRING_PROPERTY_LENGTH
265 + " bytes or less. Instead, use " + Text
.class.getName() + ", which can store "
266 + "strings of any length.");
268 } else if (value
instanceof Link
) {
269 int length
= ((Link
) value
).getValue().getBytes(UTF_8
).length
;
270 if (length
> MAX_LINK_PROPERTY_LENGTH
) {
271 throw new IllegalArgumentException(
272 prefix
+ "Link properties must be " + MAX_LINK_PROPERTY_LENGTH
273 + " bytes or less. Instead, use " + Text
.class.getName() + ", which can store "
274 + "strings of any length.");
276 } else if (value
instanceof ShortBlob
) {
277 int length
= ((ShortBlob
) value
).getBytes().length
;
278 if (length
> MAX_SHORT_BLOB_PROPERTY_LENGTH
) {
279 throw new IllegalArgumentException(prefix
+ "byte[] properties must be "
280 + MAX_SHORT_BLOB_PROPERTY_LENGTH
281 + " bytes or less. Instead, use " + Blob
.class.getName() + ", which can store binary "
282 + "data of any size.");
288 * Returns true if and only if the supplied {@code Class} can be
289 * stored in the data store.
291 public static boolean isSupportedType(Class
<?
> clazz
) {
292 return SUPPORTED_TYPES
.contains(clazz
);
296 * Returns an unmodifiable {@code Set} of supported {@code Class}
299 public static Set
<Class
<?
>> getSupportedTypes() {
300 return Collections
.unmodifiableSet(SUPPORTED_TYPES
);
304 * Returns true if the supplied {@code Class} cannot be indexed.
306 public static boolean isUnindexableType(Class
<?
> clazz
) {
307 return UNINDEXABLE_TYPES
.contains(clazz
);
310 private DataTypeUtils() {