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

io.katharsis.resource.field.ResourceAttributesBridge Maven / Gradle / Ivy

There is a newer version: 2.6.3
Show newest version
package io.katharsis.resource.field;

import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.katharsis.resource.exception.ResourceException;
import io.katharsis.resource.exception.init.InvalidResourceException;
import io.katharsis.utils.ClassUtils;
import io.katharsis.utils.PropertyUtils;
import io.katharsis.utils.java.Optional;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * Classes which implement those interface are able to provide a set of resource attributes
 */
public class ResourceAttributesBridge {

    private final Set staticFields;
    private final Class resourceClass;
    private Method jsonAnyGetter;
    private Method jsonAnySetter;

    public ResourceAttributesBridge(Set staticFields, Class resourceClass) {
        this.staticFields = staticFields;
        this.resourceClass = resourceClass;

        initializeGetterAndSetter(resourceClass);
    }

    private void initializeGetterAndSetter(Class resourceClass) {
        this.jsonAnyGetter = ClassUtils.findMethodWith(resourceClass, JsonAnyGetter.class);
        this.jsonAnySetter = ClassUtils.findMethodWith(resourceClass, JsonAnySetter.class);

        if (absentAnySetter()) {
            throw new InvalidResourceException(
                String.format("A resource %s has to have both methods annotated with @JsonAnySetter and @JsonAnyGetter",
                    resourceClass.getCanonicalName()));
        }
    }

    /**
     * The resource has to have both method annotated with {@link JsonAnySetter} and {@link JsonAnyGetter} to allow
     * proper handling.
     *
     * @return true if resource definition is incomplete, false otherwise
     */
    private boolean absentAnySetter() {
        return (jsonAnySetter == null && jsonAnyGetter != null) ||
            (jsonAnySetter != null && jsonAnyGetter == null);
    }

    /**
     * Sets instance properties using found attributes and {@link JsonAnySetter} annotated method
     *
     * @param objectMapper used to map new attributes
     * @param instance     instance to fill in attributes
     * @param attributes   set od attributes
     */
    public void setProperties(ObjectMapper objectMapper, T instance, JsonNode attributes) {
        T instanceWithNewFields;
        try {
            instanceWithNewFields = objectMapper.readerFor(resourceClass).readValue(attributes);
        } catch (IOException e) {
            throw new ResourceException(
                String.format("Exception while reading %s: %s", instance.getClass(), e.getMessage()));
        }
        Iterator propertyNameIterator = attributes.fieldNames();
        while (propertyNameIterator.hasNext()) {
            setProperty(instance, instanceWithNewFields, propertyNameIterator);
        }

        setAnyProperties(instance, instanceWithNewFields);
    }

    /**
     * Jackson {@link ObjectMapper#readerForUpdating(Object)} cannot be used here, because there might be a case where
     * instance parameter a proxied object e.g. by Hibernate.
     *
     * @param instance              instance of a resource
     * @param instanceWithNewFields a temporary instance with fields to be set
     * @param propertyNameIterator  set of properties
     */
    private void setProperty(T instance, T instanceWithNewFields, Iterator propertyNameIterator) {
        String propertyName = propertyNameIterator.next();
        Optional staticField = findStaticField(propertyName);
        if (staticField.isPresent()) {
            String underlyingName = staticField.get().getUnderlyingName();
            Object property = PropertyUtils.getProperty(instanceWithNewFields, underlyingName);
            PropertyUtils.setProperty(instance, underlyingName, property);
        } else {
            // Needed for JsonIgnore and dynamic attributes
        }
    }

    /**
     * Get a map of additional attributes and pass to {@link JsonAnySetter} annotated method
     *
     * @param instance              instance to fill in attributes
     * @param instanceWithNewFields temporary instance with new fields
     */
    private void setAnyProperties(T instance, T instanceWithNewFields) {
        if (jsonAnySetter != null) {
            @SuppressWarnings("unchecked")
            Map additionalAttributes;
            try {
                additionalAttributes = (Map) jsonAnyGetter.invoke(instanceWithNewFields);
                for (Map.Entry property : additionalAttributes.entrySet()) {
                    jsonAnySetter.invoke(instance, property.getKey(), property.getValue());
                }
            } catch (IllegalAccessException e) {
                throw new ResourceException(
                    String.format("Exception while setting %s: %s", instance.getClass(), e.getMessage()));
            } catch (InvocationTargetException e) {
                throw new ResourceException(
                    String.format("Exception while setting %s: %s", instance.getClass(), e.getMessage()));
            }
        }
    }

    private Optional findStaticField(String propertyName) {
        for (ResourceField resourceField : staticFields) {
            if (resourceField.getJsonName().equals(propertyName)) {
                return Optional.of(resourceField);
            }
        }
        return Optional.empty();
    }

	public Set getFields() {
		return staticFields;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy