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
;
8 import static java
.nio
.charset
.StandardCharsets
.UTF_8
;
10 import com
.google
.appengine
.api
.blobstore
.BlobKey
;
11 import com
.google
.appengine
.api
.users
.User
;
13 import java
.util
.Collection
;
14 import java
.util
.Collections
;
15 import java
.util
.Date
;
16 import java
.util
.EnumSet
;
17 import java
.util
.HashSet
;
19 import java
.util
.logging
.Logger
;
22 * {@code DataTypeUtils} presents a simpler interface that allows
23 * user-code to determine what Classes can safely be stored as
24 * properties in the data store.
26 * Currently this list includes:
28 * <li>{@link String} (but not {@link StringBuffer}),
30 * <li>All numeric primitive wrappers ({@link Byte} through {@link
31 * Long}, {@link Float} and {@link Double}, but not {@link
32 * java.math.BigInteger} or {@link java.math.BigDecimal}.
34 * <li>{@link Key}, for storing references to other {@link Entity}
37 * <li>{@link User}, for storing references to users.
39 * <li>{@link ShortBlob}, for storing binary data small enough to be indexed.
40 * This means properties of this type, unlike {@link Blob} properties, can be
41 * filtered and sorted on in queries.
43 * <li>{@link Blob}, for storing unindexed binary data less than 1MB.
45 * <li>{@link Text}, for storing unindexed String data less than 1MB.
47 * <li>{@link BlobKey}, for storing references to user uploaded
48 * blobs (which may exceed 1MB).
56 public final class DataTypeUtils
{
59 * Options for checking if a property value is valid for a property.
61 enum CheckValueOption
{
64 * Allow the value to be a collection of values.
69 * Require the value to be a collection of values.
74 * The value's validity has already been checked but not in conjunction with the property
77 VALUE_PRE_CHECKED_WITHOUT_NAME
80 private static final Logger logger
= Logger
.getLogger(DataTypeUtils
.class.getName());
83 * This is the maximum number of bytes that a string property
84 * can contain. If your string has more bytes, you need to
85 * wrap it in a {@link Text}.
87 public static final int MAX_STRING_PROPERTY_LENGTH
= 1500;
90 * This is the maximum number of bytes that a {@code ShortBlob} property
91 * can contain. If your data is larger, you need to use a {@code Blob}.
93 public static final int MAX_SHORT_BLOB_PROPERTY_LENGTH
= 1500;
95 public static final int MAX_LINK_PROPERTY_LENGTH
= 2083;
97 private static final Set
<Class
<?
>> SUPPORTED_TYPES
= new HashSet
<Class
<?
>>();
99 Collections
.addAll(SUPPORTED_TYPES
,
124 EmbeddedEntity
.class);
128 * If the specified object cannot be used as the value for a {@code
129 * Entity} property, throw an exception with the appropriate
132 * @throws NullPointerException if the specified value is null
133 * @throws IllegalArgumentException if the type is not supported, or
134 * if the object is in some other way invalid.
136 public static void checkSupportedValue(Object value
) {
137 checkSupportedValue(null, value
);
141 * If the specified object cannot be used as the value for a {@code
142 * Entity} property, throw an exception with the appropriate
145 * @throws NullPointerException if the specified value is null
146 * @throws IllegalArgumentException if the type is not supported, or
147 * if the object is in some other way invalid.
149 public static void checkSupportedValue(String name
, Object value
) {
150 checkSupportedValue(name
, value
, true, false);
154 * If the specified object cannot be used as the value for a {@code
155 * Entity} property, throw an exception with the appropriate
158 * @param name name of the property
159 * @param value value in question
160 * @param allowMultiValue if this property allows multivalue values
161 * @param requireMultiValue if this property requires multivalue values
163 * @throws IllegalArgumentException if the type is not supported, or
164 * if the object is in some other way invalid.
166 static void checkSupportedValue(String name
, Object value
,
167 boolean allowMultiValue
, boolean requireMultiValue
) {
168 EnumSet
<CheckValueOption
> options
= EnumSet
.noneOf(CheckValueOption
.class);
169 if (allowMultiValue
) {
170 options
.add(ALLOW_MULTI_VALUE
);
172 if (requireMultiValue
) {
173 options
.add(REQUIRE_MULTI_VALUE
);
175 checkSupportedValue(name
, value
, options
, SUPPORTED_TYPES
);
179 * If the specified object cannot be used as the value for a {@code
180 * Entity} property, throw an exception with the appropriate
183 * @param name name of the property
184 * @param value value in question
185 * @param options the options for this check invocation.
186 * @param supportedTypes the types considered to be valid types for the value.
188 * @throws IllegalArgumentException if the type is not supported, or
189 * if the object is in some other way invalid.
191 static void checkSupportedValue(String name
, Object value
, EnumSet
<CheckValueOption
> options
,
192 Set
<Class
<?
>> supportedTypes
) {
193 if (value
instanceof Collection
<?
>) {
194 if (!options
.contains(ALLOW_MULTI_VALUE
)) {
195 throw new IllegalArgumentException("A collection of values is not allowed.");
198 Collection
<?
> values
= (Collection
<?
>) value
;
199 if (!values
.isEmpty()) {
200 for (Object obj
: values
) {
201 checkSupportedSingleValue(name
, obj
, options
, supportedTypes
);
203 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
204 throw new IllegalArgumentException("A collection with at least one value is required.");
206 } else if (options
.contains(REQUIRE_MULTI_VALUE
)) {
207 throw new IllegalArgumentException("A collection of values is required.");
209 checkSupportedSingleValue(name
, value
, options
, supportedTypes
);
213 private static void checkSupportedSingleValue(String name
, Object value
,
214 EnumSet
<CheckValueOption
> options
, Set
<Class
<?
>> supportedTypes
) {
219 if (Entity
.KEY_RESERVED_PROPERTY
.equals(name
)) {
220 if (!(value
instanceof Key
)) {
221 logger
.warning(Entity
.KEY_RESERVED_PROPERTY
+ " value should be of type Key");
225 if (options
.contains(VALUE_PRE_CHECKED_WITHOUT_NAME
)) {
233 prefix
= name
+ ": ";
236 if (!supportedTypes
.contains(value
.getClass())) {
237 throw new IllegalArgumentException(
238 prefix
+ value
.getClass().getName() + " is not a supported property type.");
241 if (value
instanceof String
) {
242 int length
= ((String
) value
).getBytes(UTF_8
).length
;
243 if (length
> MAX_STRING_PROPERTY_LENGTH
) {
244 throw new IllegalArgumentException(
245 prefix
+ "String properties must be " + MAX_STRING_PROPERTY_LENGTH
246 + " bytes or less. Instead, use " + Text
.class.getName() + ", which can store "
247 + "strings of any length.");
249 } else if (value
instanceof Link
) {
250 int length
= ((Link
) value
).getValue().getBytes(UTF_8
).length
;
251 if (length
> MAX_LINK_PROPERTY_LENGTH
) {
252 throw new IllegalArgumentException(
253 prefix
+ "Link properties must be " + MAX_LINK_PROPERTY_LENGTH
254 + " bytes or less. Instead, use " + Text
.class.getName() + ", which can store "
255 + "strings of any length.");
257 } else if (value
instanceof ShortBlob
) {
258 int length
= ((ShortBlob
) value
).getBytes().length
;
259 if (length
> MAX_SHORT_BLOB_PROPERTY_LENGTH
) {
260 throw new IllegalArgumentException(prefix
+ "byte[] properties must be "
261 + MAX_SHORT_BLOB_PROPERTY_LENGTH
262 + " bytes or less. Instead, use " + Blob
.class.getName() + ", which can store binary "
263 + "data of any size.");
269 * Returns true if and only if the supplied {@code Class} can be
270 * stored in the data store.
272 public static boolean isSupportedType(Class
<?
> clazz
) {
273 return SUPPORTED_TYPES
.contains(clazz
);
277 * Returns an unmodifiable {@code Set} of supported {@code Class}
280 public static Set
<Class
<?
>> getSupportedTypes() {
281 return Collections
.unmodifiableSet(SUPPORTED_TYPES
);
284 private DataTypeUtils() {