/**
* Copyright Microsoft Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.azure.storage.table;
import java.lang.reflect.InvocationTargetException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.StorageErrorCodeStrings;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.core.SR;
/**
* The {@link TableServiceEntity} class represents the base object type for a table entity in the Storage service.
* {@link TableServiceEntity} provides a base implementation for the {@link TableEntity} interface that provides
* <code>readEntity</code> and <code>writeEntity</code> methods that by default serialize and deserialize all properties
* via reflection. A table entity class may extend this class and override the <code>readEntity</code> and
* <code>writeEntity</code> methods to provide customized or more performant serialization logic.
* <p>
* The use of reflection allows subclasses of {@link TableServiceEntity} to be serialized and deserialized without
* having to implement the serialization code themselves. When both a getter method and setter method are found for a
* given property name and data type, then the appropriate method is invoked automatically to serialize or deserialize
* the data. To take advantage of the automatic serialization code, your table entity classes should provide getter and
* setter methods for each property in the corresponding table entity in Microsoft Azure table storage. The reflection
* code looks for getter and setter methods in pairs of the form
* <p>
* <code>public <em>type</em> get<em>PropertyName</em>() { ... }</code>
* <p>
* and
* <p>
* <code>public void set<em>PropertyName</em>(<em>type</em> parameter) { ... }</code>
* <p>
* where <em>PropertyName</em> is a property name for the table entity, and <em>type</em> is a Java type compatible with
* the EDM data type of the property. See the table below for a map of property types to their Java equivalents. The
* {@link StoreAs} annotation may be applied with a <code>name</code> attribute to specify a property name for
* reflection on getter and setter methods that do not follow the property name convention. Method names and the
* <code>name</code> attribute of {@link StoreAs} annotations are case sensitive for matching property names with
* reflection. Use the {@link Ignore} annotation to prevent methods from being used by reflection for automatic
* serialization and deserialization. Note that the names "PartitionKey", "RowKey", "Timestamp", and "Etag" are reserved
* and will be ignored if set with the {@link StoreAs} annotation in a subclass.
* <p>
* The following table shows the supported property data types in Microsoft Azure storage and the corresponding Java
* types when deserialized.
* <TABLE BORDER="1" WIDTH="100%" CELLPADDING="3" CELLSPACING="0">
* <TR BGCOLOR="#EEEEFF" CLASS="TableSubHeadingColor">
* <th>Storage Type</th>
* <th>EdmType Value</th>
* <th>Java Type</th>
* <th>Description</th>
* </tr>
* <tr>
* <td><strong>Edm.Binary</strong></td>
* <td>{@link EdmType#BINARY}</td>
* <td><code>byte[], Byte[]</code></td>
* <td>An array of bytes up to 64 KB in size.</td>
* </tr>
* <tr>
* <td><strong>Edm.Boolean</strong></td>
* <td>{@link EdmType#BOOLEAN}</td>
* <td><code>boolean, Boolean</code></td>
* <td>A Boolean value.</td>
* </tr>
* <tr>
* <td><strong>Edm.DateTime</strong></td>
* <td>{@link EdmType#DATE_TIME}</td>
* <td><code>java.util.Date</code></td>
* <td>A 64-bit value expressed as Coordinated Universal Time (UTC). The supported range begins from 12:00 midnight,
* January 1, 1601 A.D. (C.E.), UTC. The range ends at December 31, 9999.</td>
* </tr>
* <tr>
* <td><strong>Edm.Double</strong></td>
* <td>{@link EdmType#DOUBLE}</td>
* <td><code>double, Double</code></td>
* <td>A 64-bit double-precision floating point value.</td>
* </tr>
* <tr>
* <td><strong>Edm.Guid</strong></td>
* <td>{@link EdmType#GUID}</td>
* <td><code>UUID</code></td>
* <td>A 128-bit globally unique identifier.</td>
* </tr>
* <tr>
* <td><strong>Edm.Int32</strong></td>
* <td>{@link EdmType#INT32}</td>
* <td><code>int, Integer</code></td>
* <td>A 32-bit integer value.</td>
* </tr>
* <tr>
* <td><strong>Edm.Int64</strong></td>
* <td>{@link EdmType#INT64}</td>
* <td><code>long, Long</code></td>
* <td>A 64-bit integer value.</td>
* </tr>
* <tr>
* <td><strong>Edm.String</strong></td>
* <td>{@link EdmType#STRING}</td>
* <td><code>String</code></td>
* <td>A UTF-16-encoded value. String values may be up to 64 KB in size.</td>
* </tr>
* </table>
* <p>
* See the MSDN topic <a href="http://msdn.microsoft.com//library/azure/dd179338.aspx">Understanding the Table Service
* Data Model</a> for an overview of tables, entities, and properties as used in the Microsoft Azure Storage service.
* <p>
* For an overview of the available EDM primitive data types and names, see the
*
* <a href="http://www.odata.org/developers/protocols/overview#AbstractTypeSystem">Primitive Data Types</a> section of
* the <a href="http://www.odata.org/developers/protocols/overview">OData Protocol Overview</a>.
* <p>
*
* @see EdmType
*/
public class TableServiceEntity implements TableEntity {
/**
* Deserializes the table entity property map into the specified object instance using reflection.
* <p>
* This static method takes an object instance that represents a table entity type and uses reflection on its class
* type to find methods to deserialize the data from the property map into the instance.
* <p>
* Each property name and data type in the properties map is compared with the methods in the class type for a pair
* of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with
* names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set
* with the property name. The methods must have return types or parameter data types that match the data type of
* the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied into the instance
* object by invoking the setter method on the instance. Properties that do not match a method pair by name and data
* type are not copied.
*
* @param instance
* An <code>Object</code> reference to an instance of a class implementing {@link TableEntity} to
* deserialize the table entity
* data into.
* @param properties
* A <code>java.util.HashMap</code> object which maps <code>String</code> property names to
* {@link EntityProperty} objects containing typed data
* values to deserialize into the instance parameter object.
* @param opContext
* An {@link OperationContext} object that represents the context for the current operation.
*
* @throws IllegalArgumentException
* if the table entity response received is invalid or improperly formatted.
* @throws IllegalAccessException
* if the table entity threw an exception during deserialization.
* @throws InvocationTargetException
* if a method invoked on the instance parameter threw an exception during deserialization.
*/
public static void readEntityWithReflection(final Object instance,
final HashMap<String, EntityProperty> properties, final OperationContext opContext)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass());
for (final Entry<String, EntityProperty> p : properties.entrySet()) {
if (props.containsKey(p.getKey())) {
props.get(p.getKey()).consumeEntityProperty(p.getValue(), instance);
}
}
}
/**
* Serializes the property data from a table entity instance into a property map using reflection.
* <p>
* This static method takes an object instance that represents a table entity type and uses reflection on its class
* type to find methods to serialize the data from the instance into the property map.
* <p>
* Each property name and data type in the properties map is compared with the methods in the class type for a pair
* of getter and setter methods to use for serialization and deserialization. The class is scanned for methods with
* names that match the property name with "get" and "set" prepended, or with the {@link StoreAs} annotation set
* with the property name. The methods must have return types or parameter data types that match the data type of
* the corresponding {@link EntityProperty} value. If such a pair is found, the data is copied from the instance
* object by invoking the getter method on the instance. Properties that do not have a method pair with matching
* name and data type are not copied.
*
* @param instance
* An <code>Object</code> reference to an instance of a class implementing {@link TableEntity} to
* serialize the table entity
* data from.
* @return
* A <code>java.util.HashMap</code> object which maps <code>String</code> property names to
* {@link EntityProperty} objects containing typed data values serialized from the instance parameter
* object.
*
* @throws IllegalArgumentException
* if the table entity is invalid or improperly formatted.
* @throws IllegalAccessException
* if the table entity threw an exception during serialization.
* @throws InvocationTargetException
* if a method invoked on the instance parameter threw an exception during serialization.
*/
public static HashMap<String, EntityProperty> writeEntityWithReflection(final Object instance)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
final HashMap<String, PropertyPair> props = PropertyPair.generatePropertyPairs(instance.getClass());
final HashMap<String, EntityProperty> retVal = new HashMap<String, EntityProperty>();
for (final Entry<String, PropertyPair> p : props.entrySet()) {
retVal.put(p.getValue().effectiveName, p.getValue().generateEntityProperty(instance));
}
return retVal;
}
/**
* The default to multiply the number of CPU's by to get the number of threads to allow in the
* <code>ReflectedEntityCache</code>
*/
private static final int DEFAULT_CONCURRENCY_MULTIPLIER = 4;
/**
* The default load factor for the <code>ReflectedEntityCache</code>
*/
private static final float DEFAULT_LOAD_FACTOR = (float) .75;
/**
* The default initial capacity for the <code>ReflectedEntityCache</code>
*/
private static final int DEFAULT_INITIAL_CAPACITY = 31;
/**
* The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
* than using reflection on a known entity type, the values from the dictionary are used instead.
*/
private static boolean disableReflectedEntityCache = false;
/**
* Reserved for internal use. The value of the partition key in the entity.
*/
protected String partitionKey = null;
/**
* Reserved for internal use. The value of the row key in the entity.
*/
protected String rowKey = null;
/**
* Reserved for internal use. The value of the ETag for the entity.
*/
protected String etag = null;
/**
* Reserved for internal use. The value of the Timestamp in the entity.
*/
protected Date timeStamp = new Date();
/**
* Initializes an empty {@link TableServiceEntity} instance.
*/
public TableServiceEntity() {
// Empty ctor
}
/**
* Initializes a new instance of the {@link TableServiceEntity} class with the specified partition key and row key.
*
* @param partitionKey
* A <code>String</code> which represents the partition key of the {@link TableServiceEntity} to be
* initialized.
* @param rowKey
* A <code>String</code> which represents the row key of the {@link TableServiceEntity} to be
* initialized.
*/
public TableServiceEntity(String partitionKey, String rowKey) {
this.partitionKey = partitionKey;
this.rowKey = rowKey;
}
/**
* Gets the ETag value to verify for the entity. This value is used to determine if the table entity has changed
* since it was last read from Microsoft Azure storage. The client cannot update this value on the service.
*
* @return
* A <code>String</code> containing the ETag for the entity.
*/
@Override
public String getEtag() {
return this.etag;
}
/**
* Gets the PartitionKey value for the entity.
*
* @return
* A <code>String</code> containing the PartitionKey value for the entity.
*/
@Override
public String getPartitionKey() {
return this.partitionKey;
}
/**
* Gets the RowKey value for the entity.
*
* @return
* A <code>String</code> containing the RowKey value for the entity.
*/
@Override
public String getRowKey() {
return this.rowKey;
}
/**
* Gets the Timestamp for the entity. The server manages the value of Timestamp, which cannot be modified.
*
* @return
* A <code>java.util.Date</code> object which represents the Timestamp value for the entity.
*/
@Override
public Date getTimestamp() {
return this.timeStamp;
}
/**
* Gets a value indicating whether or not the reflected entity cache is disabled. For most scenarios, disabling
* the reflected entity cache is not recommended due to its effect on performance.
*
* The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
* than using reflection on a known entity type, the values from the dictionary are used instead.
*
* @return
* <code>true</code> if the reflected entity cache is disabled; otherwise, <code>false</code>.
*/
public static boolean isReflectedEntityCacheDisabled() {
return TableServiceEntity.disableReflectedEntityCache;
}
/**
* Sets a boolean representing whether or not the reflected entity cache is disabled. For most scenarios, disabling
* the reflected entity cache is not recommended due to its effect on performance.
*
* The reflected entity cache stores known entity types and their respective reflected entity dictionaries. Rather
* than using reflection on a known entity type, the values from the dictionary are used instead.
*
* @param disableReflectedEntityCache
* <code>true</code> to disable the reflected entity cache; otherwise, <code>false</code>.
*/
public static void setReflectedEntityCacheDisabled(boolean disableReflectedEntityCache) {
if (TableServiceEntity.reflectedEntityCache != null && disableReflectedEntityCache) {
TableServiceEntity.reflectedEntityCache.clear();
}
TableServiceEntity.disableReflectedEntityCache = disableReflectedEntityCache;
}
/**
* Populates this table entity instance using the map of property names to {@link EntityProperty} data typed values.
* <p>
* This method invokes {@link TableServiceEntity#readEntityWithReflection} to populate the table entity instance the
* method is called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take
* advantage of this behavior by implementing getter and setter methods for the particular properties of the table
* entity in Microsoft Azure storage the class represents.
* <p>
* Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code.
*
* @param properties
* The <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty}
* data values to deserialize and store in this table entity instance.
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @throws StorageException
* if an error occurs during the deserialization.
*/
@Override
public void readEntity(final HashMap<String, EntityProperty> properties, final OperationContext opContext)
throws StorageException {
try {
readEntityWithReflection(this, properties, opContext);
}
catch (IllegalArgumentException e) {
throw new StorageException(StorageErrorCodeStrings.INVALID_DOCUMENT, SR.RESPONSE_RECEIVED_IS_INVALID,
Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
}
catch (IllegalAccessException e) {
throw new StorageException(StorageErrorCodeStrings.INVALID_DOCUMENT,
SR.EXCEPTION_THROWN_DURING_DESERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
}
catch (InvocationTargetException e) {
throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
SR.EXCEPTION_THROWN_DURING_DESERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
}
}
/**
* Sets the ETag value to verify for the entity. This value is used to determine if the table entity has changed
* since it was last read from Microsoft Azure storage. The client cannot update this value on the service.
*
* @param etag
* A <code>String</code> containing the ETag for the entity.
*/
@Override
public void setEtag(final String etag) {
this.etag = etag;
}
/**
* Sets the PartitionKey value for the entity.
*
* @param partitionKey
* A <code>String</code> containing the PartitionKey value for the entity.
*/
@Override
public void setPartitionKey(final String partitionKey) {
this.partitionKey = partitionKey;
}
/**
* Sets the RowKey value for the entity.
*
* @param rowKey
* A <code>String</code> containing the RowKey value for the entity.
*/
@Override
public void setRowKey(final String rowKey) {
this.rowKey = rowKey;
}
/**
* Sets the <code>timeStamp</code> value for the entity. Note that the timestamp property is a read-only property,
* set by the service only.
*
* @param timeStamp
* A <code>java.util.Date</code> containing the <code>timeStamp</code> value for the entity.
*/
@Override
public void setTimestamp(final Date timeStamp) {
this.timeStamp = timeStamp;
}
/**
* Returns a map of property names to {@link EntityProperty} data typed values created by serializing this table
* entity instance.
* <p>
* This method invokes {@link #writeEntityWithReflection} to serialize the table entity instance the method is
* called on using reflection. Table entity classes that extend {@link TableServiceEntity} can take advantage of
* this behavior by implementing getter and setter methods for the particular properties of the table entity in
* Microsoft Azure storage the class represents. Note that the property names "PartitionKey", "RowKey", and
* "Timestamp" are reserved and will be ignored if set on other methods with the {@link StoreAs} annotation.
* <p>
* Override this method in classes that extend {@link TableServiceEntity} to invoke custom serialization code.
*
* @param opContext
* An {@link OperationContext} object used to track the execution of the operation.
* @return
* A <code>java.util.HashMap</code> of <code>String</code> property names to {@link EntityProperty} data
* typed values representing the properties serialized from this table entity instance.
* @throws StorageException
* if an error occurs during the serialization.
*/
@Override
public HashMap<String, EntityProperty> writeEntity(final OperationContext opContext) throws StorageException {
try {
return writeEntityWithReflection(this);
}
catch (final IllegalAccessException e) {
throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
SR.ATTEMPTED_TO_SERIALIZE_INACCESSIBLE_PROPERTY, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
}
catch (final InvocationTargetException e) {
throw new StorageException(StorageErrorCodeStrings.INTERNAL_ERROR,
SR.EXCEPTION_THROWN_DURING_SERIALIZATION, Constants.HeaderConstants.HTTP_UNUSED_306, null, e);
}
}
/**
* The reflected entity cache caches known entity types and their respective reflected entity dictionaries when
* entities are deserialized and the payload does not include JSON metadata.
*/
private static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> reflectedEntityCache = initialize();
private static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> initialize() {
Runtime runtime = Runtime.getRuntime();
int numberOfProcessors = runtime.availableProcessors();
return new ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>>(DEFAULT_INITIAL_CAPACITY,
DEFAULT_LOAD_FACTOR, numberOfProcessors * DEFAULT_CONCURRENCY_MULTIPLIER);
}
/**
* The reflected entity cache caches known entity types and their respective reflected entity dictionaries when
* entities are deserialized and the payload does not include JSON metadata.
*
* @return
* The <code>ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>></code> representing the known entity
* types and their reflected entity dictionaries
*/
protected static ConcurrentHashMap<Class<?>, HashMap<String, PropertyPair>> getReflectedEntityCache() {
return TableServiceEntity.reflectedEntityCache;
}
}