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

net.sf.javaprinciples.data.transformer.JaxbMapper Maven / Gradle / Ivy

The newest version!
package net.sf.javaprinciples.data.transformer;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;

import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementRef;

import org.apache.cxf.jaxb.JAXBUtils;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.Converter;

import net.sf.javaprinciples.core.ObjectClassHelper;
import net.sf.javaprinciples.core.UnexpectedException;
import net.sf.javaprinciples.data.transformer.spring.JaxbEnumToStringConverter;
import net.sf.javaprinciples.data.transformer.spring.StringToJaxbEnumConverterFactory;

/**
 *  A Mapper class to inspect and treat object types as a {@link JAXBElement}.  Provides
 *  helper methods to get and set attributes on objects and JAXBElements.
 *
 * @author Kay Chevalier
 */
public class JaxbMapper implements ObjectTypeMapper
{
    public static final String OBJECT_FACTORY_CLASS_NAME = "ObjectFactory";
    public static final String CREATE_METHOD_NAME = "create";

    private JaxbEnumToStringConverter jaxbEnumToStringConverter;
    private StringToJaxbEnumConverterFactory stringToJaxbEnumConverterFactory;

    private ConversionService conversionService;

    @Override
    public void assignAttributeToObject(Object attributeObject, Object parentObject, String attributeName)
    {
        if (attributeObject == null)
            return;

        attributeName = nameToIdentifier(attributeName);

        if (isJAXBElement(parentObject, attributeName) && !(attributeObject instanceof JAXBElement))
        {
            Object objectFactory = createObjectFactoryFromParentObject(parentObject);
            attributeObject = invokeObjectFactoryCreateMethod(attributeObject, parentObject, attributeName, objectFactory);
        }
        else
        {
            Class attributeTypeClass = (Class)ObjectClassHelper.getAttributeTypeByName(parentObject, attributeName);
            attributeObject = convertValueToEnum(attributeObject, attributeTypeClass);
        }

        BeanWrapperImpl beanWrapper = new BeanWrapperImpl(parentObject);
        if (conversionService != null)
        {
            beanWrapper.setConversionService(conversionService);
        }
        beanWrapper.setPropertyValue(attributeName, attributeObject);
    }

    private Object convertValueToEnum(Object attributeObject, Class attributeTypeClass)
    {
        if (attributeTypeClass != null && attributeTypeClass.isEnum() && attributeObject instanceof String)
        {
            Converter converter = stringToJaxbEnumConverterFactory.getConverter(attributeTypeClass);
            return converter.convert((String)attributeObject);
        }
        else if (attributeTypeClass != null && attributeTypeClass.isEnum() && attributeObject instanceof Boolean)
        {
            Converter converter = stringToJaxbEnumConverterFactory.getConverter(attributeTypeClass);
            return converter.convert(((Boolean)attributeObject).booleanValue() ? "Y" : "N");  //TODO This assumes any boolean enums will be a 'Y' or 'N'
        }

        return attributeObject;
    }

    private Object invokeObjectFactoryCreateMethod(Object attributeObject, Object parentObject, String attributeName,
                                                 Object objectFactory)
    {
        attributeName = nameToIdentifier(attributeName);
        Method method = findObjectFactoryCreateMethod(objectFactory, attributeName, parentObject);
        try
        {
            Class paramType = (Class)findObjectFactoryCreateMethodParamType(objectFactory, attributeName, parentObject);
            if (paramType.isEnum() && attributeObject instanceof String)
            {
                attributeObject = convertValueToEnum(attributeObject, paramType);
            }

            //TODO KC - replace this code
            if (String.class.equals(paramType))
            {
                attributeObject = String.valueOf(attributeObject);
            }

            if (Long.class.equals(paramType))
            {
                attributeObject = Long.parseLong((String)attributeObject);
            }

            return method.invoke(objectFactory, paramType.cast(attributeObject));
        }
        catch (IllegalAccessException e)
        {
            throw new UnexpectedException(String.format("Unable to invoke create method for JAXBElement for attribute %s",
                    attributeName), e);
        }
        catch (InvocationTargetException e)
        {
            throw new UnexpectedException(String.format("Unable to invoke create method for JAXBElement for attribute %s",
                    attributeName), e);
        }
        catch (IllegalArgumentException e)
        {
            throw new UnexpectedException(String.format("Unable to invoke create method for JAXBElement for attribute %s",
                    attributeName), e);
        }
        catch (ClassCastException e)
        {
            throw new UnexpectedException(String.format("Unable to cast attribute %s to required parameter type",
                    attributeName), e);
        }
    }

