package com.constellio.app.services.metadata; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.fromAllSchemasIn; import static java.util.Arrays.asList; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import com.constellio.app.services.factories.AppLayerFactory; import com.constellio.app.services.metadata.AppSchemasServicesRuntimeException.AppSchemasServicesRuntimeException_CannotDeleteSchema; import com.constellio.app.services.schemasDisplay.SchemaTypesDisplayTransactionBuilder; import com.constellio.app.services.schemasDisplay.SchemasDisplayManager; import com.constellio.model.entities.records.ActionExecutorInBatch; import com.constellio.model.entities.records.Record; import com.constellio.model.entities.records.Transaction; 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.schemas.Schemas; import com.constellio.model.services.records.RecordServices; import com.constellio.model.services.records.RecordServicesException; import com.constellio.model.services.schemas.MetadataSchemaTypesAlteration; import com.constellio.model.services.schemas.MetadataSchemasManager; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.schemas.builders.MetadataBuilder; import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.logical.LogicalSearchQuery; public class AppSchemasServices { private final SchemasDisplayManager schemasDisplayManager; private final AppLayerFactory appLayerFactory; private final MetadataSchemasManager schemasManager; private final SearchServices searchServices; private final RecordServices recordServices; public AppSchemasServices(AppLayerFactory appLayerFactory) { this.appLayerFactory = appLayerFactory; this.schemasManager = appLayerFactory.getModelLayerFactory().getMetadataSchemasManager(); this.schemasDisplayManager = appLayerFactory.getMetadataSchemasDisplayManager(); this.searchServices = appLayerFactory.getModelLayerFactory().newSearchServices(); this.recordServices = appLayerFactory.getModelLayerFactory().newRecordServices(); } public boolean isSchemaDeletable(String collection, String schemaCode) { if (schemaCode.endsWith("default")) { return false; } else { List<String> references = getReferencesWithDirectAllowedReference(collection, schemaCode); MetadataSchema schema = schemasManager.getSchemaTypes(collection).getSchema(schemaCode); return references.isEmpty() && !searchServices.hasResults(from(schema).returnAll()); } } private List<String> getReferencesWithDirectAllowedReference(String collection, String schemaCode) { List<String> references = new ArrayList<>(); String schemaType = new SchemaUtils().getSchemaTypeCode(schemaCode); for (Metadata metadata : schemasManager.getSchemaTypes(collection).getAllMetadatas().onlyReferencesToType(schemaType)) { Set<String> allowedSchemas = metadata.getAllowedReferences().getAllowedSchemas(); if (allowedSchemas != null && !allowedSchemas.isEmpty()) { references.add(metadata.getCode()); } } return references; } public void deleteSchemaCode(String collection, String schemaCode) { if (!isSchemaDeletable(collection, schemaCode)) { throw new AppSchemasServicesRuntimeException_CannotDeleteSchema(schemaCode); } updateRecordsWithLinkedSchemas(collection, schemaCode, null); schemasManager.deleteCustomSchemas(asList(schemasManager.getSchemaTypes(collection).getSchema(schemaCode))); } public void modifySchemaCode(String collection, final String fromCode, final String toCode) { MetadataSchemaTypes types = schemasManager.getSchemaTypes(collection); validateModificationAllowed(types, fromCode, toCode); final String schemaTypeCode = new SchemaUtils().getSchemaTypeCode(fromCode); schemasManager.modify(collection, new MetadataSchemaTypesAlteration() { @Override public void alter(MetadataSchemaTypesBuilder types) { types.getSchemaType(schemaTypeCode).createCustomSchemaCopying( new SchemaUtils().getSchemaLocalCode(toCode), new SchemaUtils().getSchemaLocalCode(fromCode)); } }); types = schemasManager.getSchemaTypes(collection); modifyRecords(collection, types.getSchema(fromCode), types.getSchema(toCode)); modifyReferencesWithDirectTarget(types, fromCode, toCode); updateRecordsWithLinkedSchemas(collection, fromCode, toCode); configureNewSchema(collection, fromCode, toCode); schemasManager.deleteCustomSchemas(asList(types.getSchema(fromCode))); } private void updateRecordsWithLinkedSchemas(String collection, String fromCode, String toCode) { List<Record> records = searchServices.search(new LogicalSearchQuery().setCondition( fromAllSchemasIn(collection).where(Schemas.LINKED_SCHEMA).isEqualTo(fromCode))); Transaction transaction = new Transaction(); for (Record record : records) { transaction.add(record.set(Schemas.LINKED_SCHEMA, toCode)); } try { recordServices.execute(transaction); } catch (RecordServicesException e) { throw new RuntimeException(e); } } private void modifyReferencesWithDirectTarget(MetadataSchemaTypes types, final String fromCode, final String toCode) { final List<String> referencesCodeToUpdate = getReferencesWithDirectAllowedReference(types.getCollection(), fromCode); if (!referencesCodeToUpdate.isEmpty()) { schemasManager.modify(types.getCollection(), new MetadataSchemaTypesAlteration() { @Override public void alter(MetadataSchemaTypesBuilder types) { for (String referenceCodeToUpdate : referencesCodeToUpdate) { MetadataBuilder metadataBuilder = types.getMetadata(referenceCodeToUpdate); Set<String> allowedSchemas = new HashSet<String>(metadataBuilder.defineReferences().getSchemas()); allowedSchemas.remove(fromCode); allowedSchemas.add(toCode); metadataBuilder.defineReferences().clearSchemas(); for (String allowedSchema : allowedSchemas) { metadataBuilder.defineReferences().add(types.getSchema(allowedSchema)); } } } }); } } private void modifyRecords(String collection, final MetadataSchema originalSchema, final MetadataSchema destinationSchema) { try { new ActionExecutorInBatch(searchServices, "Modify schema of records", 1000) { @Override public void doActionOnBatch(List<Record> records) throws Exception { Transaction transaction = new Transaction(); transaction.getRecordUpdateOptions().setValidationsEnabled(false); for (Record record : records) { record.changeSchema(originalSchema, destinationSchema); transaction.add(record); } recordServices.execute(transaction); } }.execute(from(originalSchema).returnAll()); } catch (Exception e) { throw new RuntimeException(e); } } private void configureNewSchema(String collection, String fromCode, String toCode) { SchemaTypesDisplayTransactionBuilder transaction = schemasDisplayManager.newTransactionBuilderFor(collection); transaction.add(schemasDisplayManager.getSchema(collection, fromCode).withCode(toCode)); List<String> definedMetadataCodes = schemasDisplayManager.getDefinedMetadatasIn(collection); for (String definedMetadataCode : definedMetadataCodes) { if (definedMetadataCode.startsWith(fromCode)) { transaction.add(schemasDisplayManager.getMetadata(collection, definedMetadataCode) .withCode(definedMetadataCode.replace(fromCode, toCode))); } } schemasDisplayManager.execute(transaction.build()); } private void validateModificationAllowed(MetadataSchemaTypes types, String fromCode, String toCode) { SchemaUtils schemaUtils = new SchemaUtils(); String fromType = schemaUtils.getSchemaTypeCode(fromCode); String toType = schemaUtils.getSchemaTypeCode(toCode); if (!fromType.equals(toType)) { throw new AppSchemasServicesRuntimeException.AppSchemasServicesRuntimeException_CannotChangeCodeToOtherSchemaType(); } if (fromCode.endsWith("_default") || toCode.endsWith("_default")) { throw new AppSchemasServicesRuntimeException.AppSchemasServicesRuntimeException_CannotChangeCodeFromOrToDefault(); } } }