diff --git a/modules/core/src/com/haulmont/cuba/core/app/importexport/EntityImportExport.java b/modules/core/src/com/haulmont/cuba/core/app/importexport/EntityImportExport.java index 2363a3f915..67e859dfae 100644 --- a/modules/core/src/com/haulmont/cuba/core/app/importexport/EntityImportExport.java +++ b/modules/core/src/com/haulmont/cuba/core/app/importexport/EntityImportExport.java @@ -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 entities, View view) { return exportEntitiesToZIP(reloadEntities(entities, view)); @@ -110,17 +123,14 @@ public class EntityImportExport implements EntityImportExportAPI { .map(Entity::getId) .collect(Collectors.toList()); - Collection 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 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 importEntities(Collection entities, EntityImportView view) { - Collection result = new ArrayList<>(); - Map> entitiesByStore = new LinkedHashMap<>(); - for (Entity entity : entities) { - String storeName = metadata.getTools().getStoreName(entity.getMetaClass()); - List list = entitiesByStore.computeIfAbsent(storeName, k -> new ArrayList<>()); - list.add(entity); + public Collection importEntities(Collection entities, EntityImportView importView) { + List 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 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 entitiesToPersist = new HashMap<>(); - Set entitiesToRemove = new HashSet<>(); - List referenceInfoList = new ArrayList<>(); - Map 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 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 entitiesToCreate, - Set entitiesToRemove, + @Nullable Entity dstEntity, + EntityImportView importView, + View regularView, + CommitContext commitContext, Collection 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 entitiesToCreate, - Set entitiesToRemove, Collection referenceInfoList) { - Entity srcPropertyValue = srcEntity.getValue(viewProperty.getName()); - if (viewProperty.getView() == null) { - ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, viewProperty, srcPropertyValue); + EntityImportViewProperty importViewProperty, + View regularView, + CommitContext commitContext, + Collection referenceInfoList) { + Entity srcPropertyValue = srcEntity.getValue(importViewProperty.getName()); + Entity dstPropertyValue = dstEntity.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 entitiesToCreate, - Set entitiesToRemove, + EntityImportViewProperty importViewProperty, + View regularView, + CommitContext commitContext, Collection 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 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 srcPropertyValue = srcEntity.getValue(viewProperty.getName()); - + Collection srcPropertyValue = srcEntity.getValue(propertyName); + Collection dstPropertyValue = dstEntity.getValue(propertyName); + if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>(); Collection 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 dstValue = dstEntity.getValue(viewProperty.getName()); + if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) { + Collection dstValue = dstEntity.getValue(propertyName); if (dstValue != null) { Multimap finalFilteredItems = filteredItems; List 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 entitiesToCreate, - Set entitiesToRemove, + EntityImportViewProperty importViewProperty, + View regularView, + CommitContext commitContext, Collection referenceInfoList) { - Collection srcPropertyValue = srcEntity.getValue(viewProperty.getName()); - if (viewProperty.getView() != null) { + Collection srcPropertyValue = srcEntity.getValue(importViewProperty.getName()); + Collection dstPropertyValue = dstEntity.getValue(importViewProperty.getName()); + if (dstPropertyValue == null) dstPropertyValue = new ArrayList<>(); + if (importViewProperty.getView() != null) { //create/update passed entities Collection 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 existingCollectionValue = dstEntity.getValue(viewProperty.getName()); + if (importViewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) { + Collection 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 existingCollectionValue = dstEntity.getValue(viewProperty.getName()); + Collection 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 entitiesToCreate, - Set entitiesToRemove, + EntityImportViewProperty importViewProperty, + View regularView, + CommitContext commitContext, Collection 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 entitiesToCreate, Map 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 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 propertyValue = (Collection) 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 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 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 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; } diff --git a/modules/global/src/com/haulmont/cuba/core/app/serialization/EntitySerialization.java b/modules/global/src/com/haulmont/cuba/core/app/serialization/EntitySerialization.java index 35ee3bf974..9884a174b0 100644 --- a/modules/global/src/com/haulmont/cuba/core/app/serialization/EntitySerialization.java +++ b/modules/global/src/com/haulmont/cuba/core/app/serialization/EntitySerialization.java @@ -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; diff --git a/modules/rest-api/src/com/haulmont/restapi/controllers/RestControllerExceptionHandler.java b/modules/rest-api/src/com/haulmont/restapi/controllers/RestControllerExceptionHandler.java index abd73d4108..11da4c8090 100644 --- a/modules/rest-api/src/com/haulmont/restapi/controllers/RestControllerExceptionHandler.java +++ b/modules/rest-api/src/com/haulmont/restapi/controllers/RestControllerExceptionHandler.java @@ -47,7 +47,11 @@ public class RestControllerExceptionHandler { @ExceptionHandler(RestAPIException.class) @ResponseBody public ResponseEntity 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()); } diff --git a/modules/rest-api/src/com/haulmont/restapi/service/EntitiesControllerManager.java b/modules/rest-api/src/com/haulmont/restapi/service/EntitiesControllerManager.java index 09d670a773..64ac6abf81 100644 --- a/modules/rest-api/src/com/haulmont/restapi/service/EntitiesControllerManager.java +++ b/modules/rest-api/src/com/haulmont/restapi/service/EntitiesControllerManager.java @@ -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) {