io.katharsis.resource.field.ResourceAttributesBridge Maven / Gradle / Ivy
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