/*
* Copyright (c) 2005-2016 Vincent Vandenschrick. All rights reserved.
*
* This file is part of the Jspresso framework.
*
* Jspresso is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Jspresso is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Jspresso. If not, see <http://www.gnu.org/licenses/>.
*/
package org.jspresso.framework.model.entity;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import org.jspresso.framework.model.component.IComponent;
import org.jspresso.framework.model.descriptor.ICollectionPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IComponentDescriptor;
import org.jspresso.framework.model.descriptor.IModelDescriptorAware;
import org.jspresso.framework.model.descriptor.IPropertyDescriptor;
import org.jspresso.framework.model.descriptor.IReferencePropertyDescriptor;
import org.jspresso.framework.model.descriptor.IRelationshipEndPropertyDescriptor;
import org.jspresso.framework.util.accessor.IAccessorFactory;
import org.jspresso.framework.util.accessor.ICollectionAccessor;
/**
* Performs a copy of the entity. It may be used in application actions to
* smartly duplicate an entity.
*
* @author Vincent Vandenschrick
*/
public class SmartEntityCloneFactory extends CarbonEntityCloneFactory {
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <E extends IComponent> E cloneComponent(E componentToClone,
IEntityFactory entityFactory) {
E clonedComponent = (E) entityFactory
.createComponentInstance(componentToClone.getComponentContract());
handleRelationships(componentToClone, clonedComponent, entityFactory);
return clonedComponent;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
@Override
public <E extends IEntity> E cloneEntity(E entityToClone,
IEntityFactory entityFactory) {
E clonedEntity = (E) entityFactory.createEntityInstance(entityToClone
.getComponentContract());
handleRelationships(entityToClone, clonedEntity, entityFactory);
return clonedEntity;
}
/**
* Sets the accessorFactory.
*
* @param accessorFactory
* the accessorFactory to set.
* @deprecated accessor factory is now retrieved from the entity factory
* passed as parameter of the methods.
*/
@SuppressWarnings({"EmptyMethod", "UnusedParameters"})
@Deprecated
public void setAccessorFactory(IAccessorFactory accessorFactory) {
// NO-OP
}
/**
* Whether the object is fully initialized.
*
* @param objectOrProxy
* the object to test.
* @return true if the object is fully initialized.
*/
protected boolean isInitialized(Object objectOrProxy) {
return true;
}
private <E extends IComponent> void handleRelationships(E componentToClone,
E clonedComponent, IEntityFactory entityFactory) {
IComponentDescriptor<?> componentDescriptor = entityFactory
.getComponentDescriptor(componentToClone.getComponentContract());
Map<Object, ICollectionPropertyDescriptor<?>> collRelToUpdate = new HashMap<>();
for (Map.Entry<String, Object> propertyEntry : componentToClone
.straightGetProperties().entrySet()) {
if (propertyEntry.getValue() != null
&& !(IEntity.ID.equals(propertyEntry.getKey())
|| IEntity.VERSION.equals(propertyEntry.getKey()) || componentDescriptor
.getUnclonedProperties().contains(propertyEntry.getKey()))) {
IPropertyDescriptor propertyDescriptor = componentDescriptor
.getPropertyDescriptor(propertyEntry.getKey());
if (propertyDescriptor instanceof IRelationshipEndPropertyDescriptor) {
if (propertyEntry.getValue() instanceof IComponent
&& !(propertyEntry.getValue() instanceof IEntity)) {
clonedComponent.straightSetProperty(
propertyEntry.getKey(),
cloneComponent((IComponent) propertyEntry.getValue(),
entityFactory));
} else {
IRelationshipEndPropertyDescriptor reverseDescriptor = ((IRelationshipEndPropertyDescriptor) propertyDescriptor)
.getReverseRelationEnd();
if (propertyDescriptor instanceof IReferencePropertyDescriptor<?>) {
if (!(reverseDescriptor instanceof IReferencePropertyDescriptor<?>)) {
if (((IRelationshipEndPropertyDescriptor) propertyDescriptor)
.isComposition()) {
clonedComponent.straightSetProperty(
propertyEntry.getKey(),
cloneEntity((IEntity) propertyEntry.getValue(),
entityFactory));
} else {
clonedComponent.straightSetProperty(propertyEntry.getKey(),
propertyEntry.getValue());
if (reverseDescriptor instanceof ICollectionPropertyDescriptor<?>) {
if (isInitialized(propertyEntry.getValue())) {
collRelToUpdate.put(propertyEntry.getValue(),
(ICollectionPropertyDescriptor<?>) reverseDescriptor);
}
}
}
}
} else if (propertyDescriptor instanceof ICollectionPropertyDescriptor<?>) {
if (reverseDescriptor instanceof ICollectionPropertyDescriptor<?>) {
// We must force initialization of the collection. So do a get.
try {
entityFactory
.getAccessorFactory()
.createPropertyAccessor(propertyEntry.getKey(),
componentToClone.getComponentContract())
.getValue(componentToClone);
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new EntityException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new EntityException(ex.getCause());
}
for (Object reverseCollectionElement : (Collection<?>) propertyEntry
.getValue()) {
if (isInitialized(reverseCollectionElement)) {
collRelToUpdate.put(reverseCollectionElement,
(ICollectionPropertyDescriptor<?>) reverseDescriptor);
}
}
}
}
}
} else {
clonedComponent.straightSetProperty(propertyEntry.getKey(),
ObjectUtils.cloneIfPossible(propertyEntry.getValue()));
}
}
}
for (Map.Entry<Object, ICollectionPropertyDescriptor<?>> collectionEntry : collRelToUpdate
.entrySet()) {
ICollectionPropertyDescriptor<?> collectionDescriptor = collectionEntry
.getValue();
Class<?> masterContract = null;
if (collectionDescriptor.getReverseRelationEnd() instanceof IReferencePropertyDescriptor<?>) {
masterContract = ((IReferencePropertyDescriptor<?>) collectionDescriptor
.getReverseRelationEnd()).getReferencedDescriptor()
.getComponentContract();
} else if (collectionDescriptor.getReverseRelationEnd() instanceof ICollectionPropertyDescriptor<?>) {
masterContract = ((ICollectionPropertyDescriptor<?>) collectionDescriptor
.getReverseRelationEnd()).getReferencedDescriptor()
.getElementDescriptor().getComponentContract();
}
ICollectionAccessor collectionAccessor = entityFactory
.getAccessorFactory().createCollectionPropertyAccessor(
collectionDescriptor.getName(), masterContract,
clonedComponent.getComponentContract());
if (collectionAccessor instanceof IModelDescriptorAware) {
((IModelDescriptorAware) collectionAccessor)
.setModelDescriptor(collectionDescriptor);
}
try {
Collection<?> existingCollection = collectionAccessor
.getValue(collectionEntry.getKey());
if (!existingCollection.contains(clonedComponent)) {
// it could already be there through lifecycle handlers / property
// processors.
collectionAccessor.addToValue(collectionEntry.getKey(),
clonedComponent);
}
} catch (IllegalAccessException | NoSuchMethodException ex) {
throw new EntityException(ex);
} catch (InvocationTargetException ex) {
if (ex.getCause() instanceof RuntimeException) {
throw (RuntimeException) ex.getCause();
}
throw new EntityException(ex.getCause());
}
}
}
}