/*
* Copyright 2012-2014 the original author or authors.
*
* 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 org.lightadmin.core.web.support;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import org.lightadmin.api.config.utils.EntityNameExtractor;
import org.lightadmin.core.config.domain.DomainTypeAdministrationConfiguration;
import org.lightadmin.core.config.domain.DomainTypeBasicConfiguration;
import org.lightadmin.core.config.domain.GlobalAdministrationConfiguration;
import org.lightadmin.core.config.domain.field.FieldMetadata;
import org.lightadmin.core.config.domain.unit.DomainConfigurationUnitType;
import org.lightadmin.core.persistence.metamodel.PersistentPropertyType;
import org.lightadmin.core.storage.FileResourceStorage;
import org.springframework.data.mapping.*;
import org.springframework.data.rest.core.mapping.ResourceMappings;
import org.springframework.data.rest.webmvc.PersistentEntityResource;
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.ResourceProcessor;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static org.lightadmin.core.config.domain.configuration.support.ExceptionAwareTransformer.exceptionAwareNameExtractor;
import static org.lightadmin.core.config.domain.field.FieldMetadataUtils.customFields;
import static org.lightadmin.core.config.domain.field.FieldMetadataUtils.transientFields;
import static org.lightadmin.core.config.domain.unit.DomainConfigurationUnitType.*;
import static org.lightadmin.core.persistence.metamodel.PersistentPropertyType.FILE;
import static org.lightadmin.core.web.support.DynamicPersistentEntityResourceProcessor.PersistentEntityWrapper.persistentEntity;
@SuppressWarnings(value = {"unchecked", "unused"})
public class DynamicPersistentEntityResourceProcessor implements ResourceProcessor<PersistentEntityResource> {
private final GlobalAdministrationConfiguration adminConfiguration;
private final DynamicRepositoryEntityLinks entityLinks;
private final DomainEntityLinks domainEntityLinks;
private final FileResourceStorage fileResourceStorage;
private final AssociationLinks associationLinks;
public DynamicPersistentEntityResourceProcessor(GlobalAdministrationConfiguration adminConfiguration, FileResourceStorage fileResourceStorage, DynamicRepositoryEntityLinks entityLinks, DomainEntityLinks domainEntityLinks, ResourceMappings resourceMappings) {
this.adminConfiguration = adminConfiguration;
this.domainEntityLinks = domainEntityLinks;
this.entityLinks = entityLinks;
this.fileResourceStorage = fileResourceStorage;
this.associationLinks = new AssociationLinks(resourceMappings);
}
@Override
public PersistentEntityResource process(PersistentEntityResource persistentEntityResource) {
PersistentEntity persistentEntity = persistentEntityResource.getPersistentEntity();
Object value = persistentEntityResource.getContent();
Link[] links = persistentEntityResource.getLinks().toArray(new Link[persistentEntityResource.getLinks().size()]);
String stringRepresentation = stringRepresentation(value, persistentEntity);
Link domainLink = domainLink(persistentEntityResource);
boolean managedDomainType = adminConfiguration.isManagedDomainType(persistentEntity.getType());
String primaryKey = primaryKey(persistentEntity);
Map<DomainConfigurationUnitType, Map<String, Object>> dynamicProperties = dynamicPropertiesPerUnit(value, persistentEntity);
PersistentEntityWrapper persistentEntityWrapper = persistentEntity(value, dynamicProperties, stringRepresentation, domainLink, managedDomainType, primaryKey);
PersistentEntityResource.Builder builder = PersistentEntityResource.build(persistentEntityWrapper, persistentEntity);
for (Link link: links) {
builder = builder.withLink(link);
}
return builder.build();
}
private String primaryKey(PersistentEntity persistentEntity) {
return persistentEntity.getIdProperty().getName();
}
private String stringRepresentation(Object value, PersistentEntity persistentEntity) {
DomainTypeBasicConfiguration domainTypeBasicConfiguration = adminConfiguration.forDomainType(persistentEntity.getType());
EntityNameExtractor<Object> nameExtractor = domainTypeBasicConfiguration.getEntityConfiguration().getNameExtractor();
return exceptionAwareNameExtractor(nameExtractor, domainTypeBasicConfiguration).apply(value);
}
private Link domainLink(PersistentEntityResource persistentEntityResource) {
PersistentEntity persistentEntity = persistentEntityResource.getPersistentEntity();
if (domainEntityLinks.supports(persistentEntity.getType())) {
return domainEntityLinks.linkFor(persistentEntityResource);
}
return null;
}
private Map<DomainConfigurationUnitType, Map<String, Object>> dynamicPropertiesPerUnit(Object value, PersistentEntity persistentEntity) {
if (!adminConfiguration.isManagedDomainType(persistentEntity.getType())) {
return Collections.emptyMap();
}
DomainTypeAdministrationConfiguration managedDomainTypeConfiguration = adminConfiguration.forManagedDomainType(persistentEntity.getType());
List<DomainConfigurationUnitType> units = newArrayList(LIST_VIEW, FORM_VIEW, SHOW_VIEW, QUICK_VIEW);
List<PersistentProperty> persistentProperties = findPersistentFileProperties(persistentEntity);
List<Association> associations = findLinkableAssociations(persistentEntity);
Map<DomainConfigurationUnitType, Map<String, Object>> dynamicPropertiesPerUnit = newHashMap();
for (DomainConfigurationUnitType unit : units) {
Map<String, Object> dynamicProperties = newLinkedHashMap();
for (PersistentProperty persistentProperty : persistentProperties) {
dynamicProperties.put(persistentProperty.getName(), filePropertyValue(persistentProperty, value));
}
for (Association association : associations) {
dynamicProperties.put(association.getInverse().getName(), associationPropertyValue(association, value));
}
for (FieldMetadata customField : customFields(managedDomainTypeConfiguration.fieldsForUnit(unit))) {
dynamicProperties.put(customField.getUuid(), customField.getValue(value));
}
for (FieldMetadata transientField : transientFields(managedDomainTypeConfiguration.fieldsForUnit(unit))) {
dynamicProperties.put(transientField.getUuid(), transientField.getValue(value));
}
dynamicPropertiesPerUnit.put(unit, dynamicProperties);
}
return dynamicPropertiesPerUnit;
}
private Object associationPropertyValue(Association association, Object instance) {
PersistentProperty persistentProperty = association.getInverse();
PersistentEntity persistentEntity = persistentProperty.getOwner();
if (persistentProperty.isMap()) {
return null;
}
if (persistentProperty.isCollectionLike()) {
return associatedPersistentEntities(association, instance);
}
Object associationValue = beanWrapper(instance).getPropertyValue(persistentProperty.getName());
return associatedPersistentEntity(persistentProperty, associationValue);
}
private List<PersistentEntityWrapper> associatedPersistentEntities(Association association, Object instance) {
PersistentProperty persistentProperty = association.getInverse();
Object associationValue = beanWrapper(instance).getPropertyValue(persistentProperty.getName());
if (associationValue == null) {
return null;
}
List<PersistentEntityWrapper> result = newArrayList();
if (persistentProperty.isArray()) {
for (Object item : (Object[]) associationValue) {
result.add(associatedPersistentEntity(persistentProperty, item));
}
return result;
}
for (Object item : (Iterable<Object>) associationValue) {
result.add(associatedPersistentEntity(persistentProperty, item));
}
return result;
}
private PersistentEntityWrapper associatedPersistentEntity(PersistentProperty persistentProperty, Object associationValue) {
if (associationValue == null) {
return null;
}
Class associationType = persistentProperty.getActualType();
boolean managedDomainType = adminConfiguration.isManagedDomainType(associationType);
PersistentEntity associationPersistentEntity = adminConfiguration.forDomainType(associationType).getPersistentEntity();
String stringRepresentation = stringRepresentation(associationValue, associationPersistentEntity);
String primaryKey = primaryKey(associationPersistentEntity);
Object primaryKeyValue = beanWrapper(associationValue).getPropertyValue(primaryKey);
Link domainLink = null;
if (domainEntityLinks.supports(associationType)) {
domainLink = domainEntityLinks.linkToSingleResource(associationType, primaryKeyValue);
}
return PersistentEntityWrapper.associatedPersistentEntity(stringRepresentation, managedDomainType, primaryKey, primaryKeyValue, domainLink);
}
private static DirectFieldAccessFallbackBeanWrapper beanWrapper(Object instance) {
return new DirectFieldAccessFallbackBeanWrapper(instance);
}
private List<PersistentProperty> findPersistentFileProperties(PersistentEntity persistentEntity) {
final List<PersistentProperty> result = newArrayList();
persistentEntity.doWithProperties(new SimplePropertyHandler() {
@Override
public void doWithPersistentProperty(PersistentProperty<?> property) {
if (PersistentPropertyType.forPersistentProperty(property) == FILE) {
result.add(property);
}
}
});
return result;
}
private List<Association> findLinkableAssociations(PersistentEntity persistentEntity) {
final List<Association> result = newArrayList();
persistentEntity.doWithAssociations(new SimpleAssociationHandler() {
@Override
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
if (associationLinks.isLinkableAssociation(association.getInverse())) {
result.add(association);
}
}
});
return result;
}
private FilePropertyValue filePropertyValue(PersistentProperty persistentProperty, Object value) {
try {
if (!fileResourceStorage.fileExists(value, persistentProperty)) {
return new FilePropertyValue(false);
}
return new FilePropertyValue(entityLinks.linkForFilePropertyLink(value, persistentProperty));
} catch (Exception e) {
return null;
}
}
static class PersistentEntityWrapper {
private String stringRepresentation;
private boolean managedDomainType;
private String primaryKey;
private Link domainLink;
private Object persistentEntity;
private Map<DomainConfigurationUnitType, Map<String, Object>> dynamicProperties;
private PersistentEntityWrapper(Object persistentEntity, Map<DomainConfigurationUnitType, Map<String, Object>> dynamicProperties, String stringRepresentation, Link domainLink, boolean managedDomainType, String primaryKey) {
this.stringRepresentation = stringRepresentation;
this.domainLink = domainLink;
this.managedDomainType = managedDomainType;
this.persistentEntity = persistentEntity;
this.dynamicProperties = dynamicProperties;
this.primaryKey = primaryKey;
}
public static PersistentEntityWrapper associatedPersistentEntity(String stringRepresentation, boolean managedDomainType, String primaryKey, Object primaryKeyValue, Link domainLink) {
Map<String, Object> persistentEntity = newHashMap();
persistentEntity.put(primaryKey, primaryKeyValue);
return new PersistentEntityWrapper(persistentEntity, null, stringRepresentation, domainLink, managedDomainType, primaryKey);
}
public static PersistentEntityWrapper persistentEntity(Object instance, Map<DomainConfigurationUnitType, Map<String, Object>> dynamicProperties, String stringRepresentation, Link domainLink, boolean managedDomainType, String primaryKey) {
return new PersistentEntityWrapper(instance, dynamicProperties, stringRepresentation, domainLink, managedDomainType, primaryKey);
}
@JsonProperty("string_representation")
public String getStringRepresentation() {
return stringRepresentation;
}
@JsonProperty("primary_key")
public String getPrimaryKey() {
return primaryKey;
}
@JsonProperty("managed_type")
public boolean isManagedDomainType() {
return managedDomainType;
}
@JsonProperty("domain_link")
@JsonInclude(NON_NULL)
public Link getDomainLink() {
return domainLink;
}
@JsonProperty("original_properties")
@JsonInclude(NON_NULL)
public Object getPersistentEntity() {
return persistentEntity;
}
@JsonProperty("dynamic_properties")
@JsonInclude(NON_EMPTY)
public Map<DomainConfigurationUnitType, Map<String, Object>> getDynamicProperties() {
return dynamicProperties;
}
}
}