/*
* 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.persistence.support;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.lightadmin.core.config.domain.DomainTypeAdministrationConfiguration;
import org.lightadmin.core.config.domain.GlobalAdministrationConfiguration;
import org.lightadmin.core.config.domain.field.PersistentFieldMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.*;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.support.DomainObjectMerger;
import org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper;
import java.util.Collection;
import java.util.Iterator;
import static com.google.common.collect.Lists.newArrayList;
import static org.lightadmin.core.config.domain.field.FieldMetadataUtils.getPersistentField;
import static org.springframework.data.rest.core.support.DomainObjectMerger.NullHandlingPolicy.APPLY_NULLS;
import static org.springframework.util.ObjectUtils.nullSafeEquals;
public class DynamicDomainObjectMerger extends DomainObjectMerger {
private static final Logger logger = LoggerFactory.getLogger(DynamicDomainObjectMerger.class);
private final GlobalAdministrationConfiguration configuration;
public DynamicDomainObjectMerger(Repositories repositories, ConversionService conversionService, GlobalAdministrationConfiguration configuration) {
super(repositories, conversionService);
this.configuration = configuration;
}
/**
* Merges the given target object into the source one.
*
* @param from can be {@literal null}.
* @param target can be {@literal null}.
* @param nullPolicy how to handle {@literal null} values in the source object.
*/
@Override
public void merge(final Object from, final Object target, final NullHandlingPolicy nullPolicy) {
if (from == null || target == null) {
return;
}
final BeanWrapper fromWrapper = beanWrapper(from);
final BeanWrapper targetWrapper = beanWrapper(target);
final DomainTypeAdministrationConfiguration domainTypeAdministrationConfiguration = configuration.forManagedDomainType(target.getClass());
final PersistentEntity<?, ?> entity = domainTypeAdministrationConfiguration.getPersistentEntity();
entity.doWithProperties(new SimplePropertyHandler() {
@Override
public void doWithPersistentProperty(PersistentProperty<?> persistentProperty) {
Object sourceValue = fromWrapper.getPropertyValue(persistentProperty.getName());
Object targetValue = targetWrapper.getPropertyValue(persistentProperty.getName());
if (entity.isIdProperty(persistentProperty)) {
return;
}
if (nullSafeEquals(sourceValue, targetValue)) {
return;
}
if (propertyIsHiddenInFormView(persistentProperty, domainTypeAdministrationConfiguration)) {
return;
}
if (nullPolicy == APPLY_NULLS || sourceValue != null) {
targetWrapper.setPropertyValue(persistentProperty.getName(), sourceValue);
}
}
});
entity.doWithAssociations(new SimpleAssociationHandler() {
@Override
@SuppressWarnings("unchecked")
public void doWithAssociation(Association<? extends PersistentProperty<?>> association) {
PersistentProperty<?> persistentProperty = association.getInverse();
Object fromValue = fromWrapper.getPropertyValue(persistentProperty.getName());
Object targetValue = targetWrapper.getPropertyValue(persistentProperty.getName());
if (propertyIsHiddenInFormView(persistentProperty, domainTypeAdministrationConfiguration)) {
return;
}
if ((fromValue == null && nullPolicy == APPLY_NULLS)) {
targetWrapper.setPropertyValue(persistentProperty.getName(), fromValue);
}
if (persistentProperty.isCollectionLike()) {
Collection<Object> sourceCollection = (Collection) fromValue;
Collection<Object> targetCollection = (Collection) targetValue;
Collection<Object> candidatesForAddition = candidatesForAddition(sourceCollection, targetCollection, persistentProperty);
Collection<Object> candidatesForRemoval = candidatesForRemoval(sourceCollection, targetCollection, persistentProperty);
removeReferencedItems(targetCollection, candidatesForRemoval);
addReferencedItems(targetCollection, candidatesForAddition);
return;
}
if (!nullSafeEquals(fromValue, targetWrapper.getPropertyValue(persistentProperty.getName()))) {
targetWrapper.setPropertyValue(persistentProperty.getName(), fromValue);
}
}
});
}
private boolean propertyIsHiddenInFormView(PersistentProperty persistentProperty, DomainTypeAdministrationConfiguration configuration) {
PersistentFieldMetadata persistentField = getPersistentField(configuration.getFormViewFragment().getFields(), persistentProperty.getName());
return persistentField == null;
}
private void addReferencedItems(Collection<Object> targetCollection, Collection<Object> candidatesForAddition) {
for (Object candidateForAddition : candidatesForAddition) {
targetCollection.add(candidateForAddition);
}
}
private void removeReferencedItems(Collection<Object> targetCollection, Collection<Object> candidatesForRemoval) {
Iterator<Object> itr = targetCollection.iterator();
while (itr.hasNext()) {
Object obj = itr.next();
if (mathesAny(candidatesForRemoval, obj)) {
itr.remove();
}
}
}
private Collection<Object> candidatesForRemoval(Collection<Object> sourceCollection, Collection<Object> targetCollection, PersistentProperty<?> persistentProperty) {
Collection<Object> candidatesForRemoval = newArrayList();
for (Object targetItem : targetCollection) {
if (!mathesAny(sourceCollection, targetItem)) {
candidatesForRemoval.add(targetItem);
}
}
return candidatesForRemoval;
}
private Collection<Object> candidatesForAddition(Collection<Object> sourceCollection, Collection<Object> targetCollection, PersistentProperty<?> persistentProperty) {
Collection<Object> candidatesForAddition = newArrayList();
for (Object sourceItem : sourceCollection) {
if (!mathesAny(targetCollection, sourceItem)) {
candidatesForAddition.add(sourceItem);
}
}
return candidatesForAddition;
}
private boolean mathesAny(Collection<Object> collection, final Object item) {
final PersistentEntity<?, ?> persistentEntity = configuration.forManagedDomainType(item.getClass()).getPersistentEntity();
final PersistentProperty<?> idProperty = persistentEntity.getIdProperty();
return Iterables.any(collection, new Predicate<Object>() {
@Override
public boolean apply(Object object) {
return itemsEqual(object, item, idProperty);
}
});
}
private boolean itemsEqual(Object item1, Object item2, final PersistentProperty<?> idProperty) {
if (nullSafeEquals(item1, item2)) {
return true;
}
Object sourceItemIdValue = beanWrapper(item1).getPropertyValue(idProperty.getName());
Object itemIdValue = beanWrapper(item2).getPropertyValue(idProperty.getName());
return nullSafeEquals(itemIdValue, sourceItemIdValue);
}
private DirectFieldAccessFallbackBeanWrapper beanWrapper(Object item1) {
return new DirectFieldAccessFallbackBeanWrapper(item1);
}
}