org.openmrs.module.webservices.rest.web.ConversionUtil.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.module.webservices.rest.web.ConversionUtil.java

Source

/**
 * The contents of this file are subject to the OpenMRS Public License
 * Version 1.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://license.openmrs.org
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * Copyright (C) OpenMRS, LLC.  All Rights Reserved.
 */
package org.openmrs.module.webservices.rest.web;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.APIException;
import org.openmrs.api.context.Context;
import org.openmrs.module.webservices.rest.web.api.RestService;
import org.openmrs.module.webservices.rest.web.representation.Representation;
import org.openmrs.module.webservices.rest.web.resource.api.Converter;
import org.openmrs.module.webservices.rest.web.resource.api.Resource;
import org.openmrs.module.webservices.rest.web.response.ConversionException;
import org.openmrs.util.HandlerUtil;
import org.openmrs.util.LocaleUtility;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

public class ConversionUtil {

    static final Log log = LogFactory.getLog(ConversionUtil.class);

    @SuppressWarnings("unchecked")
    public static <T> Converter<T> getConverter(Class<T> clazz) {
        try {

            try {
                Resource resource = Context.getService(RestService.class).getResourceBySupportedClass(clazz);

                if (resource instanceof Converter) {
                    return (Converter<T>) resource;
                }
            } catch (APIException e) {
            }

            Converter<T> converter = HandlerUtil.getPreferredHandler(Converter.class, clazz);
            return converter;
        } catch (APIException ex) {
            return null;
        }
    }

