All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.azure.storage.table.PropertyPair Maven / Gradle / Ivy

/**
 * 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.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.UUID;

import com.microsoft.azure.storage.core.SR;
import com.microsoft.azure.storage.core.Utility;

/**
 * Reserved for internal use. A class used internally during the reflection process to determine which properties should
 * be serialized.
 */
final class PropertyPair {

    private Method getter = null;
    private Method setter = null;
    private String name = null;
    Class type = null;
    String effectiveName = null;
    boolean isEncrypted = false;
    
    /**
     * Reserved for internal use. A static factory method to generate a map of property names to {@link PropertyPair}
     * instances for the specified class type. Checks if the cache is enabled and if so tries to get the property pairs
     * from it. Otherwise, uses reflection to find pairs of getter and setter methods that are annotated with
     * {@link StoreAs} with a common property name, or of the form getPropertyName and
     * setPropertyName, with a common type for the getter return value and the setter parameter,
     * and stores the methods and the property name for each pair found in a map for use in serializing and
     * deserializing entity data. If the cache is enabled and the mapping was not present, adds it to the cache.
     * 
     * @param clazzType
     *            The class type to check for matching getter and setter methods with a common return and parameter
     *            type, respectively.
     */
    protected static HashMap generatePropertyPairs(final Class clazzType) {
        if (!TableServiceEntity.isReflectedEntityCacheDisabled()) {
            HashMap props = new HashMap();
            props = TableServiceEntity.getReflectedEntityCache().get(clazzType);

            if (props == null) {
                props = PropertyPair.generatePropertyPairsHelper(clazzType);
                TableServiceEntity.getReflectedEntityCache().put(clazzType, props);
            }

            return props;
        }
        else {
            return PropertyPair.generatePropertyPairsHelper(clazzType);
        }
    }

    /**
     * Reserved for internal use. A static factory method to generate a map of property names to {@link PropertyPair}
     * instances for the specified class type. Uses reflection to find pairs of getter and setter methods that are
     * annotated with {@link StoreAs} with a common property name, or of the form getPropertyName
     * and setPropertyName, with a common type for the getter return value and the
     * setter parameter, and stores the methods and the property name for each pair found in a map for use in
     * serializing and deserializing entity data.
     * 
     * @param clazzType
     *            The class type to check for matching getter and setter methods with a common return and parameter
     *            type, respectively.
     */
    private static HashMap generatePropertyPairsHelper(final Class clazzType) {
        final Method[] methods = clazzType.getMethods();
        final HashMap propMap = new HashMap();

        String propName = null;
        PropertyPair currProperty = null;

        for (final Method m : methods) {
            if (m.getName().length() < 4 || (!m.getName().startsWith("get") && !m.getName().startsWith("set"))) {
                continue;
            }

            propName = m.getName().substring(3);

            // Skip interface methods, these will be called explicitly
            if (propName.equals(TableConstants.PARTITION_KEY) || propName.equals(TableConstants.ROW_KEY)
                    || propName.equals(TableConstants.TIMESTAMP) || propName.equals("Etag")
                    || propName.equals("LastModified")) {
                continue;
            }

            if (propMap.containsKey(propName)) {
                currProperty = propMap.get(propName);
            }
            else {
                currProperty = new PropertyPair();
                currProperty.name = propName;
                propMap.put(propName, currProperty);
            }

            if (m.getName().startsWith("get") && m.getParameterTypes().length == 0) {
                currProperty.type = m.getReturnType();
                currProperty.getter = m;
            }
            else if (m.getName().startsWith("set") && m.getParameterTypes().length == 1
                    && void.class.equals(m.getReturnType())) {
                currProperty.setter = m;
            }

            // Check for StoreAs Annotation
            final StoreAs storeAsInstance = m.getAnnotation(StoreAs.class);
            if (storeAsInstance != null) {
                if (Utility.isNullOrEmpty(storeAsInstance.name())) {
                    throw new IllegalArgumentException(String.format(SR.STOREAS_USED_ON_EMPTY_PROPERTY,
                            currProperty.name));
                }

                if (currProperty.effectiveName != null && !currProperty.effectiveName.equals(currProperty.name)
                        && !currProperty.effectiveName.equals(storeAsInstance.name())) {
                    throw new IllegalArgumentException(String.format(SR.STOREAS_DIFFERENT_FOR_GETTER_AND_SETTER,
                            currProperty.name));
                }

                if (!currProperty.name.equals(storeAsInstance.name())) {
                    currProperty.effectiveName = storeAsInstance.name();
                }
            }
        }

        // Return only processable pairs
        final ArrayList keysToRemove = new ArrayList();
        final ArrayList keysToAlter = new ArrayList();

        for (final Entry e : propMap.entrySet()) {
            // Encrypt if both getter and setter indicate encryption
            e.getValue().isEncrypted = e.getValue().setter != null 
                    && e.getValue().setter.isAnnotationPresent(Encrypt.class);
            
            if (!e.getValue().shouldProcess()) {
                keysToRemove.add(e.getKey());
                continue;
            }

            if (!Utility.isNullOrEmpty(e.getValue().effectiveName)) {
                keysToAlter.add(e.getKey());
            }
            else {
                e.getValue().effectiveName = e.getValue().name;
            }
        }

        // remove all entries for keys that should not process
        for (final String key : keysToRemove) {
            propMap.remove(key);
        }

        // Any store as properties should be re-stored into the hash under the efective name.
        for (final String key : keysToAlter) {
            final PropertyPair p = propMap.get(key);
            propMap.remove(key);
            propMap.put(p.effectiveName, p);
        }

        return propMap;
    }

