package io.katharsis.jackson.serializer.include;
import io.katharsis.queryParams.include.Inclusion;
import io.katharsis.queryParams.params.IncludedRelationsParams;
import io.katharsis.queryParams.params.TypedParams;
import io.katharsis.request.path.ResourcePath;
import io.katharsis.resource.annotations.JsonApiIncludeByDefault;
import io.katharsis.resource.exception.ResourceFieldNotFoundException;
import io.katharsis.resource.field.ResourceField;
import io.katharsis.resource.information.ResourceInformation;
import io.katharsis.resource.registry.RegistryEntry;
import io.katharsis.resource.registry.ResourceRegistry;
import io.katharsis.response.BaseResponseContext;
import io.katharsis.response.Container;
import io.katharsis.utils.ClassUtils;
import io.katharsis.utils.PropertyUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
/**
* Extracts inclusions from a resource.
*/
public class IncludedRelationshipExtractor {
private static final Logger logger = LoggerFactory.getLogger(IncludedRelationshipExtractor.class);
private final ResourceRegistry resourceRegistry;
public IncludedRelationshipExtractor(ResourceRegistry resourceRegistry) {
this.resourceRegistry = resourceRegistry;
}
public Map<ResourceDigest, Container> extractIncludedResources(Object resource, BaseResponseContext response) {
Map<ResourceDigest, Container> includedResources = new HashMap<>();
//noinspection unchecked
includedResources.putAll(extractDefaultIncludedFields(resource, response));
try {
//noinspection unchecked
includedResources.putAll(extractIncludedRelationships(resource, response));
} catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException | NoSuchFieldException e) {
logger.info("Exception while extracting included fields", e);
}
return includedResources;
}
private Map<ResourceDigest, Container> extractDefaultIncludedFields(Object resource, BaseResponseContext response) {
List<?> includedResources = getIncludedByDefaultResources(resource, 1);
Map<ResourceDigest, Container> includedResourceContainers = new HashMap<>(includedResources.size());
for (Object includedResource : includedResources) {
ResourceDigest digest = getResourceDigest(includedResource);
includedResourceContainers.put(digest, new Container(includedResource, response));
}
return includedResourceContainers;
}
private List<?> getIncludedByDefaultResources(Object resource, int recurrenceLevel) {
int recurrenceLevelCounter = recurrenceLevel;
if (recurrenceLevel >= 42 || resource == null) {
return Collections.emptyList();
}
Set<ResourceField> relationshipFields = getRelationshipFields(resource);
List includedFields = new ArrayList();
//noinspection unchecked
for (ResourceField resourceField : relationshipFields) {
if (resourceField.isAnnotationPresent(JsonApiIncludeByDefault.class)) {
Object targetDataObj = PropertyUtils.getProperty(resource, resourceField.getUnderlyingName());
if (targetDataObj != null) {
recurrenceLevelCounter++;
if (targetDataObj instanceof Iterable) {
for (Object objectItem : (Iterable) targetDataObj) {
//noinspection unchecked
includedFields.add(objectItem);
//noinspection unchecked
includedFields.addAll(getIncludedByDefaultResources(objectItem, recurrenceLevelCounter));
}
} else {
//noinspection unchecked
includedFields.add(targetDataObj);
//noinspection unchecked
includedFields.addAll(getIncludedByDefaultResources(targetDataObj, recurrenceLevelCounter));
}
}
}
}
return includedFields;
}
private Map<ResourceDigest, Container> extractIncludedRelationships(Object resource, BaseResponseContext response)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Map<ResourceDigest, Container> includedResources = new HashMap<>();
TypedParams<IncludedRelationsParams> includedRelations = response.getQueryParams()
.getIncludedRelations();
String elementName = response.getJsonPath()
.getElementName();
IncludedRelationsParams includedRelationsParams = findInclusions(includedRelations, elementName);
if (includedRelationsParams != null) {
for (Inclusion inclusion : includedRelationsParams.getParams()) {
//noinspection unchecked
includedResources.putAll(extractIncludedRelationship(resource, inclusion, response));
}
}
return includedResources;
}
private static IncludedRelationsParams findInclusions(TypedParams<IncludedRelationsParams> queryParams,
String resourceName) {
if (queryParams != null && queryParams.getParams() != null) {
for (Map.Entry<String, IncludedRelationsParams> entry : queryParams.getParams()
.entrySet()) {
if (resourceName.equals(entry.getKey())) {
return entry.getValue();
}
}
}
return null;
}
private Map<ResourceDigest, Container> extractIncludedRelationship(Object resource, Inclusion inclusion, BaseResponseContext response)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
List<String> pathList = inclusion.getPathList();
if (resource == null || pathList.isEmpty()) {
return Collections.emptyMap();
}
if (!(response.getJsonPath() instanceof ResourcePath)) { // the first property name is the resource itself
pathList = pathList.subList(1, pathList.size());
if (pathList.isEmpty()) {
return Collections.emptyMap();
}
}
return getElements(resource, pathList, response);
}
private Map<ResourceDigest, Container> getElements(Object resource, List<String> pathList, BaseResponseContext response)
throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Map<ResourceDigest, Container> elements = new HashMap<>();
String fieldName = getRelationshipName(pathList.get(0), resource.getClass());
Object resourceProperty = PropertyUtils.getProperty(resource, fieldName);
if (resourceProperty != null) {
if (Iterable.class.isAssignableFrom(resourceProperty.getClass())) {
for (Object resourceToInclude : (Iterable) resourceProperty) {
ResourceDigest digest = getResourceDigest(resourceToInclude);
//noinspection unchecked
elements.put(digest, new Container(resourceToInclude, response));
}
} else {
ResourceDigest digest = getResourceDigest(resourceProperty);
//noinspection unchecked
elements.put(digest, new Container(resourceProperty, response));
}
} else {
return Collections.emptyMap();
}
return elements;
}
private <T> String getRelationshipName(String jsonName, Class<T> resourceClazz) {
RegistryEntry resourceEntry = resourceRegistry.getEntry(resourceClazz);
ResourceField relationshipField = resourceEntry.getResourceInformation().findRelationshipFieldByName(jsonName);
if (relationshipField == null) {
throw new ResourceFieldNotFoundException(String.format("%s for %s has been not found", jsonName, resourceClazz));
}
return relationshipField.getUnderlyingName();
}
private Set<ResourceField> getRelationshipFields(Object resource) {
Class<?> dataClass = resource.getClass();
RegistryEntry entry = resourceRegistry.getEntry(dataClass);
ResourceInformation resourceInformation = entry.getResourceInformation();
return resourceInformation.getRelationshipFields();
}
private ResourceDigest getResourceDigest(Object resource) {
Class<?> resourceClass = ClassUtils.getJsonApiResourceClass(resource);
RegistryEntry registryEntry = resourceRegistry.getEntry(resourceClass);
String idFieldName = registryEntry.getResourceInformation().getIdField().getUnderlyingName();
Object idValue = PropertyUtils.getProperty(resource, idFieldName);
String resourceType = resourceRegistry.getResourceType(resourceClass);
return new ResourceDigest(idValue, resourceType);
}
}