    /**
     * Converts the given object to the given type
     * 
     * @param object
     * @param toType a simple class or generic type
     * @return
     * @throws ConversionException
     * @should convert strings to locales
     * @should convert strings to enum values
     * @should convert to an array
     * @should convert to a class
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object convert(Object object, Type toType) throws ConversionException {
        if (object == null)
            return object;

        Class<?> toClass = toType instanceof Class ? ((Class<?>) toType)
                : (Class<?>) (((ParameterizedType) toType).getRawType());

        // if we're trying to convert _to_ a collection, handle it as a special case
        if (Collection.class.isAssignableFrom(toClass) || toClass.isArray()) {
            if (!(object instanceof Collection))
                throw new ConversionException("Can only convert a Collection to a Collection/Array. Not "
                        + object.getClass() + " to " + toType, null);

            if (toClass.isArray()) {
                Class<?> targetElementType = toClass.getComponentType();
                Collection input = (Collection) object;
                Object ret = Array.newInstance(targetElementType, input.size());

                int i = 0;
                for (Object element : (Collection) object) {
                    Array.set(ret, i, convert(element, targetElementType));
                    ++i;
                }
                return ret;
            }

            Collection ret = null;
            if (SortedSet.class.isAssignableFrom(toClass)) {
                ret = new TreeSet();
            } else if (Set.class.isAssignableFrom(toClass)) {
                ret = new HashSet();
            } else if (List.class.isAssignableFrom(toClass)) {
                ret = new ArrayList();
            } else {
                throw new ConversionException("Don't know how to handle collection class: " + toClass, null);
            }

            if (toType instanceof ParameterizedType) {
                // if we have generic type information for the target collection, we can use it to do conversion
                ParameterizedType toParameterizedType = (ParameterizedType) toType;
                Type targetElementType = toParameterizedType.getActualTypeArguments()[0];
                for (Object element : (Collection) object)
                    ret.add(convert(element, targetElementType));
            } else {
                // otherwise we must just add all items in a non-type-safe manner
                ret.addAll((Collection) object);
            }
            return ret;
        }

        // otherwise we're converting _to_ a non-collection type

        if (toClass.isAssignableFrom(object.getClass()))
            return object;

        // Numbers with a decimal are always assumed to be Double, so convert to Float, if necessary
        if (toClass.isAssignableFrom(Float.class) && object instanceof Double) {
            return new Float((Double) object);
        }

        if (object instanceof String) {
            String string = (String) object;
            Converter<?> converter = getConverter(toClass);
            if (converter != null)
                return converter.getByUniqueId(string);

            if (toClass.isAssignableFrom(Date.class)) {
                ParseException pex = null;
                String[] supportedFormats = { "yyyy-MM-dd'T'HH:mm:ss.SSSZ", "yyyy-MM-dd'T'HH:mm:ss.SSS",
                        "yyyy-MM-dd'T'HH:mm:ssZ", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd" };
                for (int i = 0; i < supportedFormats.length; i++) {
                    try {
                        Date date = new SimpleDateFormat(supportedFormats[i]).parse(string);
                        return date;
                    } catch (ParseException ex) {
                        pex = ex;
                    }
                }
                throw new ConversionException(
                        "Error converting date - correct format (ISO8601 Long): yyyy-MM-dd'T'HH:mm:ss.SSSZ", pex);
            } else if (toClass.isAssignableFrom(Locale.class)) {
                return LocaleUtility.fromSpecification(object.toString());
            } else if (toClass.isEnum()) {
                return Enum.valueOf((Class<? extends Enum>) toClass, object.toString());
            } else if (toClass.isAssignableFrom(Class.class)) {
                try {
                    return Context.loadClass((String) object);
                } catch (ClassNotFoundException e) {
                    throw new ConversionException("Could not convert from " + object.getClass() + " to " + toType,
                            e);
                }
            }
            // look for a static valueOf(String) method (e.g. Double, Integer, Boolean)
            try {
                Method method = toClass.getMethod("valueOf", String.class);
                if (Modifier.isStatic(method.getModifiers()) && toClass.isAssignableFrom(method.getReturnType())) {
                    return method.invoke(null, string);
                }
            } catch (Exception ex) {
            }
        } else if (object instanceof Map) {
            return convertMap((Map<String, ?>) object, toClass);
        }
        if (toClass.isAssignableFrom(Double.class) && object instanceof Number) {
            return ((Number) object).doubleValue();
        } else if (toClass.isAssignableFrom(Integer.class) && object instanceof Number) {
            return ((Number) object).intValue();
        }
        throw new ConversionException("Don't know how to convert from " + object.getClass() + " to " + toType,
                null);
    }

    /**
     * Converts a map to the given type, using the registered converter
     * 
     * @param map the map (typically a SimpleObject submitted as json) to convert
     * @param toClass the class to convert map to
     * @return the result of using a converter to instantiate a new class and set map's properties
     *         on it
     * @throws ConversionException
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static Object convertMap(Map<String, ?> map, Class<?> toClass) throws ConversionException {
        // TODO handle refs by fetching the object at their URI
        Converter converter = getConverter(toClass);
        String type = (String) map.get(RestConstants.PROPERTY_FOR_TYPE);
        Object ret = converter.newInstance(type);
        for (Map.Entry<String, ?> prop : map.entrySet()) {
            if (RestConstants.PROPERTY_FOR_TYPE.equals(prop.getKey()))
                continue;
            converter.setProperty(ret, prop.getKey(), prop.getValue());
        }
        return ret;
    }

    /**
     * Gets a property from the delegate, with the given representation
     * 
     * @param propertyName
     * @param rep
     * @return
     * @throws ConversionException
     */
    public static Object getPropertyWithRepresentation(Object bean, String propertyName, Representation rep)
            throws ConversionException {
        Object o;
        try {
            o = PropertyUtils.getProperty(bean, propertyName);
        } catch (Exception ex) {
            throw new ConversionException(null, ex);
        }
        if (o instanceof Collection) {
            List<Object> ret = new ArrayList<Object>();
            for (Object element : (Collection<?>) o)
                ret.add(convertToRepresentation(element, rep));
            return ret;
        } else {
            o = convertToRepresentation(o, rep);
            return o;
        }
    }

    @SuppressWarnings("unchecked")
    public static <S> Object convertToRepresentation(S o, Representation rep) throws ConversionException {
        return convertToRepresentation(o, rep, (Converter) null);
    }

    @SuppressWarnings("unchecked")
    public static <S> Object convertToRepresentation(S o, Representation rep, Class<?> convertAs)
            throws ConversionException {
        Converter<?> converter = convertAs != null ? getConverter(convertAs) : null;
        return convertToRepresentation(o, rep, converter);
    }

    public static <S> Object convertToRepresentation(S o, Representation rep, Converter specificConverter)
            throws ConversionException {
        if (o == null)
            return null;
        o = new HibernateLazyLoader().load(o);

        if (o instanceof Collection) {
            List ret = new ArrayList();
            for (Object item : ((Collection) o)) {
                ret.add(convertToRepresentation(item, rep, specificConverter));
            }
            return ret;

        } else {
            Converter<S> converter = specificConverter != null ? specificConverter
                    : (Converter) getConverter(o.getClass());
            if (converter == null) {
                // try a few known datatypes
                if (o instanceof Date) {
                    return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").format((Date) o);
                }
                // otherwise we have no choice but to return the plain object
                return o;
            }
            try {
                return converter.asRepresentation(o, rep);
            } catch (Exception ex) {
                throw new ConversionException("converting " + o.getClass() + " to " + rep, ex);
            }
        }
    }

}