package org.molgenis.data.importer.emx;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import org.apache.commons.lang3.StringUtils;
import org.molgenis.data.*;
import org.molgenis.data.i18n.LanguageService;
import org.molgenis.data.i18n.model.I18nString;
import org.molgenis.data.i18n.model.I18nStringFactory;
import org.molgenis.data.i18n.model.Language;
import org.molgenis.data.i18n.model.LanguageFactory;
import org.molgenis.data.importer.EntitiesValidationReport;
import org.molgenis.data.importer.MetaDataParser;
import org.molgenis.data.importer.MyEntitiesValidationReport;
import org.molgenis.data.importer.ParsedMetaData;
import org.molgenis.data.meta.AttributeType;
import org.molgenis.data.meta.EntityTypeDependencyResolver;
import org.molgenis.data.meta.SystemEntityType;
import org.molgenis.data.meta.model.*;
import org.molgenis.data.meta.model.Package;
import org.molgenis.data.support.EntityTypeUtils;
import org.molgenis.data.validation.meta.AttributeValidator;
import org.molgenis.data.validation.meta.AttributeValidator.ValidationMode;
import org.molgenis.data.validation.meta.EntityTypeValidator;
import org.molgenis.data.validation.meta.TagValidator;
import org.molgenis.util.EntityUtils;
import java.util.*;
import static com.google.common.collect.ImmutableMap.builder;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Maps.newLinkedHashMap;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import static java.lang.String.format;
import static java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import static org.molgenis.data.DataConverter.toList;
import static org.molgenis.data.i18n.I18nUtils.getLanguageCode;
import static org.molgenis.data.i18n.I18nUtils.isI18n;
import static org.molgenis.data.i18n.model.I18nStringMetaData.I18N_STRING;
import static org.molgenis.data.i18n.model.LanguageMetadata.LANGUAGE;
import static org.molgenis.data.importer.MyEntitiesValidationReport.AttributeState.*;
import static org.molgenis.data.meta.AttributeType.*;
import static org.molgenis.data.meta.model.AttributeMetadata.ATTRIBUTE_META_DATA;
import static org.molgenis.data.meta.model.EntityTypeMetadata.ENTITY_TYPE_META_DATA;
import static org.molgenis.data.meta.model.Package.PACKAGE_SEPARATOR;
import static org.molgenis.data.meta.model.TagMetadata.TAG;
import static org.molgenis.data.support.AttributeUtils.isIdAttributeTypeAllowed;
import static org.molgenis.data.support.EntityTypeUtils.isReferenceType;
import static org.molgenis.data.support.EntityTypeUtils.isStringType;
import static org.molgenis.file.model.FileMetaMetaData.FILE_META;
/**
* Parser for the EMX metadata. This class is stateless, but it passes state between methods using
* {@link IntermediateParseResults}.
*/
public class EmxMetaDataParser implements MetaDataParser
{
// Table names in the emx file
static final String EMX_PACKAGES = "packages";
static final String EMX_ENTITIES = "entities";
static final String EMX_ATTRIBUTES = "attributes";
static final String EMX_TAGS = "tags";
static final String EMX_LANGUAGES = "languages";
static final String EMX_I18NSTRINGS = "i18nstrings";
// Column names in the package sheet
private static final String EMX_PACKAGE_NAME = "name";
private static final String EMX_PACKAGE_DESCRIPTION = "description";
private static final String EMX_PACKAGE_PARENT = "parent";
private static final String EMX_PACKAGE_TAGS = "tags";
private static final String EMX_PACKAGE_LABEL = "label";
// Column names in the entities sheet
private static final String EMX_ENTITIES_NAME = "name";
private static final String EMX_ENTITIES_PACKAGE = "package";
private static final String EMX_ENTITIES_LABEL = "label";
private static final String EMX_ENTITIES_DESCRIPTION = "description";
private static final String EMX_ENTITIES_ABSTRACT = "abstract";
private static final String EMX_ENTITIES_EXTENDS = "extends";
private static final String EMX_ENTITIES_BACKEND = "backend";
private static final String EMX_ENTITIES_TAGS = "tags";
// Column names in the attributes sheet
private static final String EMX_ATTRIBUTES_NAME = "name";
private static final String EMX_ATTRIBUTES_ENTITY = "entity";
private static final String EMX_ATTRIBUTES_REF_ENTITY = "refEntity";
private static final String EMX_ATTRIBUTES_MAPPED_BY = "mappedBy";
private static final String EMX_ATTRIBUTES_DEFAULT_VALUE = "defaultValue";
private static final String EMX_ATTRIBUTES_ID_ATTRIBUTE = "idAttribute";
private static final String EMX_ATTRIBUTES_LOOKUP_ATTRIBUTE = "lookupAttribute";
private static final String EMX_ATTRIBUTES_LABEL_ATTRIBUTE = "labelAttribute";
private static final String EMX_ATTRIBUTES_PART_OF_ATTRIBUTE = "partOfAttribute";
private static final String EMX_ATTRIBUTES_AGGREGATEABLE = "aggregateable";
private static final String EMX_ATTRIBUTES_DATA_TYPE = "dataType";
private static final String EMX_ATTRIBUTES_EXPRESSION = "expression";
private static final String EMX_ATTRIBUTES_NILLABLE = "nillable";
private static final String EMX_ATTRIBUTES_VISIBLE = "visible";
private static final String EMX_ATTRIBUTES_LABEL = "label";
private static final String EMX_ATTRIBUTES_DESCRIPTION = "description";
private static final String EMX_ATTRIBUTES_ENUM_OPTIONS = "enumOptions";
private static final String EMX_ATTRIBUTES_RANGE_MIN = "rangeMin";
private static final String EMX_ATTRIBUTES_RANGE_MAX = "rangeMax";
private static final String EMX_ATTRIBUTES_READ_ONLY = "readOnly";
private static final String EMX_ATTRIBUTES_UNIQUE = "unique";
private static final String EMX_ATTRIBUTES_VALIDATION_EXPRESSION = "validationExpression";
private static final String EMX_ATTRIBUTES_TAGS = "tags";
// NOT YET SUPPORTED
// private static final String EMX_ATTRIBUTES_AUTO = "auto";
// private static final String EMX_ATTRIBUTES_VISIBLE_EXPRESSION = "visibleExpression";
// Column names in the tag sheet
static final String EMX_TAG_IDENTIFIER = "identifier";
static final String EMX_TAG_OBJECT_IRI = "objectIRI";
static final String EMX_TAG_LABEL = "label";
static final String EMX_TAG_RELATION_LABEL = "relationLabel";
static final String EMX_TAG_CODE_SYSTEM = "codeSystem";
static final String EMX_TAG_RELATION_IRI = "relationIRI";
// Column names in the language sheet
private static final String EMX_LANGUAGE_CODE = "code";
private static final String EMX_LANGUAGE_NAME = "name";
// Column names in the i18nstring sheet
private static final String EMX_I18N_STRING_MSGID = "msgid";
private static final String EMX_I18N_STRING_DESCRIPTION = "description";
private static final Map<String, String> EMX_NAME_TO_REPO_NAME_MAP = newHashMap();
static
{
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_ENTITIES, ENTITY_TYPE_META_DATA);
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_PACKAGES, PackageMetadata.PACKAGE);
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_TAGS, TAG);
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_ATTRIBUTES, ATTRIBUTE_META_DATA);
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_LANGUAGES, LANGUAGE);
EMX_NAME_TO_REPO_NAME_MAP.put(EMX_I18NSTRINGS, I18N_STRING);
}
private static final List<String> EMX_ENTITIES_ALLOWED_ATTRS = Arrays
.asList(EMX_ENTITIES_NAME.toLowerCase(), EMX_ENTITIES_PACKAGE.toLowerCase(),
EMX_ENTITIES_LABEL.toLowerCase(), EMX_ENTITIES_DESCRIPTION, EMX_ENTITIES_ABSTRACT.toLowerCase(),
EMX_ENTITIES_EXTENDS.toLowerCase(), EMX_ENTITIES_BACKEND.toLowerCase(),
EMX_ENTITIES_TAGS.toLowerCase());
private static final List<String> SUPPORTED_ATTRIBUTE_ATTRIBUTES = Arrays
.asList(EMX_ATTRIBUTES_AGGREGATEABLE, EMX_ATTRIBUTES_DATA_TYPE, EMX_ATTRIBUTES_DESCRIPTION,
EMX_ATTRIBUTES_ENTITY, EMX_ATTRIBUTES_ENUM_OPTIONS, EMX_ATTRIBUTES_ID_ATTRIBUTE,
EMX_ATTRIBUTES_LABEL, EMX_ATTRIBUTES_LABEL_ATTRIBUTE, EMX_ATTRIBUTES_LOOKUP_ATTRIBUTE,
EMX_ATTRIBUTES_NAME, EMX_ATTRIBUTES_NILLABLE, EMX_ATTRIBUTES_PART_OF_ATTRIBUTE,
EMX_ATTRIBUTES_RANGE_MAX, EMX_ATTRIBUTES_RANGE_MIN, EMX_ATTRIBUTES_READ_ONLY,
EMX_ATTRIBUTES_REF_ENTITY, EMX_ATTRIBUTES_MAPPED_BY, EMX_ATTRIBUTES_VISIBLE, EMX_ATTRIBUTES_UNIQUE,
EMX_ATTRIBUTES_EXPRESSION, EMX_ATTRIBUTES_VALIDATION_EXPRESSION, EMX_ATTRIBUTES_DEFAULT_VALUE,
EMX_ATTRIBUTES_TAGS);
private static final String AUTO = "auto";
private final DataService dataService;
private final PackageFactory packageFactory;
private final AttributeFactory attrMetaFactory;
private final EntityTypeFactory entityTypeFactory;
private final TagFactory tagFactory;
private final LanguageFactory languageFactory;
private final I18nStringFactory i18nStringFactory;
private final EntityTypeValidator entityTypeValidator;
private final AttributeValidator attributeValidator;
private final TagValidator tagValidator;
private final EntityTypeDependencyResolver entityTypeDependencyResolver;
public EmxMetaDataParser(PackageFactory packageFactory, AttributeFactory attrMetaFactory,
EntityTypeFactory entityTypeFactory, EntityTypeDependencyResolver entityTypeDependencyResolver)
{
this.dataService = null;
this.packageFactory = requireNonNull(packageFactory);
this.attrMetaFactory = requireNonNull(attrMetaFactory);
this.entityTypeFactory = requireNonNull(entityTypeFactory);
this.tagFactory = null;
this.languageFactory = null;
this.i18nStringFactory = null;
this.entityTypeValidator = null;
this.attributeValidator = null;
this.tagValidator = null;
this.entityTypeDependencyResolver = requireNonNull(entityTypeDependencyResolver);
}
public EmxMetaDataParser(DataService dataService, PackageFactory packageFactory, AttributeFactory attrMetaFactory,
EntityTypeFactory entityTypeFactory, TagFactory tagFactory, LanguageFactory languageFactory,
I18nStringFactory i18nStringFactory, EntityTypeValidator entityTypeValidator,
AttributeValidator attributeValidator, TagValidator tagValidator,
EntityTypeDependencyResolver entityTypeDependencyResolver)
{
this.dataService = requireNonNull(dataService);
this.packageFactory = requireNonNull(packageFactory);
this.attrMetaFactory = requireNonNull(attrMetaFactory);
this.entityTypeFactory = requireNonNull(entityTypeFactory);
this.tagFactory = requireNonNull(tagFactory);
this.languageFactory = requireNonNull(languageFactory);
this.i18nStringFactory = requireNonNull(i18nStringFactory);
this.entityTypeValidator = requireNonNull(entityTypeValidator);
this.attributeValidator = requireNonNull(attributeValidator);
this.tagValidator = requireNonNull(tagValidator);
this.entityTypeDependencyResolver = requireNonNull(entityTypeDependencyResolver);
}
@Override
//FIXME The source is parsed twice!!! Once by dermineImportableEntities and once by doImport
public ParsedMetaData parse(final RepositoryCollection source, String defaultPackageName)
{
if (source.getRepository(EMX_ATTRIBUTES) != null)
{
IntermediateParseResults intermediateResults = getEntityTypeFromSource(source);
List<EntityType> entities;
if (defaultPackageName == null)
{
entities = intermediateResults.getEntities();
}
else
{
entities = putEntitiesInDefaultPackage(intermediateResults, defaultPackageName);
}
return new ParsedMetaData(entityTypeDependencyResolver.resolve(entities), intermediateResults.getPackages(),
intermediateResults.getTags(), intermediateResults.getLanguages(),
intermediateResults.getI18nStrings());
}
else
{
if (dataService != null)
{
List<EntityType> metadataList = new ArrayList<>();
for (String emxName : source.getEntityNames())
{
String repoName = EMX_NAME_TO_REPO_NAME_MAP.get(emxName);
if (repoName == null) repoName = emxName;
metadataList.add(dataService.getRepository(repoName).getEntityType());
}
IntermediateParseResults intermediateResults = parseTagsSheet(source.getRepository(EMX_TAGS));
parsePackagesSheet(source.getRepository(EMX_PACKAGES), intermediateResults);
if (source.hasRepository(EMX_LANGUAGES))
{
parseLanguages(source.getRepository(EMX_LANGUAGES), intermediateResults);
}
if (source.hasRepository(EMX_I18NSTRINGS))
{
parseI18nStrings(source.getRepository(EMX_I18NSTRINGS), intermediateResults);
}
return new ParsedMetaData(entityTypeDependencyResolver.resolve(metadataList),
intermediateResults.getPackages(), intermediateResults.getTags(),
intermediateResults.getLanguages(), intermediateResults.getI18nStrings());
}
else
{
throw new UnsupportedOperationException();
}
}
}
@Override
public EntitiesValidationReport validate(RepositoryCollection source)
{
MyEntitiesValidationReport report = new MyEntitiesValidationReport();
Map<String, EntityType> metaDataMap = getEntityTypeMap(dataService, source);
return buildValidationReport(source, report, metaDataMap);
}
private ImmutableMap<String, EntityType> getEntityTypeMap(DataService dataService, RepositoryCollection source)
{
// FIXME: So if there is no attribute sheet, we assume it is already in the dataservice?
Repository attributeSourceRepository = source.getRepository(EMX_ATTRIBUTES);
if (attributeSourceRepository != null) return getEntityTypeFromSource(source).getEntityMap();
else return getEntityTypeFromDataService(dataService, source.getEntityNames());
}
private EntitiesValidationReport buildValidationReport(RepositoryCollection source,
MyEntitiesValidationReport report, Map<String, EntityType> metaDataMap)
{
metaDataMap.values().forEach(entityTypeValidator::validate);
metaDataMap.values().stream().map(EntityType::getAllAttributes).forEach(attributes -> attributes.forEach(attr ->
{
attributeValidator.validate(attr, ValidationMode.ADD);
}));
// validate package/entity/attribute tags
metaDataMap.values().stream().map(EntityType::getPackage).filter(Objects::nonNull)
.forEach(package_ -> package_.getTags().forEach(tagValidator::validate));
metaDataMap.values().forEach(entityType -> entityType.getTags().forEach(tagValidator::validate));
metaDataMap.values().stream().map(EntityType::getAllAttributes).forEach(attributes -> attributes.forEach(attr ->
{
attr.getTags().forEach(tagValidator::validate);
}));
report = generateEntityValidationReport(source, report, metaDataMap);
// Add entities without data
for (String entityName : metaDataMap.keySet())
{
if (!report.getSheetsImportable().containsKey(entityName)) report.addEntity(entityName, true);
}
return report;
}
/**
* Parses metadata from a collection of repositories.
*
* @param source the {@link RepositoryCollection} containing the metadata to parse
* @return {@link IntermediateParseResults} containing the parsed metadata
*/
private IntermediateParseResults getEntityTypeFromSource(RepositoryCollection source)
{
// TODO: this task is actually a 'merge' instead of 'import'
// so we need to consider both new metadata and existing ...
IntermediateParseResults intermediateResults = parseTagsSheet(source.getRepository(EMX_TAGS));
parsePackagesSheet(source.getRepository(EMX_PACKAGES), intermediateResults);
parseEntitiesSheet(source.getRepository(EMX_ENTITIES), intermediateResults);
parseAttributesSheet(source.getRepository(EMX_ATTRIBUTES), intermediateResults);
reiterateToMapRefEntity(source.getRepository(EMX_ATTRIBUTES), intermediateResults);
// languages tab
if (source.hasRepository(EMX_LANGUAGES))
parseLanguages(source.getRepository(EMX_LANGUAGES), intermediateResults);
// i18nstrings tab
if (source.hasRepository(EMX_I18NSTRINGS))
parseI18nStrings(source.getRepository(EMX_I18NSTRINGS), intermediateResults);
postProcessMetadata(intermediateResults);
return intermediateResults;
}
/**
* Post process metadata:
* - Select label attributes that are not defined in EMX
* - Select lookup attributes that are not defined in EMX
*
* @param intermediateResults intermediate parse results
*/
private static void postProcessMetadata(IntermediateParseResults intermediateResults)
{
intermediateResults.getEntities().forEach(entityType ->
{
// in case no label attribute was defined:
// try to set own id attribute or another own attribute as label attribute
if (entityType.getLabelAttribute() == null)
{
Attribute ownIdAttr = entityType.getOwnIdAttribute();
if (ownIdAttr != null && ownIdAttr.isVisible())
{
ownIdAttr.setLabelAttribute(true);
}
else
{
// select the first required visible owned attribute as label attribute
for (Attribute ownAttr : entityType.getOwnAtomicAttributes())
{
if (!ownAttr.isNillable() && ownAttr.isVisible() && !EntityTypeUtils.isReferenceType(ownAttr))
{
ownAttr.setLabelAttribute(true);
break;
}
}
}
}
// in case no lookup attributes were defined:
// try to set own id attribute and own label attribute as lookup attributes
if (Iterables.size(entityType.getLookupAttributes()) == 0)
{
int lookupAttrIdx = 0;
Attribute ownIdAttr = entityType.getOwnIdAttribute();
if (ownIdAttr != null && ownIdAttr.isVisible())
{
ownIdAttr.setLookupAttributeIndex(lookupAttrIdx++);
}
Attribute ownLabelAttr = entityType.getOwnLabelAttribute();
if (ownLabelAttr != null)
{
if (ownIdAttr == null || !ownIdAttr.getName().equals(ownLabelAttr.getName()))
{
ownLabelAttr.setLookupAttributeIndex(lookupAttrIdx);
}
}
}
});
}
/**
* Parses all tags defined in the tags repository.
*
* @param tagRepository the {@link Repository} that contains the tags entity
* @return Map mapping tag Identifier to tag {@link Entity}, will be empty if no tags repository was found
*/
private IntermediateParseResults parseTagsSheet(Repository<Entity> tagRepository)
{
IntermediateParseResults intermediateParseResults = new IntermediateParseResults(entityTypeFactory);
if (tagRepository != null)
{
for (Entity tagEntity : tagRepository)
{
String id = tagEntity.getString(EMX_TAG_IDENTIFIER);
if (id != null)
{
intermediateParseResults.addTag(id, entityToTag(id, tagEntity));
}
}
}
return intermediateParseResults;
}
/**
* Parses the packages sheet
*
* @param repo {@link Repository} for the packages
* @param intermediateResults {@link IntermediateParseResults} containing the parsed tag entities
*/
private void parsePackagesSheet(Repository<Entity> repo, IntermediateParseResults intermediateResults)
{
if (repo == null) return;
// Collect packages
int rowIndex = 1;
for (Entity packageEntity : resolvePackages(repo))
{
rowIndex++;
// Package name is required
String name = packageEntity.getString(EMX_PACKAGE_NAME);
if (name == null) throw new IllegalArgumentException("package.name is missing on line " + rowIndex);
Package package_ = packageFactory.create();
package_.setName(name);
package_.setDescription(packageEntity.getString(EMX_PACKAGE_DESCRIPTION));
package_.setLabel(packageEntity.getString(EMX_PACKAGE_LABEL));
// Set parent package
String parentName = packageEntity.getString(EMX_PACKAGE_PARENT);
if (parentName != null)
{
if (!name.toLowerCase().startsWith(parentName.toLowerCase())) throw new MolgenisDataException(
"Inconsistent package structure. Package: '" + name + "', parent: '" + parentName + '\'');
String simpleName = name.substring(parentName.length() + 1); // subpackage_package
package_.setSimpleName(simpleName);
package_.setParent(intermediateResults.getPackage(parentName));
}
else
{
package_.setSimpleName(name);
}
// Set package tags
List<String> tagIdentifiers = toList(packageEntity.getString(EMX_PACKAGE_TAGS));
if (tagIdentifiers != null && !tagIdentifiers.isEmpty())
{
List<Tag> tags = toTags(intermediateResults, tagIdentifiers);
package_.setTags(tags);
}
// Add the complete package to the parse results
intermediateResults.addPackage(name, package_);
}
}
/**
* Convert tag identifiers to tags
*
* @param intermediateResults
* @param tagIdentifiers
* @return
*/
private static List<Tag> toTags(IntermediateParseResults intermediateResults, List<String> tagIdentifiers)
{
if (tagIdentifiers.isEmpty())
{
return emptyList();
}
List<Tag> tags = new ArrayList<>(tagIdentifiers.size());
for (String tagIdentifier : tagIdentifiers)
{
Tag tag = intermediateResults.getTag(tagIdentifier);
if (tag == null)
{
throw new IllegalArgumentException("Unknown tag '" + tagIdentifier + '\'');
}
tags.add(tag);
}
return tags;
}
/**
* Transforms an {@link Entity} to a {@link Tag}
*
* @param id
* @param tagEntity
* @return
*/
private Tag entityToTag(String id, Entity tagEntity)
{
Tag tag = tagFactory.create(id);
tag.setObjectIri(tagEntity.getString(EMX_TAG_OBJECT_IRI));
tag.setLabel(tagEntity.getString(EMX_TAG_LABEL));
tag.setRelationLabel(tagEntity.getString(EMX_TAG_RELATION_LABEL));
tag.setCodeSystem(tagEntity.getString(EMX_TAG_CODE_SYSTEM));
tag.setRelationIri(tagEntity.getString(EMX_TAG_RELATION_IRI));
return tag;
}
/**
* Load all entities (optional)
*
* @param entitiesRepo the Repository for the entities
* @param intermediateResults {@link IntermediateParseResults} containing the attributes already parsed
*/
private void parseEntitiesSheet(Repository<Entity> entitiesRepo, IntermediateParseResults intermediateResults)
{
if (entitiesRepo != null)
{
for (Attribute attr : entitiesRepo.getEntityType().getAtomicAttributes())
{
if (!EMX_ENTITIES_ALLOWED_ATTRS.contains(attr.getName().toLowerCase()) && !(isI18n(attr.getName()) && (
attr.getName().startsWith(EMX_ENTITIES_DESCRIPTION) || attr.getName()
.startsWith(EMX_ENTITIES_LABEL))))
{
throw new IllegalArgumentException("Unsupported entity metadata: entities." + attr.getName());
}
}
int i = 1;
for (Entity entity : entitiesRepo)
{
i++;
String emxEntityName = entity.getString(EMX_ENTITIES_NAME);
String emxEntityPackage = entity.getString(EMX_ENTITIES_PACKAGE);
String emxEntityLabel = entity.getString(EMX_ENTITIES_LABEL);
String emxEntityDescription = entity.getString(EMX_ENTITIES_DESCRIPTION);
String emxEntityAbstract = entity.getString(EMX_ENTITIES_ABSTRACT);
String emxEntityExtends = entity.getString(EMX_ENTITIES_EXTENDS);
String emxEntityBackend = entity.getString(EMX_ENTITIES_BACKEND);
String emxEntityTags = entity.getString(EMX_ENTITIES_TAGS);
// required
if (emxEntityName == null)
{
throw new IllegalArgumentException("entity.name is missing on line " + i);
}
String entityName;
if (emxEntityPackage != null)
{
entityName = emxEntityPackage + PACKAGE_SEPARATOR + emxEntityName;
}
else
{
entityName = emxEntityName;
}
EntityType entityType = intermediateResults.getEntityType(entityName);
if (entityType == null)
{
entityType = intermediateResults.addEntityType(entityName);
}
if (dataService != null)
{
if (emxEntityBackend != null)
{
if (dataService.getMeta().getBackend(emxEntityBackend) == null)
{
throw new MolgenisDataException("Unknown backend '" + emxEntityBackend + '\'');
}
}
else
{
emxEntityBackend = dataService.getMeta().getDefaultBackend().getName();
}
entityType.setBackend(emxEntityBackend);
}
if (emxEntityPackage != null)
{
Package p = intermediateResults.getPackage(emxEntityPackage);
if (p == null)
{
throw new MolgenisDataException(
"Unknown package: '" + emxEntityPackage + "' for entity '" + emxEntityName
+ "'. Please specify the package on the " + EMX_PACKAGES
+ " sheet and use the fully qualified package and entity names.");
}
entityType.setPackage(p);
}
entityType.setLabel(emxEntityLabel);
entityType.setDescription(emxEntityDescription);
for (String attributeName : entity.getAttributeNames())
{
if (isI18n(attributeName))
{
if (attributeName.startsWith(EMX_ENTITIES_DESCRIPTION))
{
String description = entity.getString(attributeName);
if (description != null)
{
String languageCode = getLanguageCode(attributeName);
entityType.setDescription(languageCode, description);
}
}
else if (attributeName.startsWith(EMX_ENTITIES_LABEL))
{
String label = entity.getString(attributeName);
if (label != null)
{
String languageCode = getLanguageCode(attributeName);
entityType.setLabel(languageCode, label);
}
}
}
}
if (emxEntityAbstract != null)
{
entityType.setAbstract(parseBoolean(emxEntityAbstract, i, EMX_ENTITIES_ABSTRACT));
}
if (emxEntityExtends != null)
{
EntityType extendsEntityType = null;
if (intermediateResults.knowsEntity(emxEntityExtends))
{
extendsEntityType = intermediateResults.getEntityType(emxEntityExtends);
}
else
{
if (dataService != null)
{
extendsEntityType = dataService.getMeta().getEntityType(emxEntityExtends);
}
}
if (extendsEntityType == null)
{
throw new MolgenisDataException(
"Missing super entity " + emxEntityExtends + " for entity " + emxEntityName
+ " on line " + i);
}
entityType.setExtends(extendsEntityType);
}
List<String> tagIdentifiers = toList(emxEntityTags);
if (tagIdentifiers != null && !tagIdentifiers.isEmpty())
{
entityType.setTags(toTags(intermediateResults, tagIdentifiers));
}
}
}
}
/**
* Resolves package fullNames by looping through all the packages and their parents
*
* @param packageRepo
* @return
*/
private static List<Entity> resolvePackages(Repository<Entity> packageRepo)
{
List<Entity> resolved = new ArrayList<>();
if ((packageRepo == null) || Iterables.isEmpty(packageRepo)) return resolved;
List<Entity> unresolved = new ArrayList<>();
Map<String, Entity> resolvedByName = new HashMap<>();
for (Entity pack : packageRepo)
{
String name = pack.getString(PackageMetadata.SIMPLE_NAME);
String parentName = pack.getString(PackageMetadata.PARENT);
if (parentName == null)
{
resolved.add(pack);
resolvedByName.put(name, pack);
}
else
{
unresolved.add(pack);
}
}
if (resolved.isEmpty()) throw new IllegalArgumentException(
"Missing root package. There must be at least one package without a parent.");
List<Entity> ready = new ArrayList<>();
while (!unresolved.isEmpty())
{
for (Entity pack : unresolved)
{
Entity parent = resolvedByName.get(pack.getString(PackageMetadata.PARENT));
if (parent != null)
{
String name = pack.getString(PackageMetadata.SIMPLE_NAME);
ready.add(pack);
resolvedByName.put(name, pack);
}
}
if (ready.isEmpty())
throw new IllegalArgumentException("Could not resolve packages. Is there a circular reference?");
resolved.addAll(ready);
unresolved.removeAll(ready);
ready.clear();
}
return resolved;
}
/**
* Load all attributes from the source repository and add it to the {@link IntermediateParseResults}.
*
* @param attributesRepo Repository for the attributes
* @param intermediateResults {@link IntermediateParseResults} with the tags already parsed
*/
private void parseAttributesSheet(Repository<Entity> attributesRepo, IntermediateParseResults intermediateResults)
{
for (Attribute attr : attributesRepo.getEntityType().getAtomicAttributes())
{
if (!SUPPORTED_ATTRIBUTE_ATTRIBUTES.contains(attr.getName()) && !((isI18n(attr.getName()) && (
attr.getName().startsWith(EMX_ATTRIBUTES_LABEL) || attr.getName()
.startsWith(EMX_ATTRIBUTES_DESCRIPTION)))))
{
// check if casing mismatch
SUPPORTED_ATTRIBUTE_ATTRIBUTES.forEach(emxAttrMetaAttr ->
{
if (emxAttrMetaAttr.equalsIgnoreCase(attr.getName()))
{
throw new IllegalArgumentException(String.format(
"Unsupported attribute metadata: attributes.%s, did you mean attributes.%s?",
attr.getName(), emxAttrMetaAttr));
}
});
throw new IllegalArgumentException("Unsupported attribute metadata: attributes." + attr.getName());
}
}
Map<String, Map<String, EmxAttribute>> attributesMap = newLinkedHashMap();
// 1st pass: create attribute stubs
int rowIndex = 1;// Header
for (Entity attributeEntity : attributesRepo)
{
rowIndex++;
String attributeName = attributeEntity.getString(EMX_ATTRIBUTES_NAME);
if (attributeName == null)
throw new IllegalArgumentException(format("attributes.name is missing on line [%d]", rowIndex));
String entityName = attributeEntity.getString(EMX_ATTRIBUTES_ENTITY);
if (entityName == null) throw new IllegalArgumentException(
format("attributes.entity is missing for attribute named: %s on line [%d]", attributeName,
rowIndex));
// create attribute
Attribute attribute = attrMetaFactory.create().setName(attributeName);
Map<String, EmxAttribute> entitiesMap = attributesMap.get(entityName);
if (entitiesMap == null)
{
entitiesMap = newLinkedHashMap();
attributesMap.put(entityName, entitiesMap);
}
entitiesMap.put(attributeName, new EmxAttribute(attribute));
}
// 2nd pass: set all properties on attribute stubs except for attribute relations
rowIndex = 1;// Header
for (Entity emxAttrEntity : attributesRepo)
{
rowIndex++;
String emxEntityName = emxAttrEntity.getString(EMX_ATTRIBUTES_ENTITY);
Map<String, EmxAttribute> entityMap = attributesMap.get(emxEntityName);
// If an entity is defined in the attribute sheet only,
// make sure to create EntityType and set the backend
EntityType md = intermediateResults.getEntityType(emxEntityName);
if (md == null)
{
md = intermediateResults.addEntityType(emxEntityName);
if (dataService != null) md.setBackend(dataService.getMeta().getDefaultBackend().getName());
}
String emxName = emxAttrEntity.getString(EMX_ATTRIBUTES_NAME);
EmxAttribute emxAttr = entityMap.get(emxName);
Attribute attr = emxAttr.getAttr();
String emxDataType = emxAttrEntity.getString(EMX_ATTRIBUTES_DATA_TYPE);
String emxRefEntity = emxAttrEntity.getString(EMX_ATTRIBUTES_REF_ENTITY);
if (emxDataType != null)
{
AttributeType type = toEnum(emxDataType);
if (type == null)
{
throw new IllegalArgumentException(
"attributes.dataType error on line " + rowIndex + ": " + emxDataType
+ " unknown data type");
}
attr.setDataType(type);
}
else
{
attr.setDataType(STRING);
}
String emxAttrNillable = emxAttrEntity.getString(EMX_ATTRIBUTES_NILLABLE);
String emxIdAttrValue = emxAttrEntity.getString(EMX_ATTRIBUTES_ID_ATTRIBUTE);
String emxAttrVisible = emxAttrEntity.getString(EMX_ATTRIBUTES_VISIBLE);
String emxAggregatable = emxAttrEntity.getString(EMX_ATTRIBUTES_AGGREGATEABLE);
String emxIsLookupAttr = emxAttrEntity.getString(EMX_ATTRIBUTES_LOOKUP_ATTRIBUTE);
String emxIsLabelAttr = emxAttrEntity.getString(EMX_ATTRIBUTES_LABEL_ATTRIBUTE);
String emxReadOnly = emxAttrEntity.getString(EMX_ATTRIBUTES_READ_ONLY);
String emxUnique = emxAttrEntity.getString(EMX_ATTRIBUTES_UNIQUE);
String expression = emxAttrEntity.getString(EMX_ATTRIBUTES_EXPRESSION);
String validationExpression = emxAttrEntity.getString(EMX_ATTRIBUTES_VALIDATION_EXPRESSION);
String defaultValue = emxAttrEntity.getString(EMX_ATTRIBUTES_DEFAULT_VALUE);
Object emxAttrTags = emxAttrEntity.get(EMX_ENTITIES_TAGS);
List<String> tagIdentifiers = toList(emxAttrTags);
if (tagIdentifiers != null && !tagIdentifiers.isEmpty())
{
attr.setTags(toTags(intermediateResults, tagIdentifiers));
}
if (emxAttrNillable != null)
attr.setNillable(parseBoolean(emxAttrNillable, rowIndex, EMX_ATTRIBUTES_NILLABLE));
if (emxIdAttrValue != null)
{
if (!emxIdAttrValue.equalsIgnoreCase("true") && !emxIdAttrValue.equalsIgnoreCase("false")
&& !emxIdAttrValue.equalsIgnoreCase(AUTO))
{
throw new IllegalArgumentException(
format("Attributes error on line [%d]. Illegal idAttribute value. Allowed values are 'TRUE', 'FALSE' or 'AUTO'",
rowIndex));
}
if (emxIdAttrValue.equalsIgnoreCase("true"))
{
if (!isIdAttributeTypeAllowed(attr))
{
throw new MolgenisDataException("Identifier is of type [" + attr.getDataType()
+ "]. Id attributes can only be of type 'STRING', 'INT' or 'LONG'");
}
}
attr.setAuto(emxIdAttrValue.equalsIgnoreCase(AUTO));
if (!attr.isAuto())
emxAttr.setIdAttr(parseBoolean(emxIdAttrValue, rowIndex, EMX_ATTRIBUTES_ID_ATTRIBUTE));
else emxAttr.setIdAttr(true); // If it is auto, set idAttr to true
}
if (attr.isAuto() && !isStringType(attr))
{
throw new IllegalArgumentException(
format("Attributes error on line [%d]. Auto attributes can only be of data type 'string'",
rowIndex));
}
if (emxAttrVisible != null)
{
if (emxAttrVisible.equalsIgnoreCase("true") || emxAttrVisible.equalsIgnoreCase("false"))
{
attr.setVisible(parseBoolean(emxAttrVisible, rowIndex, EMX_ATTRIBUTES_VISIBLE));
}
else
{
attr.setVisibleExpression(emxAttrVisible);
}
}
if (emxAggregatable != null)
attr.setAggregatable(parseBoolean(emxAggregatable, rowIndex, EMX_ATTRIBUTES_AGGREGATEABLE));
if (emxReadOnly != null) attr.setReadOnly(parseBoolean(emxReadOnly, rowIndex, EMX_ATTRIBUTES_READ_ONLY));
if (emxUnique != null) attr.setUnique(parseBoolean(emxUnique, rowIndex, EMX_ATTRIBUTES_UNIQUE));
if (expression != null) attr.setExpression(expression);
if (validationExpression != null) attr.setValidationExpression(validationExpression);
if (defaultValue != null) attr.setDefaultValue(defaultValue);
if (emxIsLookupAttr != null)
{
boolean isLookAttr = parseBoolean(emxIsLookupAttr, rowIndex, EMX_ATTRIBUTES_LOOKUP_ATTRIBUTE);
if (isLookAttr && isReferenceType(attr))
{
throw new IllegalArgumentException(
format("attributes.lookupAttribute error on line [%d] (%s.%s) lookupAttribute cannot be of type %s",
rowIndex, emxEntityName, emxName, attr.getDataType().toString()));
}
emxAttr.setLookupAttr(isLookAttr);
}
if (emxIsLabelAttr != null)
{
boolean isLabelAttr = parseBoolean(emxIsLabelAttr, rowIndex, EMX_ATTRIBUTES_LABEL_ATTRIBUTE);
if (isLabelAttr && isReferenceType(attr))
{
throw new IllegalArgumentException(
format("attributes.labelAttribute error on line [%d] (%s.%s): labelAttribute cannot be of type %s",
rowIndex, emxEntityName, emxName, attr.getDataType().toString()));
}
emxAttr.setLabelAttr(isLabelAttr);
}
attr.setLabel(emxAttrEntity.getString(EMX_ATTRIBUTES_LABEL));
for (String attrName : emxAttrEntity.getAttributeNames())
{
if (isI18n(attrName))
{
if (attrName.startsWith(EMX_ATTRIBUTES_LABEL))
{
String label = emxAttrEntity.getString(attrName);
if (label != null)
{
String languageCode = getLanguageCode(attrName);
attr.setLabel(languageCode, label);
}
}
else if (attrName.startsWith(EMX_ATTRIBUTES_DESCRIPTION))
{
String description = emxAttrEntity.getString(attrName);
if (description != null)
{
String languageCode = getLanguageCode(attrName);
attr.setDescription(languageCode, description);
}
}
}
}
attr.setDescription(emxAttrEntity.getString(EMX_ATTRIBUTES_DESCRIPTION));
if (attr.getDataType() == ENUM)
{
List<String> enumOptions = DataConverter.toList(emxAttrEntity.get(EMX_ATTRIBUTES_ENUM_OPTIONS));
if (enumOptions == null || enumOptions.isEmpty())
{
throw new IllegalArgumentException(
format("Missing enum options for attribute [%s] of entity [%s]", attr.getName(),
emxEntityName));
}
attr.setEnumOptions(enumOptions);
}
if (attr.getDataType() != FILE)
{
// Only if an attribute is not of type file we apply the normal reference rules
if (isReferenceType(attr) && StringUtils.isEmpty(emxRefEntity))
{
throw new IllegalArgumentException(
format("Missing refEntity on line [%d] (%s.%s)", rowIndex, emxEntityName, emxName));
}
}
if (isReferenceType(attr) && attr.isNillable() && attr.isAggregatable())
{
throw new IllegalArgumentException(
format("attributes.isAggregatable error on line [%d] (%s.%s): isAggregatable nillable attribute cannot be of type %s",
rowIndex, emxEntityName, emxName, attr.getDataType().toString()));
}
String emxRangeMin = emxAttrEntity.getString(EMX_ATTRIBUTES_RANGE_MIN);
Long rangeMin;
if (emxRangeMin != null)
{
try
{
rangeMin = Long.valueOf(emxRangeMin);
}
catch (NumberFormatException e)
{
throw new MolgenisDataException(
format("Invalid range rangeMin [%s] value for attribute [%s] of entity [%s], should be a long",
emxRangeMin, emxName, emxEntityName));
}
}
else
{
rangeMin = null;
}
String emxRangeMax = emxAttrEntity.getString(EMX_ATTRIBUTES_RANGE_MAX);
Long rangeMax;
if (emxRangeMax != null)
{
try
{
rangeMax = Long.valueOf(emxRangeMax);
}
catch (NumberFormatException e)
{
throw new MolgenisDataException(
format("Invalid range rangeMax [%s] value for attribute [%s] of entity [%s], should be a long",
emxRangeMax, emxName, emxEntityName));
}
}
else
{
rangeMax = null;
}
if (rangeMin != null || rangeMax != null)
{
attr.setRange(new Range(rangeMin, rangeMax));
}
}
// 3rd pass: validate and create attribute relationships
Map<String, Set<String>> rootAttributes = newLinkedHashMap();
rowIndex = 1;// Header
for (Entity attributeEntity : attributesRepo)
{
rowIndex++;
String entityName = attributeEntity.getString(EMX_ATTRIBUTES_ENTITY);
Map<String, EmxAttribute> entityMap = attributesMap.get(entityName);
String attributeName = attributeEntity.getString(EMX_ATTRIBUTES_NAME);
Attribute attribute = entityMap.get(attributeName).getAttr();
// bootstrap attribute parent-children relations for compound attributes
String partOfAttribute = attributeEntity.getString(EMX_ATTRIBUTES_PART_OF_ATTRIBUTE);
if (partOfAttribute != null && !partOfAttribute.isEmpty())
{
Attribute compoundAttribute = entityMap.get(partOfAttribute).getAttr();
if (compoundAttribute == null)
{
throw new IllegalArgumentException(
"partOfAttribute [" + partOfAttribute + "] of attribute [" + attributeName + "] of entity ["
+ entityName + "] must refer to an existing compound attribute on line "
+ rowIndex);
}
if (compoundAttribute.getDataType() != COMPOUND)
{
throw new IllegalArgumentException(
"partOfAttribute [" + partOfAttribute + "] of attribute [" + attributeName + "] of entity ["
+ entityName + "] must refer to a attribute of type [" + COMPOUND + "] on line "
+ rowIndex);
}
attribute.setParent(compoundAttribute);
}
Set<String> entityRootAttributes = rootAttributes.get(entityName);
if (entityRootAttributes == null)
{
entityRootAttributes = new LinkedHashSet<>();
rootAttributes.put(entityName, entityRootAttributes);
}
entityRootAttributes.add(attributeName);
}
// store attributes with entities
for (Map.Entry<String, Map<String, EmxAttribute>> entry : attributesMap.entrySet())
{
String entityName = entry.getKey();
Map<String, EmxAttribute> attributes = entry.getValue();
List<EmxAttribute> editableEntityType = newArrayList();
// add root attributes to entity
Set<String> entityAttributeNames = rootAttributes.get(entityName);
if (entityAttributeNames != null)
{
for (EmxAttribute attribute : attributes.values())
{
if (entityAttributeNames.contains(attribute.getAttr().getName()))
{
editableEntityType.add(attribute);
}
}
}
intermediateResults.addAttributes(entityName, editableEntityType);
}
}
/**
* re-iterate to map the mrefs/xref refEntity (or give error if not found)
* TODO consider also those in existing db
*
* @param attributeRepo the attributes {@link Repository}
* @param intermediateResults {@link ParsedMetaData} to add the ref entities to
*/
private void reiterateToMapRefEntity(Repository<Entity> attributeRepo, IntermediateParseResults intermediateResults)
{
int rowIndex = 1;
for (Entity attribute : attributeRepo)
{
final String entityName = attribute.getString(EMX_ATTRIBUTES_ENTITY);
final String attributeName = attribute.getString(EMX_ATTRIBUTES_NAME);
final String refEntityName = (String) attribute.get(EMX_ATTRIBUTES_REF_ENTITY);
final String mappedByAttrName = (String) attribute.get(EMX_ATTRIBUTES_MAPPED_BY);
EntityType EntityType = intermediateResults.getEntityType(entityName);
Attribute Attribute = EntityType.getAttribute(attributeName);
if (Attribute.getDataType().equals(FILE))
{
// If attribute is of type file, set refEntity to file meta and continue to the next attribute
Attribute.setRefEntity(dataService.getEntityType(FILE_META));
continue;
}
rowIndex++;
if (refEntityName != null)
{
if (dataService != null)
{
EntityType refEntityType;
if (intermediateResults.knowsEntity(refEntityName))
{
refEntityType = intermediateResults.getEntityType(refEntityName);
}
else
{
refEntityType = dataService.getEntityType(refEntityName);
if (refEntityType == null)
{
throw new IllegalArgumentException(
"attributes.refEntity error on line " + rowIndex + ": " + refEntityName
+ " unknown");
}
}
Attribute.setRefEntity(refEntityType);
if (mappedByAttrName != null)
{
Attribute mappedByAttr = refEntityType.getAttribute(mappedByAttrName);
if (mappedByAttr == null)
{
throw new IllegalArgumentException(
"attributes.mappedBy error on line " + rowIndex + ": " + mappedByAttrName
+ " unknown");
}
Attribute.setMappedBy(mappedByAttr);
}
}
else
{
Attribute.setRefEntity(intermediateResults.getEntityType(refEntityName));
}
}
}
}
/**
* Put the entities that are not in a package in the selected package
*
* @param intermediateResults
* @param defaultPackageName
* @return
*/
private List<EntityType> putEntitiesInDefaultPackage(IntermediateParseResults intermediateResults,
String defaultPackageName)
{
Package p = getPackage(intermediateResults, defaultPackageName);
if (p == null && dataService != null)
{
throw new IllegalArgumentException(format("Unknown package [%s]", defaultPackageName));
}
List<EntityType> entities = newArrayList();
for (EntityType entityType : intermediateResults.getEntities())
{
if (entityType.getPackage() == null)
{
entityType.setPackage(p);
}
entities.add(entityType);
}
return entities;
}
/**
* Retrieves a {@link Package} by name from parsed data or existing data.
*
* @param intermediateResults parsed data
* @param packageName package name
* @return package or <code>null</code> if no package with the given name exists in parsed or existing data
*/
private Package getPackage(IntermediateParseResults intermediateResults, String packageName)
{
Package package_ = intermediateResults.getPackage(packageName);
if (package_ == null && dataService != null)
{
package_ = dataService.findOneById(PackageMetadata.PACKAGE, packageName, Package.class);
}
return package_;
}
private static ImmutableMap<String, EntityType> getEntityTypeFromDataService(DataService dataService,
Iterable<String> emxEntityNames)
{
ImmutableMap.Builder<String, EntityType> builder = builder();
emxEntityNames.forEach(emxName ->
{
String repoName = EMX_NAME_TO_REPO_NAME_MAP.get(emxName);
if (repoName == null) repoName = emxName;
builder.put(emxName, dataService.getRepository(repoName).getEntityType());
});
return builder.build();
}
/**
* Throws Exception if an import is trying to update metadata of a system entity
*
* @param allEntityTypeMap
* @param existingMetaData
*/
public static void scanMetaDataForSystemEntityType(Map<String, EntityType> allEntityTypeMap,
Iterable<EntityType> existingMetaData)
{
existingMetaData.forEach(emd ->
{
if (!allEntityTypeMap.containsKey(emd.getName())) allEntityTypeMap.put(emd.getName(), emd);
else if ((!EntityUtils.equals(emd, allEntityTypeMap.get(emd.getName()))) && emd instanceof SystemEntityType)
{
throw new MolgenisDataException(
"SystemEntityType in the database conflicts with the metadata for this import");
}
});
}
/**
* Validates whether an EMX value for a boolean attribute is valid and returns the parsed boolean value.
*
* @param booleanString boolean string
* @param rowIndex row index
* @param columnName column name
* @return true or false
* @throws IllegalArgumentException if the given boolean string value is not one of [true, false] (case insensitive)
*/
private static boolean parseBoolean(String booleanString, int rowIndex, String columnName)
{
if (booleanString.equalsIgnoreCase(TRUE.toString())) return true;
else if (booleanString.equalsIgnoreCase(FALSE.toString())) return false;
else throw new IllegalArgumentException(
format("attributes.[%s] error on line [%d]: Invalid value [%s] (expected true or false)",
columnName, rowIndex, booleanString));
}
private void parseLanguages(Repository<Entity> emxLanguageRepo, IntermediateParseResults intermediateParseResults)
{
emxLanguageRepo.forEach(emxLanguageEntity ->
{
Language language = toLanguage(emxLanguageEntity);
intermediateParseResults.addLanguage(language);
});
}
/**
* Creates a language entity from a EMX entity describing a language
*
* @param emxLanguageEntity EMX language entity
* @return language entity
*/
private Language toLanguage(Entity emxLanguageEntity)
{
Language language = languageFactory.create();
language.setCode(emxLanguageEntity.getString(EMX_LANGUAGE_CODE));
language.setName(emxLanguageEntity.getString(EMX_LANGUAGE_NAME));
return language;
}
private void parseI18nStrings(Repository<Entity> emxI18nStringRepo,
IntermediateParseResults intermediateParseResults)
{
emxI18nStringRepo.forEach(emxI18nStringEntity ->
{
I18nString i18nString = toI18nString(emxI18nStringEntity);
intermediateParseResults.addI18nString(i18nString);
});
}
private I18nString toI18nString(Entity emxI18nStringEntity)
{
I18nString i18nString = i18nStringFactory.create();
i18nString.setMessageId(emxI18nStringEntity.getString(EMX_I18N_STRING_MSGID));
i18nString.setDescription(emxI18nStringEntity.getString(EMX_I18N_STRING_DESCRIPTION));
LanguageService.getLanguageCodes().forEach(lang -> i18nString.set(lang, emxI18nStringEntity.getString(lang)));
return i18nString;
}
/**
* Goes through all the sheets in the source EMX and creates an {@link MyEntitiesValidationReport}
*
* @param source
* @param report
* @param metaDataMap
* @return
*/
private MyEntitiesValidationReport generateEntityValidationReport(RepositoryCollection source,
MyEntitiesValidationReport report, Map<String, EntityType> metaDataMap)
{
for (String sheet : source.getEntityNames())
{
if (EMX_PACKAGES.equals(sheet))
{
IntermediateParseResults parseResult = parseTagsSheet(source.getRepository(EMX_TAGS));
parsePackagesSheet(source.getRepository(sheet), parseResult);
parseResult.getPackages().keySet().forEach(report::addPackage);
}
else if (!EMX_ENTITIES.equals(sheet) && !EMX_ATTRIBUTES.equals(sheet) && !EMX_TAGS.equals(sheet)
&& !EMX_LANGUAGES.equals(sheet) && !EMX_I18NSTRINGS.equals(sheet))
{
// check if sheet is known
report = report.addEntity(sheet, metaDataMap.containsKey(sheet));
// check the fields
Repository<Entity> sourceRepository = source.getRepository(sheet);
EntityType target = metaDataMap.get(sheet);
if (target != null)
{
for (Attribute att : sourceRepository.getEntityType().getAttributes())
{
Attribute attribute = target.getAttribute(att.getName());
// Bi-directional one-to-many attributes mapped by another attribute can only be imported
// through the owning side: one-to-many attribute 'books' of entity 'author' mapped by attribute
// 'author' of entity 'book' cannot be imported, only the owning attribute 'book' can be
// imported.
boolean known = attribute != null && attribute.getExpression() == null && !(
attribute.getDataType() == ONE_TO_MANY && attribute.isMappedBy());
report = report.addAttribute(att.getName(), known ? IMPORTABLE : UNKNOWN);
}
for (Attribute att : target.getAttributes())
{
// See comment above regarding import of bi-directional one-to-many data
if (att.getDataType() != COMPOUND && !(att.getDataType() == ONE_TO_MANY && att.isMappedBy()))
{
if (!att.isAuto() && att.getExpression() == null && !report.getFieldsImportable().get(sheet)
.contains(att.getName()))
{
boolean required = !att.isNillable() && !att.isAuto();
report = report.addAttribute(att.getName(), required ? REQUIRED : AVAILABLE);
}
}
}
}
}
}
return report;
}
static class EmxAttribute
{
private final Attribute attr;
private boolean idAttr;
private boolean labelAttr;
private boolean lookupAttr;
public EmxAttribute(Attribute attr)
{
this.attr = requireNonNull(attr);
}
public Attribute getAttr()
{
return attr;
}
public boolean isIdAttr()
{
return idAttr;
}
public void setIdAttr(boolean idAttr)
{
this.idAttr = idAttr;
}
public boolean isLabelAttr()
{
return labelAttr;
}
public void setLabelAttr(boolean labelAttr)
{
this.labelAttr = labelAttr;
}
public boolean isLookupAttr()
{
return lookupAttr;
}
public void setLookupAttr(boolean lookupAttr)
{
this.lookupAttr = lookupAttr;
}
}
}