package com.constellio.app.modules.es.services.mapping; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.constellio.app.entities.schemasDisplay.SchemaDisplayConfig; import com.constellio.app.modules.es.ConstellioESModule; import com.constellio.app.modules.es.connectors.ConnectorServicesFactory; import com.constellio.app.modules.es.connectors.ConnectorUtilsServices; import com.constellio.app.modules.es.connectors.spi.Connector; import com.constellio.app.modules.es.extensions.api.ESModuleExtensions; import com.constellio.app.modules.es.extensions.api.params.TargetMetadataCreationParams; import com.constellio.app.modules.es.model.connectors.ConnectorInstance; import com.constellio.app.modules.es.model.connectors.ConnectorType; import com.constellio.app.modules.es.services.ESSchemasRecordsServices; import com.constellio.app.services.collections.CollectionsManager; import com.constellio.app.services.schemasDisplay.SchemaDisplayManagerTransaction; import com.constellio.app.services.schemasDisplay.SchemasDisplayManager; import com.constellio.model.entities.Language; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchema; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.entities.structures.MapStringListStringStructure; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.schemas.MetadataSchemasManagerException.OptimisticLocking; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.schemas.builders.MetadataBuilder; import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder; public class ConnectorMappingService { private static final String MAPPING_METADATA_PREFIX = "MAP"; private static final String USER_METADATA_PREFIX = MAPPING_METADATA_PREFIX; MetadataSchemasManager metadataSchemasManager; SchemasDisplayManager schemasDisplayManager; RecordServices recordServices; ESSchemasRecordsServices es; ESModuleExtensions extensions; CollectionsManager collectionsManager; public ConnectorMappingService(ESSchemasRecordsServices es) { this.es = es; metadataSchemasManager = es.getModelLayerFactory().getMetadataSchemasManager(); schemasDisplayManager = es.getAppLayerFactory().getMetadataSchemasDisplayManager(); recordServices = es.getModelLayerFactory().newRecordServices(); extensions = es.getAppLayerFactory().getExtensions().forCollection(es.getCollection()).forModule(ConstellioESModule.ID); collectionsManager = es.getAppLayerFactory().getCollectionsManager(); } public List<String> getDocumentTypes(ConnectorInstance<?> instance) { return getConnectorFor(instance).getConnectorDocumentTypes(); } public List<ConnectorField> getConnectorFields(ConnectorInstance<?> instance, String documentType) { ConnectorType connectorType = es.getConnectorType(instance.getConnectorType()); List<ConnectorField> fields = new ArrayList<>(); Set<String> connectorFieldsIds = new HashSet<>(); for (ConnectorField field : connectorType.getDefaultAvailableConnectorFields()) { if (field.getId().startsWith(documentType + ":") && !connectorFieldsIds.contains(field.getId())) { connectorFieldsIds.add(field.getId()); fields.add(field); } } for (ConnectorField field : instance.getAvailableFields()) { if (field.getId().startsWith(documentType + ":") && !connectorFieldsIds.contains(field.getId())) { connectorFieldsIds.add(field.getId()); fields.add(field); } } Collections.sort(fields); return fields; } public List<Metadata> getTargetMetadata(ConnectorInstance<?> instance, String documentType) { MetadataSchema schema = getConnectorDocumentSchema(instance, documentType); List<Metadata> result = new ArrayList<>(); for (Metadata metadata : schema.getMetadatas()) { if (metadata.getLocalCode().startsWith(MAPPING_METADATA_PREFIX)) { result.add(metadata); } } return Collections.unmodifiableList(result); } public boolean canQuickConfig(ConnectorInstance<?> instance, String documentType) { for (List<String> mappings : getMapping(instance, documentType).values()) { if (mappings.size() > 1) { return false; } } return true; } public List<MappingParams> getDefaultMappingParams(ConnectorInstance<?> instance, String documentType) { List<MappingParams> result = new ArrayList<>(); Map<String, Metadata> targets = getTargetMetadataMap(instance, documentType); Set<String> fields = getMappedFields(instance, documentType); for (ConnectorField field : getConnectorFields(instance, documentType)) { String code = cleanMetadataCode(field.getId()); TargetParams params = new TargetParams( code, field.getLabel(), field.getType(), targets.containsKey(MAPPING_METADATA_PREFIX + code)); MappingParams mapping = new MappingParams(field.getId(), params); mapping.setActive(fields.contains(field.getId())); result.add(mapping); } return Collections.unmodifiableList(result); } public Metadata createTargetMetadata(ConnectorInstance<?> instance, String documentType, TargetParams params) { if (instance == null) { throw new ConnectorMappingServiceRuntimeException.ConnectorMappingServiceRuntimeException_InvalidArgument(); } String collection = instance.getCollection(); ConnectorMappingTransaction transaction = createTransaction(collection); String localCode = transaction.createTargetMetadata(instance, documentType, params); transaction.execute(); MetadataSchema schema = getConnectorDocumentSchema(instance, documentType); return schema.getMetadata(localCode); } public Metadata createTargetUserMetadata(MetadataSchema schema, TargetParams params) { if (schema == null) { throw new ConnectorMappingServiceRuntimeException.ConnectorMappingServiceRuntimeException_InvalidArgument(); } String collection = schema.getCollection(); ConnectorMappingTransaction transaction = createTransaction(collection); String localCode = transaction.createTargetUserMetadata(schema, params); transaction.execute(); return metadataSchemasManager.getSchemaTypes(collection).getSchema(schema.getCode()).getMetadata(localCode); } public Map<String, List<String>> getMapping(ConnectorInstance<?> instance, String documentType) { Map<String, List<String>> allDocumentTypesMapping = new HashMap<>(); if (instance.getPropertiesMapping() != null) { allDocumentTypesMapping.putAll(instance.getPropertiesMapping()); } Map<String, List<String>> documentTypeMapping = new HashMap<>(); for (Map.Entry<String, List<String>> mapEntry : allDocumentTypesMapping.entrySet()) { if (mapEntry.getKey().startsWith(documentType + ":")) { String key = mapEntry.getKey().split(":")[1]; documentTypeMapping.put(key, mapEntry.getValue()); } } return documentTypeMapping; } public <T extends ConnectorInstance> ConnectorInstance<T> setMapping( ConnectorInstance<T> instance, String documentType, Map<String, List<String>> mapping) { Map<String, List<String>> allDocumentTypesMapping = new HashMap<>(); recordServices.refresh(instance); if (instance.getPropertiesMapping() != null) { allDocumentTypesMapping.putAll(instance.getPropertiesMapping()); } Set<String> allKeys = new HashSet<>(allDocumentTypesMapping.keySet()); for (String key : allKeys) { if (key.startsWith(documentType + ":")) { allDocumentTypesMapping.remove(key); } } for (Map.Entry<String, List<String>> mappingEntry : mapping.entrySet()) { allDocumentTypesMapping.put(documentType + ":" + mappingEntry.getKey(), mappingEntry.getValue()); } instance.setPropertiesMapping(new MapStringListStringStructure(allDocumentTypesMapping)); try { recordServices.update(instance); } catch (RecordServicesException e) { throw new RuntimeException(e); } return instance; } public <T extends ConnectorInstance> ConnectorInstance<T> setMapping( ConnectorInstance<T> instance, String documentType, List<MappingParams> config) { ConnectorMappingTransaction transaction = createTransaction(instance.getCollection()); Map<String, List<String>> mapping = new HashMap<>(); for (MappingParams params : config) { if (params.isActive()) { String localCode = transaction.createTargetMetadata(instance, documentType, params.getTarget()); mapping.put(localCode, Arrays.asList(params.getFieldId())); } } transaction.execute(); return setMapping(instance, documentType, mapping); } private Set<String> getMappedFields(ConnectorInstance<?> instance, String documentType) { HashSet<String> result = new HashSet<>(); for (List<String> fields : getMapping(instance, documentType).values()) { result.addAll(fields); } return result; } private Map<String, Metadata> getTargetMetadataMap(ConnectorInstance<?> instance, String documentType) { HashMap<String, Metadata> result = new HashMap<>(); for (Metadata metadata : getTargetMetadata(instance, documentType)) { result.put(metadata.getLocalCode(), metadata); } return result; } private String cleanMetadataCode(String code) { String[] parts = code.split(":"); String result = parts[parts.length - 1].replace("_", ""); if (result.toLowerCase().endsWith("id")) { result += "0"; } return result; } private MetadataSchema getConnectorDocumentSchema(ConnectorInstance<?> instance, String documentType) { Connector connector = getConnectorFor(instance); if (!connector.getConnectorDocumentTypes().contains(documentType)) { throw new ConnectorMappingServiceRuntimeException.ConnectorMappingServiceRuntimeException_InvalidArgument(); } MetadataSchemaTypes types = metadataSchemasManager.getSchemaTypes(instance.getCollection()); return types.getSchema(documentType + "_" + instance.getId()); } private Connector getConnectorFor(ConnectorInstance<?> connectorInstance) { return connectorServicesFor(connectorInstance).instantiateConnector(connectorInstance); } private ConnectorUtilsServices connectorServicesFor(ConnectorInstance<?> connectorInstance) { return ConnectorServicesFactory.forConnectorInstance(es.getAppLayerFactory(), connectorInstance); } private ConnectorMappingTransaction createTransaction(String collection) { return new ConnectorMappingTransaction( metadataSchemasManager.modify(collection), new SchemaDisplayManagerTransaction()); } public class ConnectorMappingTransaction { private final MetadataSchemaTypesBuilder types; private final SchemaDisplayManagerTransaction transaction; private final List<Alteration> alterations; public ConnectorMappingTransaction(MetadataSchemaTypesBuilder types, SchemaDisplayManagerTransaction transaction) { this.types = types; this.transaction = transaction; alterations = new ArrayList<>(); } public String createTargetMetadata(ConnectorInstance<?> instance, String documentType, TargetParams params) { if (instance == null || documentType == null || !params.isValid()) { throw new ConnectorMappingServiceRuntimeException.ConnectorMappingServiceRuntimeException_InvalidArgument(); } MetadataSchema schema = getConnectorDocumentSchema(instance, documentType); extensions.beforeTargetMetadataCreation(new TargetMetadataCreationParams(this, params)); return createTargetMetadata(schema, params, ConnectorMappingService.MAPPING_METADATA_PREFIX); } public String createTargetUserMetadata(MetadataSchema schema, TargetParams params) { if (schema == null || !params.isValid()) { throw new ConnectorMappingServiceRuntimeException.ConnectorMappingServiceRuntimeException_InvalidArgument(); } return createTargetMetadata(schema, params, USER_METADATA_PREFIX); } private String createTargetMetadata(MetadataSchema schema, TargetParams params, String prefix) { String code = params.getCode(); if (code.startsWith(prefix)) { code = code.substring(prefix.length()); } String newMetadataLocalCode = prefix + code; if (!params.isExisting()) { if (schema.hasMetadataWithCode(newMetadataLocalCode)) { throw new ConnectorMappingServiceRuntimeException .ConnectorMappingServiceRuntimeException_MetadataAlreadyExist(code); } alterations.add(new Alteration(schema.getCode(), newMetadataLocalCode, params)); } return newMetadataLocalCode; } protected void execute() { for (Alteration alteration : alterations) { MetadataBuilder builder = types.getSchemaType(new SchemaUtils().getSchemaTypeCode(alteration.schema)) .getSchema(alteration.schema) .create(alteration.localCode) .setType(alteration.params.getType()) .setMultivalue(alteration.params.isMultivalue()) .setSearchable(alteration.params.isSearchable()); for (String languageStr : collectionsManager.getCollectionLanguages(es.getCollection())) { builder.addLabel(Language.withCode(languageStr), alteration.params.getLabel()); } } saveSchemas(); for (Alteration alteration : alterations) { transaction.add(schemasDisplayManager.getMetadata(es.getCollection(), alteration.schema, alteration.localCode) .withVisibleInAdvancedSearchStatus(alteration.params.isAdvancedSearch())); if (alteration.params.isSearchResults()) { SchemaDisplayConfig schema = transaction.getModifiedSchema(alteration.schema); if (schema == null) { schema = schemasDisplayManager.getSchema(es.getCollection(), alteration.schema); } transaction.addReplacing(schema.withNewSearchResultMetadataCode( alteration.schema + "_" + alteration.localCode)); } } schemasDisplayManager.execute(transaction); } private void saveSchemas() { try { metadataSchemasManager.saveUpdateSchemaTypes(types); } catch (OptimisticLocking e) { saveSchemas(); } } private class Alteration { private final String schema; private final String localCode; private final TargetParams params; public Alteration(String schema, String localCode, TargetParams params) { this.localCode = localCode; this.schema = schema; this.params = params; } } } }