    /**
     * Instantiates an instance of the attribute object.  Given the parentObject, retrieves the
     * class type of the corresponding attribute to the attributeName.  If the attribute type
     * is a {@link JAXBElement} an object of the class type of the JAXBElement value is instantiated.
     * @param parentObject - Object containing the attribute
     * @param attributeName  - the name of the attribute
     * @return an instantiated object of the attribute
     */
    public Object instantiateObjectFromAttributeName(Object parentObject, String attributeName)
    {
        attributeName = nameToIdentifier(attributeName);
        java.lang.Class attributeValueType;

        if (isJAXBElement(parentObject, attributeName))
        {
            Object objectFactory = createObjectFactoryFromParentObject(parentObject);
            attributeValueType = findObjectFactoryCreateMethodParamType(objectFactory, attributeName, parentObject);
        }
        else
        {
            attributeValueType = new BeanWrapperImpl(parentObject).getPropertyType(attributeName);
        }

        return ObjectClassHelper.createObjectFromClassName(attributeValueType.getName());
    }

    /**
     * Creates an instance of the parent objects corresponding ObjectFactory.
     * It is assumed that the object factory has a name of ObjectFactory and resides
     * in the same package as the parent object.
     *
     * @param parentObject - the object to create the ObjectFactory class instance from
     * @return ObjectFactory - the ObjectFactory instance
     */
    public Object createObjectFactoryFromParentObject(Object parentObject)
    {
        String parentObjectPackage = parentObject.getClass().getPackage().getName();
        String objectFactoryClassName = parentObjectPackage + "." + OBJECT_FACTORY_CLASS_NAME;
        return ObjectClassHelper.createObjectFromClassName(objectFactoryClassName);
    }

    private Class findObjectFactoryCreateMethodParamType(Object objectFactory, String attributeName, Object parentObject)
    {
        Method method = findObjectFactoryCreateMethod(objectFactory, attributeName, parentObject);
        Class[] classTypes = method.getParameterTypes();
        return classTypes[0];
    }

    private Method findObjectFactoryCreateMethod(Object objectFactory, String attributeName, Object parentObject)
    {
        attributeName = nameToIdentifier(attributeName);
        char[] stringArray = attributeName.toCharArray();
        stringArray[0] = Character.toUpperCase(stringArray[0]);
        String capitalizedAttributeName = new String(stringArray);

        Method[] methods = objectFactory.getClass().getMethods();
        Method parentObjectCreateMethod = findObjectFactoryCreateMethod(parentObject.getClass(), methods, capitalizedAttributeName);
        if (parentObjectCreateMethod != null)
            return parentObjectCreateMethod;

        Method parentSuperClassObjectCreateMethod = findObjectFactoryCreateMethod(parentObject.getClass().getSuperclass(), methods, capitalizedAttributeName);
        if (parentSuperClassObjectCreateMethod != null)
            return parentSuperClassObjectCreateMethod;

        throw new UnexpectedException(String.format("Unable to find create method on " + OBJECT_FACTORY_CLASS_NAME + " for attribute %s",
                attributeName));
    }

    private Method findObjectFactoryCreateMethod(Class parentObject, Method[] createFactoryMethods, String capitalizedAttributeName)
    {
        String createMethodName = CREATE_METHOD_NAME + parentObject.getSimpleName() + capitalizedAttributeName;

        for (Method method : createFactoryMethods)
        {
            if (method.getName().equals(createMethodName))
            {
                return method;
            }
        }

        return null;
    }

    /**
     * Retrieves the value of the sourceAttributeName property from the given
     * input object.  Checks if the input object is of {@link JAXBElement} type and
     * if null considers this as a nillable attribute and throws {@link AttributeNotFoundException}.
     * If the sourceAttribute value is not a {@link JAXBElement} then nillable is not true and the
     * value can be null. Extracts the value attribute from the JAXBElement.
     *
     * @param input - the input object to retrieve the attribute instance from
     * @param sourceAttributeName  - the name of the attribute
     * @throws AttributeNotFoundException
     */
    @Override
    public Object getSourceAttribute(Object input, String sourceAttributeName) throws AttributeNotFoundException
    {
        sourceAttributeName = nameToIdentifier(sourceAttributeName, input);
        Object value = getProperty(input, sourceAttributeName);

        if (isJAXBElement(input, sourceAttributeName))
        {
            if ((value == null || ((JAXBElement)value).getValue() == null))
                throw new AttributeNotFoundException(String.format("Attribute: %s not found and is a nillable JAXBElement.",
                        sourceAttributeName));

            value = ((JAXBElement)value).getValue();
        }

        if(value instanceof Enum)
        {
            value = jaxbEnumToStringConverter.convert((Enum)value);
        }

        return value;
    }

    public Object getAttributeFromObject(Object input, String sourceAttributeName) throws AttributeNotFoundException
    {
        sourceAttributeName = nameToIdentifier(sourceAttributeName, input);

        Object value = getProperty(input, sourceAttributeName);

        return value;
    }

