PL-8690 REST API: CRUD operations on non-persistent entities from a custom data store

This commit is contained in:
Maxim Gorbunkov 2017-03-30 11:19:39 +04:00
parent d22c067a77
commit 865dbef4cf
4 changed files with 319 additions and 202 deletions

View File

@ -22,7 +22,12 @@ import com.google.common.collect.Multimap;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.Range;
import com.haulmont.cuba.core.*;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.PersistenceSecurity;
import com.haulmont.cuba.core.Transaction;
import com.haulmont.cuba.core.app.DataStore;
import com.haulmont.cuba.core.app.RdbmsStore;
import com.haulmont.cuba.core.app.StoreFactory;
import com.haulmont.cuba.core.app.dynamicattributes.DynamicAttributesManagerAPI;
import com.haulmont.cuba.core.app.serialization.EntitySerializationAPI;
import com.haulmont.cuba.core.app.serialization.EntitySerializationOption;
@ -30,9 +35,7 @@ import com.haulmont.cuba.core.entity.BaseEntityInternalAccess;
import com.haulmont.cuba.core.entity.BaseGenericIdEntity;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.entity.SoftDelete;
import com.haulmont.cuba.core.global.Metadata;
import com.haulmont.cuba.core.global.PersistenceHelper;
import com.haulmont.cuba.core.global.View;
import com.haulmont.cuba.core.global.*;
import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
@ -40,6 +43,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import javax.inject.Inject;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -61,12 +65,21 @@ public class EntityImportExport implements EntityImportExportAPI {
@Inject
protected Metadata metadata;
@Inject
protected DataManager dataManager;
@Inject
protected DynamicAttributesManagerAPI dynamicAttributesManagerAPI;
@Inject
protected PersistenceSecurity persistenceSecurity;
@Inject
protected StoreFactory storeFactory;
@Inject
protected ViewRepository viewRepository;
@Override
public byte[] exportEntitiesToZIP(Collection<? extends Entity> entities, View view) {
return exportEntitiesToZIP(reloadEntities(entities, view));
@ -110,17 +123,14 @@ public class EntityImportExport implements EntityImportExportAPI {
.map(Entity::getId)
.collect(Collectors.toList());
Collection<? extends Entity> result;
try (Transaction tx = persistence.createTransaction()) {
EntityManager em = persistence.getEntityManager();
MetaClass metaClass = metadata.getClassNN(view.getEntityClass());
Query query = em.createQuery("select e from " + metaClass.getName() + " e where e.id in :ids")
.setParameter("ids", ids)
.setView(view);
result = query.getResultList();
tx.commit();
}
return result;
MetaClass metaClass = metadata.getClassNN(view.getEntityClass());
LoadContext.Query query = LoadContext.createQuery("select e from " + metaClass.getName() + " e where e.id in :ids")
.setParameter("ids", ids);
LoadContext<? extends Entity> ctx = LoadContext.create(view.getEntityClass())
.setQuery(query)
.setView(view);
return dataManager.loadList(ctx);
}
protected ArchiveEntry newStoredEntry(String name, byte[] data) {
@ -172,105 +182,103 @@ public class EntityImportExport implements EntityImportExportAPI {
}
@Override
public Collection<Entity> importEntities(Collection<? extends Entity> entities, EntityImportView view) {
Collection<Entity> result = new ArrayList<>();
Map<String, List<Entity>> entitiesByStore = new LinkedHashMap<>();
for (Entity entity : entities) {
String storeName = metadata.getTools().getStoreName(entity.getMetaClass());
List<Entity> list = entitiesByStore.computeIfAbsent(storeName, k -> new ArrayList<>());
list.add(entity);
public Collection<Entity> importEntities(Collection<? extends Entity> entities, EntityImportView importView) {
List<ReferenceInfo> referenceInfoList = new ArrayList<>();
CommitContext commitContext = new CommitContext();
//import is performed in two steps. We have to do so, because imported entity may have a reference to
//the reference that is imported in the same batch.
//
//1. entities that should be persisted are processed first, fields that should be references to existing entities
//are stored in the referenceInfoList variable
for (Entity srcEntity : entities) {
View regularView = buildViewFromImportView(importView);
//set softDeletion to false because we can import deleted entity, so we'll restore it and update
LoadContext<? extends Entity> ctx = LoadContext.create(srcEntity.getClass())
.setSoftDeletion(false)
.setView(regularView)
.setId(srcEntity.getId());
Entity dstEntity = dataManager.load(ctx);
if (dstEntity instanceof BaseGenericIdEntity) {
byte[] securityToken = BaseEntityInternalAccess.getSecurityToken((BaseGenericIdEntity) srcEntity);
BaseEntityInternalAccess.setSecurityToken((BaseGenericIdEntity) dstEntity, securityToken);
}
importEntity(srcEntity, dstEntity, importView, regularView, commitContext, referenceInfoList);
}
for (String storeName : entitiesByStore.keySet()) {
Map<Object, Entity> entitiesToPersist = new HashMap<>();
Set<Entity> entitiesToRemove = new HashSet<>();
List<ReferenceInfo> referenceInfoList = new ArrayList<>();
Map<Object, Entity> loadedEntities = new HashMap<>();
//2. references to existing entities are processed
try (Transaction tx = persistence.getTransaction(storeName)) {
//store a list of loaded entities in the collection to prevent unnecessary database requests for searching the
//same instance
Set<Entity> loadedEntities = new HashSet<>();
for (ReferenceInfo referenceInfo : referenceInfoList) {
processReferenceInfo(referenceInfo, commitContext, loadedEntities);
}
//import is performed in two steps. We have to do so, because imported entity may have a reference to
//the reference that is imported in the same batch.
//
//1. entities that should be created are processing first, fields that should be references to existing entities
//are stored in the referenceInfoList variable
for (Entity entity : entities) {
importEntity(entity, view, storeName, entitiesToPersist, entitiesToRemove, referenceInfoList);
for (Entity commitInstance : commitContext.getCommitInstances()) {
if (!PersistenceHelper.isNew(commitInstance)) {
if (commitInstance instanceof SoftDelete && ((SoftDelete) commitInstance).isDeleted()) {
((SoftDelete) commitInstance).setDeleteTs(null);
}
//2. references to existing entities are processed
for (ReferenceInfo referenceInfo : referenceInfoList) {
processReferenceInfo(referenceInfo, storeName, entitiesToPersist, loadedEntities);
}
EntityManager em = persistence.getEntityManager(storeName);
for (Entity entity : entitiesToPersist.values()) {
if (PersistenceHelper.isNew(entity)) {
em.persist(entity);
result.add(entity);
} else {
if (entity instanceof SoftDelete && ((SoftDelete) entity).isDeleted()) {
((SoftDelete) entity).setDeleteTs(null);
}
Entity merged = em.merge(entity);
result.add(merged);
}
if (entityHasDynamicAttributes(entity)) {
dynamicAttributesManagerAPI.storeDynamicAttributes((BaseGenericIdEntity) entity);
}
}
entitiesToRemove.forEach(em::remove);
tx.commit();
}
if (entityHasDynamicAttributes(commitInstance)) {
dynamicAttributesManagerAPI.storeDynamicAttributes((BaseGenericIdEntity) commitInstance);
}
}
return result;
return dataManager.commit(commitContext);
}
/**
* Method imports the entity.
*
* @param srcEntity entity that came to the {@code EntityImportExport} bean
* @param dstEntity reloaded srcEntity or null if entity doesn't exist in the database
* @param importView importView used for importing the entity
* @param regularView view that was used for loading dstEntity
* @param commitContext entities that must be commited or deleted will be set to the commitContext
* @param referenceInfoList list of referenceInfos for further processing
* @return dstEntity that has fields values from the srcEntity
*/
protected Entity importEntity(Entity srcEntity,
EntityImportView view,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
@Nullable Entity dstEntity,
EntityImportView importView,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
EntityManager em = persistence.getEntityManager(storeName);
//set softDeletion to false because we can import deleted entity, so we'll restore it and update
em.setSoftDeletion(false);
Entity dstEntity = em.reload(srcEntity);
if (dstEntity instanceof BaseGenericIdEntity) {
byte[] securityToken = BaseEntityInternalAccess.getSecurityToken((BaseGenericIdEntity) srcEntity);
BaseEntityInternalAccess.setSecurityToken((BaseGenericIdEntity) dstEntity, securityToken);
}
MetaClass metaClass = srcEntity.getMetaClass();
if (dstEntity == null) {
dstEntity = metadata.create(metaClass);
dstEntity.setValue("id", srcEntity.getId());
}
entitiesToCreate.put(dstEntity.getId(), dstEntity);
//we must specify a view here because otherwise we may get UnfetchedAttributeException during merge
commitContext.addInstanceToCommit(dstEntity, regularView);
for (EntityImportViewProperty viewProperty : view.getProperties()) {
MetaProperty metaProperty = metaClass.getPropertyNN(viewProperty.getName());
for (EntityImportViewProperty importViewProperty : importView.getProperties()) {
String propertyName = importViewProperty.getName();
MetaProperty metaProperty = metaClass.getPropertyNN(propertyName);
if ((metaProperty.getRange().isDatatype() && !"version".equals(metaProperty.getName())) || metaProperty.getRange().isEnum()) {
dstEntity.setValue(viewProperty.getName(), srcEntity.getValue(viewProperty.getName()));
dstEntity.setValue(propertyName, srcEntity.getValue(propertyName));
} else if (metaProperty.getRange().isClass()) {
View regularPropertyView = regularView.getProperty(propertyName) != null ? regularView.getProperty(propertyName).getView() : null;
if (metadata.getTools().isEmbedded(metaProperty)) {
if (viewProperty.getView() != null) {
Entity embeddedEntity = importEmbeddedAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
dstEntity.setValue(viewProperty.getName(), embeddedEntity);
if (importViewProperty.getView() != null) {
Entity embeddedEntity = importEmbeddedAttribute(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
dstEntity.setValue(propertyName, embeddedEntity);
}
} else {
switch (metaProperty.getRange().getCardinality()) {
case MANY_TO_MANY:
importManyToManyCollectionAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
importManyToManyCollectionAttribute(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
break;
case ONE_TO_MANY:
importOneToManyCollectionAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
importOneToManyCollectionAttribute(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
break;
default:
importReference(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
importReference(srcEntity, dstEntity, importViewProperty, regularPropertyView, commitContext, referenceInfoList);
}
}
}
@ -289,44 +297,55 @@ public class EntityImportExport implements EntityImportExportAPI {
protected void importReference(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove, Collection<ReferenceInfo> referenceInfoList) {
Entity srcPropertyValue = srcEntity.<Entity>getValue(viewProperty.getName());
if (viewProperty.getView() == null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, viewProperty, srcPropertyValue);
EntityImportViewProperty importViewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
Entity srcPropertyValue = srcEntity.<Entity>getValue(importViewProperty.getName());
Entity dstPropertyValue = dstEntity.<Entity>getValue(importViewProperty.getName());
if (importViewProperty.getView() == null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, importViewProperty, srcPropertyValue);
referenceInfoList.add(referenceInfo);
} else {
Entity dstPropertyValue = importEntity(srcPropertyValue, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
dstEntity.setValue(viewProperty.getName(), dstPropertyValue);
dstPropertyValue = importEntity(srcPropertyValue, dstPropertyValue, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
dstEntity.setValue(importViewProperty.getName(), dstPropertyValue);
}
}
protected void importOneToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
EntityImportViewProperty importViewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
String propertyName = importViewProperty.getName();
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(propertyName);
MetaProperty inverseMetaProperty = metaProperty.getInverse();
//filteredItems collection will contain entities filtered by the row-level security
Multimap<String, UUID> filteredItems = ArrayListMultimap.create();
if (srcEntity instanceof BaseGenericIdEntity) {
//create an entity copy here, because filtered items must not be reloaded in the srcEntity for now,
//we only need a collection of filtered properties
byte[] securityToken = BaseEntityInternalAccess.getSecurityToken((BaseGenericIdEntity) srcEntity);
Entity srcEntityCopy = metadata.getTools().deepCopy(srcEntity);
BaseEntityInternalAccess.setSecurityToken((BaseGenericIdEntity) srcEntityCopy, securityToken);
persistenceSecurity.restoreFilteredData((BaseGenericIdEntity<?>) srcEntityCopy);
filteredItems = BaseEntityInternalAccess.getFilteredData((BaseGenericIdEntity) srcEntityCopy);
String storeName = metadata.getTools().getStoreName(srcEntity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
//row-level security works only for entities from RdbmsStore
if (dataStore instanceof RdbmsStore) {
//create an entity copy here, because filtered items must not be reloaded in the srcEntity for now,
//we only need a collection of filtered properties
try (Transaction tx = persistence.getTransaction()) {
byte[] securityToken = BaseEntityInternalAccess.getSecurityToken((BaseGenericIdEntity) srcEntity);
Entity srcEntityCopy = metadata.getTools().deepCopy(srcEntity);
BaseEntityInternalAccess.setSecurityToken((BaseGenericIdEntity) srcEntityCopy, securityToken);
persistenceSecurity.restoreFilteredData((BaseGenericIdEntity<?>) srcEntityCopy);
filteredItems = BaseEntityInternalAccess.getFilteredData((BaseGenericIdEntity) srcEntityCopy);
tx.commit();
}
}
}
Collection<Entity> srcPropertyValue = srcEntity.getValue(viewProperty.getName());
Collection<Entity> srcPropertyValue = srcEntity.getValue(propertyName);
Collection<Entity> dstPropertyValue = dstEntity.getValue(propertyName);
if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>();
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
@ -336,9 +355,16 @@ public class EntityImportExport implements EntityImportExportAPI {
if (srcPropertyValue != null) {
for (Entity srcChildEntity : srcPropertyValue) {
if (viewProperty.getView() != null) {
if (importViewProperty.getView() != null) {
//create new referenced entity
Entity dstChildEntity = importEntity(srcChildEntity, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
Entity dstChildEntity = null;
for (Entity _entity : dstPropertyValue) {
if (_entity.equals(srcChildEntity)) {
dstChildEntity = _entity;
break;
}
}
dstChildEntity = importEntity(srcChildEntity, dstChildEntity, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
if (inverseMetaProperty != null) {
dstChildEntity.setValue(inverseMetaProperty.getName(), dstEntity);
}
@ -347,30 +373,33 @@ public class EntityImportExport implements EntityImportExportAPI {
}
}
if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) {
Collection<? extends Entity> dstValue = dstEntity.getValue(viewProperty.getName());
if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) {
Collection<? extends Entity> dstValue = dstEntity.getValue(propertyName);
if (dstValue != null) {
Multimap<String, UUID> finalFilteredItems = filteredItems;
List<? extends Entity> collectionItemsToRemove = dstValue.stream()
.filter(entity -> !collection.contains(entity) &&
(finalFilteredItems == null || !finalFilteredItems.containsValue(entity.getId())))
.collect(Collectors.toList());
entitiesToRemove.addAll(collectionItemsToRemove);
for (Entity _entity : collectionItemsToRemove) {
commitContext.addInstanceToRemove(_entity);
}
}
}
dstEntity.setValue(viewProperty.getName(), collection);
dstEntity.setValue(propertyName, collection);
}
protected void importManyToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
EntityImportViewProperty importViewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
Collection<Entity> srcPropertyValue = srcEntity.getValue(viewProperty.getName());
if (viewProperty.getView() != null) {
Collection<Entity> srcPropertyValue = srcEntity.getValue(importViewProperty.getName());
Collection<Entity> dstPropertyValue = dstEntity.getValue(importViewProperty.getName());
if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>();
if (importViewProperty.getView() != null) {
//create/update passed entities
Collection<Entity> collection;
try {
@ -381,12 +410,19 @@ public class EntityImportExport implements EntityImportExportAPI {
for (Entity srcChildEntity : srcPropertyValue) {
//create new referenced entity
Entity dstChildEntity = importEntity(srcChildEntity, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
Entity dstChildEntity = null;
for (Entity _entity : dstPropertyValue) {
if (_entity.equals(srcChildEntity)) {
dstChildEntity = _entity;
break;
}
}
dstChildEntity = importEntity(srcChildEntity, dstChildEntity, importViewProperty.getView(), regularView, commitContext, referenceInfoList);
collection.add(dstChildEntity);
}
if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> existingCollectionValue = dstEntity.getValue(viewProperty.getName());
if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> existingCollectionValue = dstEntity.getValue(importViewProperty.getName());
if (existingCollectionValue != null) {
for (Entity existingCollectionItem : existingCollectionValue) {
if (!collection.contains(existingCollectionItem)) collection.add(existingCollectionItem);
@ -394,12 +430,12 @@ public class EntityImportExport implements EntityImportExportAPI {
}
}
dstEntity.setValue(viewProperty.getName(), collection);
dstEntity.setValue(importViewProperty.getName(), collection);
} else {
//create ReferenceInfo objects - they will be parsed later
Collection<Entity> existingCollectionValue = dstEntity.getValue(viewProperty.getName());
Collection<Entity> existingCollectionValue = dstEntity.getValue(importViewProperty.getName());
if (existingCollectionValue != null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, viewProperty, srcPropertyValue, existingCollectionValue);
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, importViewProperty, srcPropertyValue, existingCollectionValue);
referenceInfoList.add(referenceInfo);
}
}
@ -407,34 +443,34 @@ public class EntityImportExport implements EntityImportExportAPI {
protected Entity importEmbeddedAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
EntityImportViewProperty importViewProperty,
View regularView,
CommitContext commitContext,
Collection<ReferenceInfo> referenceInfoList) {
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
Entity srcEmbeddedEntity = srcEntity.getValue(viewProperty.getName());
String propertyName = importViewProperty.getName();
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(propertyName);
Entity srcEmbeddedEntity = srcEntity.getValue(propertyName);
if (srcEmbeddedEntity == null) {
return null;
}
Entity dstEmbeddedEntity = dstEntity.getValue(viewProperty.getName());
Entity dstEmbeddedEntity = dstEntity.getValue(propertyName);
MetaClass embeddedAttrMetaClass = metaProperty.getRange().asClass();
if (dstEmbeddedEntity == null) {
dstEmbeddedEntity = metadata.create(embeddedAttrMetaClass);
}
for (EntityImportViewProperty vp : viewProperty.getView().getProperties()) {
for (EntityImportViewProperty vp : importViewProperty.getView().getProperties()) {
MetaProperty mp = embeddedAttrMetaClass.getPropertyNN(vp.getName());
if ((mp.getRange().isDatatype() && !"version".equals(mp.getName())) || mp.getRange().isEnum()) {
dstEmbeddedEntity.setValue(vp.getName(), srcEmbeddedEntity.getValue(vp.getName()));
} else if (mp.getRange().isClass()) {
View propertyRegularView = regularView.getProperty(propertyName) != null ? regularView.getProperty(propertyName).getView() : null;
if (metaProperty.getRange().getCardinality() == Range.Cardinality.ONE_TO_MANY) {
importOneToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
importOneToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, propertyRegularView, commitContext, referenceInfoList);
} else if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_MANY) {
importManyToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
}
else {
importReference(srcEmbeddedEntity, dstEmbeddedEntity, vp, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
importManyToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, propertyRegularView, commitContext, referenceInfoList);
} else {
importReference(srcEmbeddedEntity, dstEmbeddedEntity, vp, propertyRegularView, commitContext, referenceInfoList);
}
}
}
@ -442,15 +478,16 @@ public class EntityImportExport implements EntityImportExportAPI {
return dstEmbeddedEntity;
}
protected void processReferenceInfo(ReferenceInfo referenceInfo, String store, Map<Object, Entity> entitiesToCreate, Map<Object, Entity> loadedEntities) {
/**
* Method finds and set a reference value to the entity or throws EntityImportException if ERROR_ON_MISSING policy
* is violated
*/
protected void processReferenceInfo(ReferenceInfo referenceInfo, CommitContext commitContext, Set<Entity> loadedEntities) {
Entity entity = referenceInfo.getEntity();
String propertyName = referenceInfo.getViewProperty().getName();
MetaProperty metaProperty = entity.getMetaClass().getPropertyNN(propertyName);
if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_MANY) {
Collection<Entity> propertyValue = (Collection<Entity>) referenceInfo.getPropertyValue();
if (propertyValue == null) {
entity.setValue(propertyName, null);
return;
@ -464,21 +501,28 @@ public class EntityImportExport implements EntityImportExportAPI {
}
for (Entity childEntity : propertyValue) {
if (loadedEntities.get(childEntity.getId()) != null) {
collection.add(loadedEntities.get(childEntity.getId()));
} else if (entitiesToCreate.get(childEntity.getId()) != null) {
collection.add(entitiesToCreate.get(childEntity.getId()));
Entity entityFromLoadedEntities = findEntityInCollection(loadedEntities, childEntity);
if (entityFromLoadedEntities != null) {
collection.add(entityFromLoadedEntities);
} else {
EntityManager em = persistence.getEntityManager(store);
Entity loadedReference = em.reload(childEntity);
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + entity.getId() + " is missing");
}
Entity entityFromCommitContext = findEntityInCollection(commitContext.getCommitInstances(), childEntity);
if (entityFromCommitContext != null) {
collection.add(entityFromCommitContext);
} else {
collection.add(loadedReference);
LoadContext<? extends Entity> ctx = LoadContext.create(childEntity.getClass())
.setSoftDeletion(false)
.setView(View.MINIMAL)
.setId(childEntity.getId());
Entity loadedReference = dataManager.load(ctx);
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + entity.getId() + " is missing");
}
} else {
collection.add(loadedReference);
loadedEntities.add(loadedReference);
}
}
loadedEntities.put(entity.getId(), loadedReference);
}
}
@ -496,31 +540,82 @@ public class EntityImportExport implements EntityImportExportAPI {
entity.setValue(propertyName, collection);
//restore filtered data, otherwise they will be lost
persistenceSecurity.restoreFilteredData((BaseGenericIdEntity<?>) entity);
//row-level security works only for entities from RdbmsStore
String storeName = metadata.getTools().getStoreName(entity.getMetaClass());
DataStore dataStore = storeFactory.get(storeName);
if (dataStore instanceof RdbmsStore) {
//restore filtered data, otherwise they will be lost
try (Transaction tx = persistence.getTransaction()) {
persistenceSecurity.restoreFilteredData((BaseGenericIdEntity<?>) entity);
tx.commit();
}
}
//end of many-to-many processing block
} else {
//all other reference types (except many-to-many)
Entity propertyValue = (Entity) referenceInfo.getPropertyValue();
if (propertyValue == null) {
entity.setValue(propertyName, null);
} else if (loadedEntities.get(propertyValue.getId()) != null) {
entity.setValue(propertyName, loadedEntities.get(propertyValue.getId()));
} else if (entitiesToCreate.get(propertyValue.getId()) != null) {
entity.setValue(propertyName, entitiesToCreate.get(propertyValue.getId()));
} else {
EntityManager em = persistence.getEntityManager(store);
Entity loadedReference = em.find(propertyValue.getClass(), propertyValue.getId());
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + propertyValue.getId() + " is missing");
}
Entity entityFromLoadedEntities = findEntityInCollection(loadedEntities, propertyValue);
if (entityFromLoadedEntities != null) {
entity.setValue(propertyName, entityFromLoadedEntities);
} else {
entity.setValue(propertyName, loadedReference);
Entity entityFromCommitContext = findEntityInCollection(commitContext.getCommitInstances(), propertyValue);
if (entityFromCommitContext != null) {
entity.setValue(propertyName, entityFromCommitContext);
} else {
LoadContext<? extends Entity> ctx = LoadContext.create(propertyValue.getClass())
.setId(propertyValue.getId());
dataManager.load(ctx);
Entity loadedReference = dataManager.load(ctx);
if (loadedReference == null) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + propertyValue.getId() + " is missing");
}
} else {
entity.setValue(propertyName, loadedReference);
loadedEntities.add(loadedReference);
}
}
}
loadedEntities.put(propertyValue.getId(), loadedReference);
}
}
}
/**
* Method builds a regular {@link View} from the {@link EntityImportView}. The regular view will include all
* properties defined in the import view.
*/
protected View buildViewFromImportView(EntityImportView importView) {
View regularView = new View(importView.getEntityClass());
MetaClass metaClass = metadata.getClassNN(importView.getEntityClass());
for (EntityImportViewProperty importViewProperty : importView.getProperties()) {
EntityImportView importViewPropertyView = importViewProperty.getView();
if (importViewPropertyView == null) {
MetaProperty metaProperty = metaClass.getPropertyNN(importViewProperty.getName());
if (metaProperty.getRange().isClass()) {
MetaClass propertyMetaClass = metaProperty.getRange().asClass();
regularView.addProperty(importViewProperty.getName(), viewRepository.getView(propertyMetaClass, View.MINIMAL));
} else {
regularView.addProperty(importViewProperty.getName());
}
} else {
regularView.addProperty(importViewProperty.getName(), buildViewFromImportView(importViewPropertyView));
}
}
return regularView;
}
@Nullable
protected Entity findEntityInCollection(Collection<Entity> collection, Entity entity) {
for (Entity entityFromCollection : collection) {
if (entityFromCollection.equals(entity)) return entityFromCollection;
}
return null;
}
protected class ReferenceInfo {
protected Entity entity;
protected EntityImportViewProperty viewProperty;
@ -547,6 +642,7 @@ public class EntityImportExport implements EntityImportExportAPI {
public Object getPrevPropertyValue() {
return prevPropertyValue;
}
public Entity getEntity() {
return entity;
}

View File

@ -373,27 +373,40 @@ public class EntitySerialization implements EntitySerializationAPI {
if (entityLoadInfo != null) {
//fallback to platform version 6.4
pkValue = entityLoadInfo.getId();
} else if (idJsonElement != null) {
} else {
MetaProperty primaryKeyProperty = metadataTools.getPrimaryKeyProperty(resultMetaClass);
if (primaryKeyProperty != null) {
if (metadataTools.hasCompositePrimaryKey(resultMetaClass)) {
MetaClass pkMetaClass = primaryKeyProperty.getRange().asClass();
pkValue = readEntity(idJsonElement.getAsJsonObject(), pkMetaClass);
} else {
String idString = idJsonElement.getAsJsonPrimitive().getAsString();
Datatype idDatatype = Datatypes.getNN(primaryKeyProperty.getJavaType());
try {
pkValue = idDatatype.parse(idString);
if (entity instanceof BaseDbGeneratedIdEntity) {
pkValue = IdProxy.of((Long) pkValue);
JsonPrimitive uuidPrimitive = jsonObject.getAsJsonPrimitive("uuid");
if (uuidPrimitive != null) {
UUID uuid = UUID.fromString(uuidPrimitive.getAsString());
((IdProxy) pkValue).setUuid(uuid);
if (idJsonElement != null) {
if (metadataTools.hasCompositePrimaryKey(resultMetaClass)) {
MetaClass pkMetaClass = primaryKeyProperty.getRange().asClass();
pkValue = readEntity(idJsonElement.getAsJsonObject(), pkMetaClass);
} else {
String idString = idJsonElement.getAsJsonPrimitive().getAsString();
try {
Datatype pkDatatype = Datatypes.getNN(primaryKeyProperty.getJavaType());
pkValue = pkDatatype.parse(idString);
if (entity instanceof BaseDbGeneratedIdEntity) {
pkValue = IdProxy.of((Long) pkValue);
JsonPrimitive uuidPrimitive = jsonObject.getAsJsonPrimitive("uuid");
if (uuidPrimitive != null) {
UUID uuid = UUID.fromString(uuidPrimitive.getAsString());
((IdProxy) pkValue).setUuid(uuid);
}
}
} catch (ParseException e) {
throw new EntitySerializationException(e);
}
}
} else if (!"id".equals(primaryKeyProperty.getName())){
//pk may be in another field, not "id"
JsonElement pkElement = jsonObject.get(primaryKeyProperty.getName());
if (pkElement.isJsonPrimitive()) {
try {
Datatype pkDatatype = Datatypes.getNN(primaryKeyProperty.getJavaType());
pkValue = pkDatatype.parse(pkElement.getAsJsonPrimitive().getAsString());
} catch (ParseException e) {
throw new EntitySerializationException(e);
}
} catch (ParseException e) {
throw new EntitySerializationException(e);
}
}
}
@ -420,7 +433,9 @@ public class EntitySerialization implements EntitySerializationAPI {
if (processedEntity != null) {
entity = processedEntity;
} else {
processedEntities.put(entity.getId(), resultMetaClass, entity);
if (entity.getId() != null) {
processedEntities.put(entity.getId(), resultMetaClass, entity);
}
readFields(jsonObject, entity);
}
return entity;

View File

@ -47,7 +47,11 @@ public class RestControllerExceptionHandler {
@ExceptionHandler(RestAPIException.class)
@ResponseBody
public ResponseEntity<ErrorInfo> handleRestAPIException(RestAPIException e) {
log.info("RestAPIException: {}, {}", e.getMessage(), e.getDetails());
if (e.getCause() == null) {
log.info("RestAPIException: {}, {}", e.getMessage(), e.getDetails());
} else {
log.error("RestAPIException: {}, {}", e.getMessage(), e.getDetails(), e.getCause());
}
ErrorInfo errorInfo = new ErrorInfo(e.getMessage(), e.getDetails());
return new ResponseEntity<>(errorInfo, e.getHttpStatus());
}

View File

@ -18,7 +18,6 @@ package com.haulmont.restapi.service;
import com.google.common.base.Strings;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.client.sys.PersistenceManagerClient;
import com.haulmont.cuba.core.app.importexport.EntityImportException;
import com.haulmont.cuba.core.app.importexport.EntityImportExportService;
@ -47,6 +46,7 @@ import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.lang.reflect.Method;
import java.util.*;
/**
@ -186,7 +186,7 @@ public class EntitiesControllerManager {
try {
entity = entitySerializationAPI.entityFromJson(entityJson, metaClass);
} catch (Exception e) {
throw new RestAPIException("Cannot deserialize an entity from JSON", "", HttpStatus.BAD_REQUEST);
throw new RestAPIException("Cannot deserialize an entity from JSON", "", HttpStatus.BAD_REQUEST, e);
}
Validator validator = beanValidation.getValidator();
@ -202,7 +202,7 @@ public class EntitiesControllerManager {
try {
importedEntities = entityImportExportService.importEntities(Collections.singletonList(entity), entityImportView);
} catch (EntityImportException e) {
throw new RestAPIException("Entity creation failed", e.getMessage(), HttpStatus.BAD_REQUEST);
throw new RestAPIException("Entity creation failed", e.getMessage(), HttpStatus.BAD_REQUEST, e);
}
//if many entities were created (because of @Composition references) we must find the main entity
@ -229,7 +229,7 @@ public class EntitiesControllerManager {
try {
entity = entitySerializationAPI.entityFromJson(entityJson, metaClass);
} catch (Exception e) {
throw new RestAPIException("Cannot deserialize an entity from JSON", "", HttpStatus.BAD_REQUEST);
throw new RestAPIException("Cannot deserialize an entity from JSON", "", HttpStatus.BAD_REQUEST, e);
}
if (entity instanceof BaseGenericIdEntity) {
@ -249,7 +249,7 @@ public class EntitiesControllerManager {
try {
importedEntities = entityImportExportService.importEntities(Collections.singletonList(entity), entityImportView);
} catch (EntityImportException e) {
throw new RestAPIException("Entity update failed", e.getMessage(), HttpStatus.BAD_REQUEST);
throw new RestAPIException("Entity update failed", e.getMessage(), HttpStatus.BAD_REQUEST, e);
}
//there may be multiple entities in importedEntities (because of @Composition references), so we must find
// the main entity that will be returned
@ -270,19 +270,21 @@ public class EntitiesControllerManager {
private Object getIdFromString(String entityId, MetaClass metaClass) {
try {
MetaProperty primaryKeyProperty = metadata.getTools().getPrimaryKeyProperty(metaClass);
Class<?> declaringClass = primaryKeyProperty.getJavaType();
Object id;
if (BaseDbGeneratedIdEntity.class.isAssignableFrom(metaClass.getJavaClass())) {
id = IdProxy.of(Long.valueOf(entityId));
} else if (UUID.class.isAssignableFrom(declaringClass)) {
id = UUID.fromString(entityId);
} else if (Integer.class.isAssignableFrom(declaringClass)) {
id = Integer.valueOf(entityId);
} else if (Long.class.isAssignableFrom(declaringClass)) {
id = Long.valueOf(entityId);
} else {
id = entityId;
Method getIdMethod = metaClass.getJavaClass().getMethod("getId");
Class<?> idClass = getIdMethod.getReturnType();
if (UUID.class.isAssignableFrom(idClass)) {
id = UUID.fromString(entityId);
} else if (Integer.class.isAssignableFrom(idClass)) {
id = Integer.valueOf(entityId);
} else if (Long.class.isAssignableFrom(idClass)) {
id = Long.valueOf(entityId);
} else {
id = entityId;
}
}
return id;
} catch (Exception e) {