    /**
     * Reserved for internal use. Invokes the setter method on the specified instance parameter with the value of the
     * {@link EntityProperty} deserialized as the appropriate type.
     * 
     * @param prop
     *            The {@link EntityProperty} containing the value to pass to the setter on the instance.
     * @param instance
     *            An instance of a class supporting this property with getter and setter methods of the
     *            appropriate name and parameter or return type.
     * 
     * @throws IllegalArgumentException
     *             if the specified instance parameter is not an instance of the class
     *             or interface declaring the setter method (or of a subclass or implementor thereof).
     * @throws IllegalAccessException
     *             if the setter method is inaccessible.
     * @throws InvocationTargetException
     *             if the setter method throws an exception.
     */
    protected void consumeEntityProperty(final EntityProperty prop, final Object instance)
            throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
        Class paramType = this.setter.getParameterTypes()[0];
        if (prop.getIsNull()) {
            if (!paramType.isPrimitive()) {
                this.setter.invoke(instance, (Object) null);
            }
        }
        else {
            if (prop.getEdmType() == EdmType.STRING) {
                if (paramType.equals(String.class)) {
                    this.setter.invoke(instance, prop.getValueAsString());
                }
            }
            else if (prop.getEdmType() == EdmType.BINARY) {
                if (paramType.equals(Byte[].class)) {
                    this.setter.invoke(instance, (Object) prop.getValueAsByteObjectArray());
                }
                else if (paramType.equals(byte[].class)) {
                    this.setter.invoke(instance, prop.getValueAsByteArray());
                }
            }
            else if (prop.getEdmType() == EdmType.BOOLEAN) {
                if (paramType.equals(Boolean.class)) {
                    this.setter.invoke(instance, prop.getValueAsBooleanObject());
                }
                else if (paramType.equals(boolean.class)) {
                    this.setter.invoke(instance, prop.getValueAsBoolean());
                }
            }
            else if (prop.getEdmType() == EdmType.DOUBLE) {
                if (paramType.equals(Double.class)) {
                    this.setter.invoke(instance, prop.getValueAsDoubleObject());
                }
                else if (paramType.equals(double.class)) {
                    this.setter.invoke(instance, prop.getValueAsDouble());
                }
            }
            else if (prop.getEdmType() == EdmType.GUID) {                
                if (paramType.equals(UUID.class)) {
                    this.setter.invoke(instance, prop.getValueAsUUID());
                }
            }
            else if (prop.getEdmType() == EdmType.INT32) {
                if (paramType.equals(Integer.class)) {
                    this.setter.invoke(instance, prop.getValueAsIntegerObject());
                }
                else if (paramType.equals(int.class)) {
                    this.setter.invoke(instance, prop.getValueAsInteger());
                }
            }
            else if (prop.getEdmType() == EdmType.INT64) {
                if (paramType.equals(Long.class)) {
                    this.setter.invoke(instance, prop.getValueAsLongObject());
                }
                else if (paramType.equals(long.class)) {
                    this.setter.invoke(instance, prop.getValueAsLong());
                }
            }
            else if (prop.getEdmType() == EdmType.DATE_TIME) {
                if (paramType.equals(Date.class)) {
                    this.setter.invoke(instance, prop.getValueAsDate());
                }
            }
            else {
                throw new IllegalArgumentException(String.format(SR.PROPERTY_CANNOT_BE_SERIALIZED_AS_GIVEN_EDMTYPE,
                        this.name, prop.getEdmType().toString()));
            }
        }
    }

    /**
     * Reserved for internal use. Generates an {@link EntityProperty} from the result of invoking the getter method for
     * this property on the specified instance parameter.
     * 
     * @param instance
     *            An instance of a class supporting this property with getter and setter methods of the
     *            appropriate name and parameter or return type.
     * 
     * @return
     *         An {@link EntityProperty} with the data type and value returned by the invoked getter on the instance.
     * 
     * @throws IllegalArgumentException
     *             if the specified instance parameter is not an instance of the class
     *             or interface declaring the getter method (or of a subclass or implementor thereof).
     * @throws IllegalAccessException
     *             if the getter method is inaccessible.
     * @throws InvocationTargetException
     *             if the getter method throws an exception.
     */
    protected EntityProperty generateEntityProperty(final Object instance) throws IllegalArgumentException,
            IllegalAccessException, InvocationTargetException {
        final Class getType = this.getter.getReturnType();
        Object val = this.getter.invoke(instance, (Object[]) null);
        
        EntityProperty property = new EntityProperty(val, getType);
        property.setIsEncrypted(this.isEncrypted);
        return property;
    }

    /**
     * Reserved for internal use. A utility function that returns true if this property is accessible
     * through reflection.
     * 
     * @return
     */
    protected boolean shouldProcess() {
        if (Utility.isNullOrEmpty(this.name) || this.getter == null || this.getter.isAnnotationPresent(Ignore.class)
                || this.setter == null || this.setter.isAnnotationPresent(Ignore.class)
                || (!this.getter.getReturnType().equals(this.setter.getParameterTypes()[0]))) {
            return false;
        }

        return true;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy