PL-8650 Rework EntityImportView API

This commit is contained in:
Maxim Gorbunkov 2017-02-17 12:03:43 +04:00
parent 839f1cdadc
commit 82174d8c9c
9 changed files with 368 additions and 183 deletions

View File

@ -21,6 +21,7 @@ import com.google.common.collect.ArrayListMultimap;
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.app.dynamicattributes.DynamicAttributesManagerAPI;
import com.haulmont.cuba.core.app.serialization.EntitySerializationAPI;
@ -161,7 +162,7 @@ public class EntityImportExport implements EntityImportExportAPI {
}
for (String storeName : entitiesByStore.keySet()) {
Map<Object, Entity> entitiesToCreate = new HashMap<>();
Map<Object, Entity> entitiesToPersist = new HashMap<>();
Set<Entity> entitiesToRemove = new HashSet<>();
List<ReferenceInfo> referenceInfoList = new ArrayList<>();
Map<Object, Entity> loadedEntities = new HashMap<>();
@ -169,20 +170,21 @@ public class EntityImportExport implements EntityImportExportAPI {
try (Transaction tx = persistence.getTransaction(storeName)) {
//import is performed in two steps. We have to do so, because imported entity may have a reference to
//some next imported entity.
//1. entities that should be created processed first, fields that should be references to existing entities
//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, entitiesToCreate, entitiesToRemove, referenceInfoList);
importEntity(entity, view, storeName, entitiesToPersist, entitiesToRemove, referenceInfoList);
}
//2. references to existing entities are processed
for (ReferenceInfo referenceInfo : referenceInfoList) {
processReferenceInfo(referenceInfo, storeName, entitiesToCreate, loadedEntities);
processReferenceInfo(referenceInfo, storeName, entitiesToPersist, loadedEntities);
}
EntityManager em = persistence.getEntityManager(storeName);
for (Entity entity : entitiesToCreate.values()) {
for (Entity entity : entitiesToPersist.values()) {
if (PersistenceHelper.isNew(entity)) {
em.persist(entity);
result.add(entity);
@ -239,10 +241,17 @@ public class EntityImportExport implements EntityImportExportAPI {
Entity embeddedEntity = importEmbeddedAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
dstEntity.setValue(viewProperty.getName(), embeddedEntity);
}
} else if (metaProperty.getRange().getCardinality().isMany()) {
importCollectionAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
} else {
importReference(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
switch (metaProperty.getRange().getCardinality()) {
case MANY_TO_MANY:
importManyToManyCollectionAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
break;
case ONE_TO_MANY:
importOneToManyCollectionAttribute(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
break;
default:
importReference(srcEntity, dstEntity, viewProperty, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
}
}
}
}
@ -258,6 +267,124 @@ public class EntityImportExport implements EntityImportExportAPI {
return entity instanceof BaseGenericIdEntity && ((BaseGenericIdEntity) entity).getDynamicAttributes() != null;
}
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);
referenceInfoList.add(referenceInfo);
} else {
Entity dstPropertyValue = importEntity(srcPropertyValue, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
dstEntity.setValue(viewProperty.getName(), dstPropertyValue);
}
}
protected void importOneToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
Collection<ReferenceInfo> referenceInfoList) {
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
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);
}
Collection<Entity> srcPropertyValue = srcEntity.getValue(viewProperty.getName());
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
if (srcPropertyValue != null) {
for (Entity srcChildEntity : srcPropertyValue) {
if (viewProperty.getView() != null) {
//create new referenced entity
Entity dstChildEntity = importEntity(srcChildEntity, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
if (inverseMetaProperty != null) {
dstChildEntity.setValue(inverseMetaProperty.getName(), dstEntity);
}
collection.add(dstChildEntity);
}
}
}
if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.REMOVE_ABSENT_ITEMS) {
Collection<? extends Entity> dstValue = dstEntity.getValue(viewProperty.getName());
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);
}
}
dstEntity.setValue(viewProperty.getName(), collection);
}
protected void importManyToManyCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
Collection<ReferenceInfo> referenceInfoList) {
Collection<Entity> srcPropertyValue = srcEntity.getValue(viewProperty.getName());
if (viewProperty.getView() != null) {
//create/update passed entities
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
for (Entity srcChildEntity : srcPropertyValue) {
//create new referenced entity
Entity dstChildEntity = importEntity(srcChildEntity, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
collection.add(dstChildEntity);
}
if (viewProperty.getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> existingCollectionValue = dstEntity.getValue(viewProperty.getName());
if (existingCollectionValue != null) {
for (Entity existingCollectionItem : existingCollectionValue) {
if (!collection.contains(existingCollectionItem)) collection.add(existingCollectionItem);
}
}
}
dstEntity.setValue(viewProperty.getName(), collection);
} else {
//create ReferenceInfo objects - they will be parsed later
Collection<Entity> existingCollectionValue = dstEntity.getValue(viewProperty.getName());
if (existingCollectionValue != null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, viewProperty, srcPropertyValue, existingCollectionValue);
referenceInfoList.add(referenceInfo);
}
}
}
protected Entity importEmbeddedAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
@ -281,115 +408,27 @@ public class EntityImportExport implements EntityImportExportAPI {
if ((mp.getRange().isDatatype() && !"version".equals(mp.getName())) || mp.getRange().isEnum()) {
dstEmbeddedEntity.setValue(vp.getName(), srcEmbeddedEntity.getValue(vp.getName()));
} else if (mp.getRange().isClass()) {
if (mp.getRange().getCardinality().isMany()) {
importCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
} else {
if (metaProperty.getRange().getCardinality() == Range.Cardinality.ONE_TO_MANY) {
importOneToManyCollectionAttribute(srcEmbeddedEntity, dstEmbeddedEntity, vp, storeName, entitiesToCreate, entitiesToRemove, 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);
}
}
}
return dstEmbeddedEntity;
}
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.getName(), srcPropertyValue, viewProperty.getReferenceImportBehaviour());
referenceInfoList.add(referenceInfo);
} else {
Entity dstPropertyValue = importEntity(srcPropertyValue, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
dstEntity.setValue(viewProperty.getName(), dstPropertyValue);
}
}
protected void importCollectionAttribute(Entity srcEntity,
Entity dstEntity,
EntityImportViewProperty viewProperty,
String storeName,
Map<Object, Entity> entitiesToCreate,
Set<Entity> entitiesToRemove,
Collection<ReferenceInfo> referenceInfoList) {
MetaProperty metaProperty = srcEntity.getMetaClass().getPropertyNN(viewProperty.getName());
MetaProperty inverseMetaProperty = metaProperty.getInverse();
boolean isComposition = metaProperty.getType() == MetaProperty.Type.COMPOSITION;
// if (srcEntity instanceof BaseGenericIdEntity) {
// persistenceSecurity.restoreFilteredData((BaseGenericIdEntity<?>) srcEntity);
// }
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);
}
Collection<Entity> srcPropertyValue = srcEntity.getValue(viewProperty.getName());
if (srcPropertyValue == null && filteredItems.isEmpty()) {
//remove absent items from the composition collection
if (isComposition) {
Collection<? extends Entity> value = dstEntity.getValue(viewProperty.getName());
if (value != null) {
entitiesToRemove.addAll(value);
}
}
dstEntity.setValue(viewProperty.getName(), null);
return;
}
Collection<Entity> collection;
try {
collection = srcPropertyValue.getClass().newInstance();
} catch (Exception e) {
throw new RuntimeException("Error on import entities", e);
}
for (Entity srcChildEntity : srcPropertyValue) {
if (viewProperty.getView() == null) {
ReferenceInfo referenceInfo = new ReferenceInfo(dstEntity, viewProperty.getName(), srcPropertyValue, viewProperty.getReferenceImportBehaviour());
referenceInfoList.add(referenceInfo);
} else {
//create new referenced entity
Entity dstChildEntity = importEntity(srcChildEntity, viewProperty.getView(), storeName, entitiesToCreate, entitiesToRemove, referenceInfoList);
if (inverseMetaProperty != null) {
dstChildEntity.setValue(inverseMetaProperty.getName(), dstEntity);
}
collection.add(dstChildEntity);
}
}
if (isComposition) {
Collection<? extends Entity> dstValue = dstEntity.getValue(viewProperty.getName());
if (dstValue != null) {
Multimap<String, UUID> finalFilteredItems = filteredItems;
List<? extends Entity> compositionEntitiesToRemove = dstValue.stream()
.filter(entity -> !collection.contains(entity) &&
(finalFilteredItems == null || !finalFilteredItems.containsValue(entity.getId())))
.collect(Collectors.toList());
entitiesToRemove.addAll(compositionEntitiesToRemove);
}
}
dstEntity.setValue(viewProperty.getName(), collection);
}
protected void processReferenceInfo(ReferenceInfo referenceInfo, String store, Map<Object, Entity> entitiesToCreate, Map<Object, Entity> loadedEntities) {
Entity entity = referenceInfo.getEntity();
String propertyName = referenceInfo.getPropertyName();
String propertyName = referenceInfo.getViewProperty().getName();
MetaProperty metaProperty = entity.getMetaClass().getPropertyNN(propertyName);
if (metaProperty.getRange().getCardinality().isMany()) {
if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_MANY) {
Collection<Entity> propertyValue = (Collection<Entity>) referenceInfo.getPropertyValue();
if (propertyValue == null) {
@ -413,7 +452,7 @@ public class EntityImportExport implements EntityImportExportAPI {
EntityManager em = persistence.getEntityManager(store);
Entity loadedReference = em.reload(childEntity);
if (loadedReference == null) {
if (referenceInfo.getImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + entity.getId() + " is missing");
}
} else {
@ -422,6 +461,19 @@ public class EntityImportExport implements EntityImportExportAPI {
loadedEntities.put(entity.getId(), loadedReference);
}
}
//keep absent collection members if we need it
if (referenceInfo.getViewProperty().getCollectionImportPolicy() == CollectionImportPolicy.KEEP_ABSENT_ITEMS) {
Collection<Entity> prevCollectionValue = (Collection<Entity>) referenceInfo.getPrevPropertyValue();
if (prevCollectionValue != null) {
for (Entity prevCollectionItem : prevCollectionValue) {
if (!collection.contains(prevCollectionItem)) {
collection.add(prevCollectionItem);
}
}
}
}
entity.setValue(propertyName, collection);
//restore filtered data, otherwise they will be lost
@ -438,7 +490,7 @@ public class EntityImportExport implements EntityImportExportAPI {
EntityManager em = persistence.getEntityManager(store);
Entity loadedReference = em.find(propertyValue.getClass(), propertyValue.getId());
if (loadedReference == null) {
if (referenceInfo.getImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
if (referenceInfo.getViewProperty().getReferenceImportBehaviour() == ReferenceImportBehaviour.ERROR_ON_MISSING) {
throw new EntityImportException("Referenced entity for property '" + propertyName + "' with id = " + propertyValue.getId() + " is missing");
}
} else {
@ -451,31 +503,36 @@ public class EntityImportExport implements EntityImportExportAPI {
protected class ReferenceInfo {
protected Entity entity;
protected String propertyName;
protected EntityImportViewProperty viewProperty;
protected Object propertyValue;
protected ReferenceImportBehaviour importBehaviour;
protected Object prevPropertyValue;
public ReferenceInfo(Entity entity, String propertyName, Object propertyValue, ReferenceImportBehaviour importBehaviour) {
public ReferenceInfo(Entity entity, EntityImportViewProperty viewProperty, Object propertyValue) {
this.entity = entity;
this.propertyName = propertyName;
this.viewProperty = viewProperty;
this.propertyValue = propertyValue;
this.importBehaviour = importBehaviour;
}
public ReferenceInfo(Entity entity, EntityImportViewProperty viewProperty, Object propertyValue, Object prevPropertyValue) {
this.entity = entity;
this.viewProperty = viewProperty;
this.propertyValue = propertyValue;
this.prevPropertyValue = prevPropertyValue;
}
public EntityImportViewProperty getViewProperty() {
return viewProperty;
}
public Object getPrevPropertyValue() {
return prevPropertyValue;
}
public Entity getEntity() {
return entity;
}
public String getPropertyName() {
return propertyName;
}
public Object getPropertyValue() {
return propertyValue;
}
public ReferenceImportBehaviour getImportBehaviour() {
return importBehaviour;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2008-2017 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.core.app.importexport;
/**
* Enum describes the policy of processing collection members when importing the entity with the {@link EntityImportView}
*/
public enum CollectionImportPolicy {
/**
* Absent collection items will be keep.
*/
KEEP_ABSENT_ITEMS,
/**
* Absent collection items will be removed from the database or excluded from the collection.
*/
REMOVE_ABSENT_ITEMS
}

View File

@ -29,39 +29,42 @@ import java.util.HashMap;
import java.util.Map;
/**
* <p>Class describing how entity fields should be saved during the import performed by
* {@link EntityImportExportService EntityImportExportService}</p>
*
* <p>Only fields that are added as properties to the {@code EntityImportView} will be saved.</p>
*
* <p>For simple entity property the rule is simple: if property name is added to the view, then the property will be saved.</p>
*
* <p>For references to other entities there are three options:</p>
* <ul>
* <li>Create or update the referenced entity/entities. This is useful when you are importing the master entities
* with a collection of its detail entities, like Order and its OrderItems</li>
* <li>Try to find the referenced entity in the database and continue the import process if it is not found</li>
* <li>Try to find the referenced entity in the database and stop the import process if it is not found</li>
* </ul>
*
* <p>Use the {@link #addProperty(String, EntityImportView)} method when you want to save the referenced entity. It will
* be saved according to the {@code EntityImportView} passed as parameter.</p>
*
* <p>The property should be added with the {@link #addProperty(String, ReferenceImportBehaviour)} method if you want
* to find the reference in the database. {@link ReferenceImportBehaviour} enum describes the desired behavior:
* ignore missing reference or throw an exception if it was not found in the database.</p>
*
* <p>You can invoke {@code addProperty} methods in fluent interface style. There are also useful methods like
* {@link #addLocalProperties()}, {@link #addSystemProperties()} or {@link #addProperties(String...)}</p>
*
* <p>Example of creating the EntityImportView object:</p>
* Class describing how entity fields should be saved during the import performed by {@link EntityImportExportService
* EntityImportExportService} <p> Only fields that are added as properties to the {@code EntityImportView} will be
* saved.</p> <p> For local entity property the rule is simple: if property name is added to the view, then the property
* will be saved. Use {@link #addLocalProperty(String)} method for adding local property to the view. <p> For
* <b>many-to-one</b> references there are two possible options: <ul> <li>Create or update the referenced entity. Use
* the {@link #addManyToOneProperty(String, EntityImportView)} method. The referenced entity will be saved according to
* the {@code EntityImportView} passed as parameter</li> <li>Try to find the reference in the database and put it to the
* property value. {@link #addManyToOneProperty(String, ReferenceImportBehaviour)} must be used for this. {@link
* ReferenceImportBehaviour} parameter specifies the behaviour in case when referenced entity is missed in the database:
* missing entity can be ignored or import may fail with an error.</li> </ul>
* <p>
* For <b>one-to-one</b> references behavior is the same as for the many-to-one references. Just use the corresponding
* methods for adding properties to the view: {@link #addOneToOneProperty(String, EntityImportView)} or {@link
* #addOneToOneProperty(String, ReferenceImportBehaviour)}.
* <p>
* For <b>one-to-many</b> references you must specify the {@link EntityImportView} which defines how entities from the
* collection must be saved. The second parameter is the {@link CollectionImportPolicy} which specifies what to do with
* collection items that weren't passed to the import: they can be removed or remained.
* <p>
* For <b>many-to-many</b> references the following things must be defined: <ul> <li>Whether the passed collection
* members must be created/updated or just searched in the database</li> <li>Whether the collection items not passed to
* the import must be removed or remain. Keep in mind that for many-to-many properties missing collection members will be
* removed from the collection only, not from the database</li> </ul>
* <p>
* You can invoke methods for adding view properties in fluent interface style. There are also useful methods like
* {@link #addLocalProperties()}, {@link #addSystemProperties()} or {@link #addProperties(String...)}
* <p>
* Example of creating the EntityImportView object:
* <pre>
* EntityImportView importView = new EntityImportView(Group.class)
* .addLocalProperties()
* .addProperty("constraints", new EntityImportView(Constraint.class).addLocalProperties())
* .addProperty("parent", ReferenceImportBehaviour.IGNORE_MISSING);
* .addOneToManyProperty("constraints",
* new EntityImportView(Constraint.class).addLocalProperties(),
* CollectionImportPolicy.KEEP_ABSENT_ITEMS)
* .addManyToOneProperty("parent", ReferenceImportBehaviour.ERROR_ON_MISSING);
* </pre>
*
*/
public class EntityImportView implements Serializable {
@ -73,20 +76,56 @@ public class EntityImportView implements Serializable {
this.entityClass = entityClass;
}
public EntityImportView addProperty(String name, EntityImportView view) {
public EntityImportView addLocalProperty(String name) {
EntityImportViewProperty property = new EntityImportViewProperty(name);
properties.put(name, property);
return this;
}
public EntityImportView addManyToOneProperty(String name, EntityImportView view) {
EntityImportViewProperty property = new EntityImportViewProperty(name, view);
properties.put(name, property);
return this;
}
public EntityImportView addProperty(String name, ReferenceImportBehaviour referenceImportBehaviour) {
public EntityImportView addManyToOneProperty(String name, ReferenceImportBehaviour referenceImportBehaviour) {
EntityImportViewProperty property = new EntityImportViewProperty(name, referenceImportBehaviour);
properties.put(name, property);
return this;
}
public EntityImportView addProperty(String name) {
EntityImportViewProperty property = new EntityImportViewProperty(name);
public EntityImportView addOneToOneProperty(String name, EntityImportView view) {
EntityImportViewProperty property = new EntityImportViewProperty(name, view);
properties.put(name, property);
return this;
}
public EntityImportView addOneToOneProperty(String name, ReferenceImportBehaviour referenceImportBehaviour) {
EntityImportViewProperty property = new EntityImportViewProperty(name, referenceImportBehaviour);
properties.put(name, property);
return this;
}
public EntityImportView addOneToManyProperty(String name, EntityImportView view, CollectionImportPolicy collectionImportPolicy) {
EntityImportViewProperty property = new EntityImportViewProperty(name, view, collectionImportPolicy);
properties.put(name, property);
return this;
}
public EntityImportView addManyToManyProperty(String name, EntityImportView view, CollectionImportPolicy collectionImportPolicy) {
EntityImportViewProperty property = new EntityImportViewProperty(name, view, collectionImportPolicy);
properties.put(name, property);
return this;
}
public EntityImportView addManyToManyProperty(String name, ReferenceImportBehaviour referenceImportBehaviour, CollectionImportPolicy collectionImportPolicy) {
EntityImportViewProperty property = new EntityImportViewProperty(name, referenceImportBehaviour, collectionImportPolicy);
properties.put(name, property);
return this;
}
public EntityImportView addEmbeddedProperty(String name, EntityImportView view) {
EntityImportViewProperty property = new EntityImportViewProperty(name, view);
properties.put(name, property);
return this;
}
@ -114,7 +153,7 @@ public class EntityImportView implements Serializable {
MetadataTools metadataTools = metadata.getTools();
metaClass.getProperties().stream()
.filter(property -> !property.getRange().isClass() && !metadataTools.isSystem(property))
.forEach(metaProperty -> addProperty(metaProperty.getName()));
.forEach(metaProperty -> addLocalProperty(metaProperty.getName()));
return this;
}
@ -124,13 +163,13 @@ public class EntityImportView implements Serializable {
MetadataTools metadataTools = metadata.getTools();
metaClass.getProperties().stream()
.filter(metadataTools::isSystem)
.forEach(metaProperty -> addProperty(metaProperty.getName()));
.forEach(metaProperty -> addLocalProperty(metaProperty.getName()));
return this;
}
public EntityImportView addProperties(String... names) {
for (String name : names) {
addProperty(name);
addLocalProperty(name);
}
return this;
}

View File

@ -36,7 +36,7 @@ import java.util.Map;
/**
*/
@Component(EntityImportViewBuilderAPI.NAME)
public class EntityImportViewBuilder implements EntityImportViewBuilderAPI{
public class EntityImportViewBuilder implements EntityImportViewBuilderAPI {
@Inject
protected MetadataTools metadataTools;
@ -68,7 +68,7 @@ public class EntityImportViewBuilder implements EntityImportViewBuilderAPI{
Class<?> propertyType = metaProperty.getJavaType();
if (propertyRange.isDatatype() || propertyRange.isEnum()) {
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addProperty(propertyName);
view.addLocalProperty(propertyName);
} else if (propertyRange.isClass()) {
if (Entity.class.isAssignableFrom(propertyType)) {
if (metadataTools.isEmbedded(metaProperty)) {
@ -79,7 +79,7 @@ public class EntityImportViewBuilder implements EntityImportViewBuilderAPI{
}
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName)) {
EntityImportView propertyImportView = buildFromJsonObject(propertyJsonObject.getAsJsonObject(), propertyMetaClass);
view.addProperty(propertyName, propertyImportView);
view.addEmbeddedProperty(propertyName, propertyImportView);
}
} else {
MetaClass propertyMetaClass = metadata.getClass(propertyType);
@ -90,28 +90,40 @@ public class EntityImportViewBuilder implements EntityImportViewBuilderAPI{
}
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName)) {
EntityImportView propertyImportView = buildFromJsonObject(propertyJsonObject.getAsJsonObject(), propertyMetaClass);
view.addProperty(propertyName, propertyImportView);
if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_ONE) {
view.addManyToOneProperty(propertyName, propertyImportView);
} else {
view.addOneToOneProperty(propertyName, propertyImportView);
}
}
} else {
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addProperty(propertyName, ReferenceImportBehaviour.ERROR_ON_MISSING);
if (metaProperty.getRange().getCardinality() == Range.Cardinality.MANY_TO_ONE) {
view.addManyToOneProperty(propertyName, ReferenceImportBehaviour.ERROR_ON_MISSING);
} else {
view.addOneToOneProperty(propertyName, ReferenceImportBehaviour.ERROR_ON_MISSING);
}
}
}
} else if (Collection.class.isAssignableFrom(propertyType)) {
MetaClass propertyMetaClass = metaProperty.getRange().asClass();
if (metaProperty.getType() == MetaProperty.Type.COMPOSITION) {
JsonElement compositionJsonArray = entry.getValue();
if (!compositionJsonArray.isJsonArray()) {
throw new RuntimeException("JsonArray was expected for property " + propertyName);
}
EntityImportView propertyImportView = buildFromJsonArray(compositionJsonArray.getAsJsonArray(), propertyMetaClass);
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addProperty(propertyName, propertyImportView);
} else {
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addProperty(propertyName, ReferenceImportBehaviour.ERROR_ON_MISSING);
switch (metaProperty.getRange().getCardinality()) {
case MANY_TO_MANY:
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addManyToManyProperty(propertyName, ReferenceImportBehaviour.ERROR_ON_MISSING, CollectionImportPolicy.REMOVE_ABSENT_ITEMS);
break;
case ONE_TO_MANY:
if (metaProperty.getType() == MetaProperty.Type.COMPOSITION) {
JsonElement compositionJsonArray = entry.getValue();
if (!compositionJsonArray.isJsonArray()) {
throw new RuntimeException("JsonArray was expected for property " + propertyName);
}
EntityImportView propertyImportView = buildFromJsonArray(compositionJsonArray.getAsJsonArray(), propertyMetaClass);
if (security.isEntityAttrUpdatePermitted(metaClass, propertyName))
view.addOneToManyProperty(propertyName, propertyImportView, CollectionImportPolicy.REMOVE_ABSENT_ITEMS);
}
break;
}
}
}
@ -139,6 +151,7 @@ public class EntityImportViewBuilder implements EntityImportViewBuilderAPI{
EntityImportViewProperty propertyCopy = new EntityImportViewProperty(p.getName());
propertyCopy.setView(p.getView());
propertyCopy.setReferenceImportBehaviour(p.getReferenceImportBehaviour());
propertyCopy.setCollectionImportPolicy(p.getCollectionImportPolicy());
resultView.addProperty(propertyCopy);
});
}

View File

@ -32,7 +32,10 @@ public interface EntityImportViewBuilderAPI {
* All references will be added to the view as a {@link com.haulmont.cuba.core.app.importexport.ReferenceImportBehaviour#ERROR_ON_MISSING}
* behavior. All references that have a @Composition annotation will be added to the view with a property that has a
* {@link com.haulmont.cuba.core.app.importexport.EntityImportViewProperty}. This means that compositions will be
* persisted during the import.
* persisted during the import. Absent collection items will be removed from the database.
* <p>
* For many-to-many association items corresponding entities will be searched in the database. If any of them is
* missing, an error will be thrown. Absent collection members will be excluded from the many-to-many association.
*
* @param json a string that represents a JSON object
* @param metaClass a MetaClass of the entity

View File

@ -27,6 +27,8 @@ public class EntityImportViewProperty implements Serializable {
protected ReferenceImportBehaviour referenceImportBehaviour;
protected CollectionImportPolicy collectionImportPolicy;
public EntityImportViewProperty(String name) {
this.name = name;
}
@ -36,11 +38,23 @@ public class EntityImportViewProperty implements Serializable {
this.view = view;
}
public EntityImportViewProperty(String name, EntityImportView view, CollectionImportPolicy collectionImportPolicy) {
this.name = name;
this.view = view;
this.collectionImportPolicy = collectionImportPolicy;
}
public EntityImportViewProperty(String name, ReferenceImportBehaviour referenceImportBehaviour) {
this.name = name;
this.referenceImportBehaviour = referenceImportBehaviour;
}
public EntityImportViewProperty(String name, ReferenceImportBehaviour referenceImportBehaviour, CollectionImportPolicy collectionImportPolicy) {
this.name = name;
this.referenceImportBehaviour = referenceImportBehaviour;
this.collectionImportPolicy = collectionImportPolicy;
}
public String getName() {
return name;
}
@ -64,4 +78,12 @@ public class EntityImportViewProperty implements Serializable {
public void setReferenceImportBehaviour(ReferenceImportBehaviour referenceImportBehaviour) {
this.referenceImportBehaviour = referenceImportBehaviour;
}
public CollectionImportPolicy getCollectionImportPolicy() {
return collectionImportPolicy;
}
public void setCollectionImportPolicy(CollectionImportPolicy collectionImportPolicy) {
this.collectionImportPolicy = collectionImportPolicy;
}
}

View File

@ -20,8 +20,10 @@ package com.haulmont.cuba.gui.app.core.entityinspector;
import com.haulmont.bali.util.ParamsMap;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.chile.core.model.Range;
import com.haulmont.chile.core.model.Session;
import com.haulmont.cuba.client.ClientConfig;
import com.haulmont.cuba.core.app.importexport.CollectionImportPolicy;
import com.haulmont.cuba.core.app.importexport.EntityImportExportService;
import com.haulmont.cuba.core.app.importexport.EntityImportView;
import com.haulmont.cuba.core.app.importexport.ReferenceImportBehaviour;
@ -401,12 +403,18 @@ public class EntityInspectorBrowse extends AbstractLookup {
switch (metaProperty.getType()) {
case DATATYPE:
case ENUM:
entityImportView.addProperty(metaProperty.getName());
entityImportView.addLocalProperty(metaProperty.getName());
break;
case ASSOCIATION:
case COMPOSITION:
if (!metaProperty.getRange().getCardinality().isMany()) {
entityImportView.addProperty(metaProperty.getName(), ReferenceImportBehaviour.IGNORE_MISSING);
Range.Cardinality cardinality = metaProperty.getRange().getCardinality();
switch (cardinality) {
case MANY_TO_ONE:
entityImportView.addManyToOneProperty(metaProperty.getName(), ReferenceImportBehaviour.IGNORE_MISSING);
break;
case ONE_TO_ONE:
entityImportView.addOneToOneProperty(metaProperty.getName(), ReferenceImportBehaviour.IGNORE_MISSING);
break;
}
break;
default:

View File

@ -18,6 +18,7 @@ package com.haulmont.cuba.gui.app.security.group.browse;
import com.haulmont.bali.util.ParamsMap;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.app.importexport.CollectionImportPolicy;
import com.haulmont.cuba.core.app.importexport.EntityImportExportService;
import com.haulmont.cuba.core.app.importexport.EntityImportView;
import com.haulmont.cuba.core.app.importexport.ReferenceImportBehaviour;
@ -278,12 +279,18 @@ public class GroupBrowser extends AbstractWindow {
protected EntityImportView createGroupsImportView() {
return new EntityImportView(Group.class)
.addLocalProperties()
.addProperty("parent", ReferenceImportBehaviour.ERROR_ON_MISSING)
.addProperty("hierarchyList", new EntityImportView(GroupHierarchy.class)
.addManyToOneProperty("parent", ReferenceImportBehaviour.ERROR_ON_MISSING)
.addOneToManyProperty("hierarchyList",
new EntityImportView(GroupHierarchy.class)
.addLocalProperties()
.addProperty("parent", ReferenceImportBehaviour.ERROR_ON_MISSING))
.addProperty("sessionAttributes", new EntityImportView(SessionAttribute.class).addLocalProperties())
.addProperty("constraints", new EntityImportView(Constraint.class).addLocalProperties());
.addManyToOneProperty("parent", ReferenceImportBehaviour.ERROR_ON_MISSING),
CollectionImportPolicy.REMOVE_ABSENT_ITEMS)
.addOneToManyProperty("sessionAttributes",
new EntityImportView(SessionAttribute.class).addLocalProperties(),
CollectionImportPolicy.REMOVE_ABSENT_ITEMS)
.addOneToManyProperty("constraints",
new EntityImportView(Constraint.class).addLocalProperties(),
CollectionImportPolicy.REMOVE_ABSENT_ITEMS);
}
public void copyGroup() {

View File

@ -16,6 +16,7 @@
*/
package com.haulmont.cuba.gui.app.security.role.browse;
import com.haulmont.cuba.core.app.importexport.CollectionImportPolicy;
import com.haulmont.cuba.core.app.importexport.EntityImportExportService;
import com.haulmont.cuba.core.app.importexport.EntityImportView;
import com.haulmont.cuba.core.entity.Entity;
@ -212,7 +213,9 @@ public class RoleBrowser extends AbstractLookup {
protected EntityImportView createRolesImportView() {
return new EntityImportView(Role.class)
.addLocalProperties()
.addProperty("permissions", new EntityImportView(Permission.class).addLocalProperties());
.addOneToManyProperty("permissions",
new EntityImportView(Permission.class).addLocalProperties(),
CollectionImportPolicy.REMOVE_ABSENT_ITEMS);
}
protected class ExportAction extends ItemTrackingAction {