mirror of
https://gitee.com/jmix/cuba.git
synced 2024-12-04 04:07:42 +08:00
PL-8690 REST API: CRUD operations on non-persistent entities from a custom data store
This commit is contained in:
parent
d22c067a77
commit
865dbef4cf
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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) {
|
||||
|
Loading…
Reference in New Issue
Block a user