uk.co.blackpepper.support.retrofit.jsoup.spring.AbstractBeanHtmlConverter.java Source code

Java tutorial

Introduction

Here is the source code for uk.co.blackpepper.support.retrofit.jsoup.spring.AbstractBeanHtmlConverter.java

Source

/*
 * Copyright 2014 Black Pepper Software
 *
 * 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 uk.co.blackpepper.support.retrofit.jsoup.spring;

import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.Map;

import org.jsoup.nodes.Element;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.beans.TypeMismatchException;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.format.support.DefaultFormattingConversionService;

import com.google.common.base.Function;

import retrofit.converter.ConversionException;
import retrofit.converter.Converter;
import retrofit.mime.TypedInput;
import retrofit.mime.TypedOutput;
import uk.co.blackpepper.support.retrofit.jsoup.TypedElement;

public abstract class AbstractBeanHtmlConverter<T extends TypedOutput> implements Converter {

    private final Map<String, Function<? super Element, String>> fromBodyFunctionsByPropertyName;

    private final Map<String, String> toBodyNamesByPropertyName;

    private ConversionService conversionService;

    public AbstractBeanHtmlConverter() {
        fromBodyFunctionsByPropertyName = new HashMap<>();
        toBodyNamesByPropertyName = new HashMap<>();
        conversionService = new DefaultFormattingConversionService();
    }

    protected final ConversionService getConversionService() {
        return conversionService;
    }

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

    /**
     * @deprecated Use {@link #setProperty(String, Function, String)} with {@link FromBody#textById(String)} instead.
     */
    @Deprecated
    protected final void setPropertyId(String propertyName, String id) {
        setProperty(propertyName, FromBody.textById(id), id);
    }

    /**
     * @deprecated Use {@link #setProperty(String, Function, String)} with
     *             {@link FromBody#attributeById(String, String)} instead.
     */
    @Deprecated
    protected final void setPropertyIdAndAttribute(String propertyName, String id, String attributeName) {
        setProperty(propertyName, FromBody.attributeById(id, attributeName), id);
    }

    /**
     * @deprecated Use {@link #setProperty(String, Function, String)} with {@link FromBody#textByClass(String)} instead.
     */
    @Deprecated
    protected final void setPropertyClass(String propertyName, String className) {
        setProperty(propertyName, FromBody.textByClass(className), className);
    }

    /**
     * @deprecated Use {@link #setProperty(String, Function, String)} with
     *             {@link FromBody#attributeByClass(String, String)} instead.
     */
    @Deprecated
    protected final void setPropertyClassAndAttribute(String propertyName, String className, String attributeName) {
        setProperty(propertyName, FromBody.attributeByClass(className, attributeName), className);
    }

    protected final void setProperty(String propertyName, Function<? super Element, String> fromBodyFunction,
            String toBodyName) {
        fromBodyFunctionsByPropertyName.put(propertyName, fromBodyFunction);
        toBodyNamesByPropertyName.put(propertyName, toBodyName);
    }

    @Override
    public Object fromBody(TypedInput body, Type type) throws ConversionException {
        TypedElement element = TypedElement.parse(body);
        Object bean = createBean(type);
        setProperties(bean, element.getElement());
        return bean;
    }

    @Override
    public TypedOutput toBody(Object bean) {
        return toBody(bean, newTypedOutput());
    }

    protected TypedOutput toBody(Object bean, T output) {
        for (String propertyName : toBodyNamesByPropertyName.keySet()) {
            String toBodyName = toBodyNamesByPropertyName.get(propertyName);
            String text = getAsText(bean, propertyName);

            if (text != null) {
                addProperty(output, toBodyName, text);
            }
        }

        return output;
    }

    protected abstract T newTypedOutput();

    protected abstract void addProperty(T output, String id, String text);

    /**
     * Instantiates the bean used when converting from HTTP body.
     * <p>
     * This method is called by {@link #fromBody(TypedInput, Type)}. The default implementation attempts to instantiate
     * the bean using its default constructor. Override this method to create the bean using a different constructor.
     * 
     * @throws ConversionException
     *             if the bean cannot be created
     */
    protected Object createBean(Type type) throws ConversionException {
        Class<?> beanClass = (Class<?>) type;
        return BeanUtils.instantiateClass(beanClass);
    }

    private void setProperties(Object bean, Element element) throws ConversionException {
        for (String propertyName : fromBodyFunctionsByPropertyName.keySet()) {
            Function<? super Element, String> fromBodyFunction = fromBodyFunctionsByPropertyName.get(propertyName);
            String text = fromBodyFunction.apply(element);

            if (text != null) {
                setAsText(bean, propertyName, text);
            }
        }
    }

    private String getAsText(Object bean, String propertyName) {
        BeanWrapper beanWrapper = wrap(bean);
        Object value = beanWrapper.getPropertyValue(propertyName);
        TypeDescriptor typeDescriptor = beanWrapper.getPropertyTypeDescriptor(propertyName);
        return (String) conversionService.convert(value, typeDescriptor, TypeDescriptor.valueOf(String.class));
    }

    private void setAsText(Object bean, String propertyName, String text) throws ConversionException {
        BeanWrapper beanWrapper = wrap(bean);
        TypeDescriptor typeDescriptor = beanWrapper.getPropertyTypeDescriptor(propertyName);

        Object value;
        try {
            value = conversionService.convert(text, TypeDescriptor.valueOf(String.class), typeDescriptor);
        } catch (TypeMismatchException exception) {
            String message = String.format("Error converting from '%s' to type '%s' for property '%s'", text,
                    typeDescriptor, propertyName);
            throw new ConversionException(message, exception);
        }

        try {
            beanWrapper.setPropertyValue(propertyName, value);
        } catch (BeansException exception) {
            String message = String.format("Error setting bean property '%s' to: %s", propertyName, value);
            throw new ConversionException(message, exception);
        }
    }

    private BeanWrapper wrap(Object bean) {
        BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
        beanWrapper.setConversionService(conversionService);
        return beanWrapper;
    }
}