    public Class getParameterizedCollectionTypeFromObject(Object input, String sourceAttributeName) throws AttributeNotFoundException
    {
        sourceAttributeName = nameToIdentifier(sourceAttributeName, input);

        try
        {
            Field listField = input.getClass().getDeclaredField(sourceAttributeName);
            Object listFieldGenericType = listField.getGenericType();
            if (listFieldGenericType instanceof ParameterizedType)
            {
                ParameterizedType listType = (ParameterizedType) listFieldGenericType;
                return (Class) listType.getActualTypeArguments()[0];
            }

            return null;
        }
        catch (NoSuchFieldException e)
        {
            throw new UnexpectedException(String.format("Unable to determine list type for attribute %s", sourceAttributeName));
        }
    }

    public boolean isJAXBElement(Object owningObject, String attributeName)
    {
        attributeName = nameToIdentifier(attributeName);
        Class attributeTypeClass = ObjectClassHelper.getAttributeTypeByName(owningObject, attributeName);
        return JAXBElement.class.equals(attributeTypeClass);
    }

    /**
     * Formats a targetNamespace into a package name according to the rules
     * specified in the JAXB Specification.
     *
     * @param targetNamespace - the targetNamespace string to be converted
     * @return String - the converted package name
     */
    @Override
    public String formatPackageName(String targetNamespace)
    {
        return JAXBUtils.namespaceURIToPackage(targetNamespace);
    }

    /**
     * Formats a name into a java identifier according to the rules
     * specified in the JAXB Specification for a {@link JAXBUtils.IdentifierType} VARIABLE.
     *
     * @param name - the name to be converted
     * @return String - the java identifier
     */
    public String nameToIdentifier(String name)
    {
//        if (isAllUpperCase(name))
//        {
//            return name;
//        }
        name = net.sf.javaprinciples.core.JAXBUtils.nameToIdentifier(name, net.sf.javaprinciples.core.JAXBUtils.IdentifierType.CLASS);
        char[] stringArray = name.toCharArray();
        stringArray[0] = Character.toLowerCase(stringArray[0]);
        return new String(stringArray);
    }

    public Class determineModelTypeFromTarget(String name, Object owningObject)
    {
        String correctedName = nameToIdentifier(name);

        Field[] fields = owningObject.getClass().getDeclaredFields();

        if (fields == null)
        {
            throw new UnexpectedException("No fields for: " + owningObject.getClass());
        }

        for (Field field : fields)
        {
            XmlElementRef annotation = field.getAnnotation(XmlElementRef.class);
            if (annotation != null && name.equals(annotation.name()))
            {
                return annotation.type();
            }

            XmlElement xmlAnnotation = field.getAnnotation(XmlElement.class);
            if (xmlAnnotation != null && name.equals(xmlAnnotation.name()))
            {
                return xmlAnnotation.type();
            }
        }
        return null;
    }

    public String nameToIdentifier(String name, Object owningObject)
    {
        if (name == null)
        {
            return null;
        }

        String correctedName = nameToIdentifier(name);

        Field[] fields = owningObject.getClass().getDeclaredFields();

        if (fields == null)
            return correctedName;

        for (Field field : fields)
        {
            XmlElementRef annotation = field.getAnnotation(XmlElementRef.class);
            if (annotation != null && name.equals(annotation.name()))
            {
                if (Collection.class.isAssignableFrom(field.getType()))
                {
                    if (!correctedName.endsWith("s"))
                    {
                        correctedName = correctedName + "s";
                    }
                }
                return correctedName;
            }

            XmlElement xmlAnnotation = field.getAnnotation(XmlElement.class);
            if (xmlAnnotation != null && name.equals(xmlAnnotation.name()))
            {
                if (Collection.class.isAssignableFrom(field.getType()))
                {
                    if (!correctedName.endsWith("s"))
                    {
                        correctedName = correctedName + "s";
                    }
                }
                return correctedName;
            }
        }

        return correctedName;
    }

    private boolean isAllUpperCase(String toCheck)
    {
        for (char c : toCheck.toCharArray())
        {
            if (Character.isLowerCase(c))
            {
                return false;
            }
        }
        return true;
    }

    private Object getProperty(Object input, String sourceAttributeName)
    {
        if (input instanceof JAXBElement)
        {
            input = ((JAXBElement)input).getValue();
        }

        return new BeanWrapperImpl(input).getPropertyValue(sourceAttributeName);
    }

    public void setStringToJaxbEnumConverterFactory(StringToJaxbEnumConverterFactory stringToJaxbEnumConverterFactory)
    {
        this.stringToJaxbEnumConverterFactory = stringToJaxbEnumConverterFactory;
    }

    public void setJaxbEnumToStringConverter(JaxbEnumToStringConverter jaxbEnumToStringConverter)
    {
        this.jaxbEnumToStringConverter = jaxbEnumToStringConverter;
    }

    public void setConversionService(ConversionService conversionService)
    {
        this.conversionService = conversionService;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy