package com.constellio.model.services.schemas;
import static com.constellio.model.services.schemas.xml.MetadataSchemaXMLWriter3.FORMAT_ATTRIBUTE;
import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.jdom2.Document;
import org.jdom2.Element;
import com.constellio.data.dao.managers.StatefulService;
import com.constellio.data.dao.managers.config.ConfigManager;
import com.constellio.data.dao.managers.config.ConfigManagerException.OptimisticLockingConfiguration;
import com.constellio.data.dao.managers.config.ConfigManagerRuntimeException.NoSuchConfiguration;
import com.constellio.data.dao.managers.config.DocumentAlteration;
import com.constellio.data.dao.managers.config.events.ConfigEventListener;
import com.constellio.data.dao.managers.config.values.XMLConfiguration;
import com.constellio.data.dao.services.DataStoreTypesFactory;
import com.constellio.data.utils.Delayed;
import com.constellio.data.utils.ImpossibleRuntimeException;
import com.constellio.model.entities.CollectionObject;
import com.constellio.model.entities.batchprocess.BatchProcess;
import com.constellio.model.entities.records.Record;
import com.constellio.model.entities.schemas.MetadataSchema;
import com.constellio.model.entities.schemas.MetadataSchemaType;
import com.constellio.model.entities.schemas.MetadataSchemaTypes;
import com.constellio.model.services.batch.manager.BatchProcessesManager;
import com.constellio.model.services.collections.CollectionsListManager;
import com.constellio.model.services.extensions.ConstellioModulesManager;
import com.constellio.model.services.factories.ModelLayerFactory;
import com.constellio.model.services.schemas.MetadataSchemasManagerException.OptimisticLocking;
import com.constellio.model.services.schemas.MetadataSchemasManagerRuntimeException.MetadataSchemasManagerRuntimeException_NoSuchCollection;
import com.constellio.model.services.schemas.builders.MetadataSchemaTypesBuilder;
import com.constellio.model.services.schemas.impacts.SchemaTypesAlterationImpact;
import com.constellio.model.services.schemas.impacts.SchemaTypesAlterationImpactsCalculator;
import com.constellio.model.services.schemas.xml.MetadataSchemaXMLReader1;
import com.constellio.model.services.schemas.xml.MetadataSchemaXMLReader2;
import com.constellio.model.services.schemas.xml.MetadataSchemaXMLReader3;
import com.constellio.model.services.schemas.xml.MetadataSchemaXMLWriter3;
import com.constellio.model.services.search.SearchServices;
import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition;
import com.constellio.model.services.taxonomies.TaxonomiesManager;
import com.constellio.model.utils.ClassProvider;
import com.constellio.model.utils.DefaultClassProvider;
import com.constellio.model.utils.OneXMLConfigPerCollectionManager;
import com.constellio.model.utils.OneXMLConfigPerCollectionManagerListener;
import com.constellio.model.utils.XMLConfigReader;
public class MetadataSchemasManager implements StatefulService, OneXMLConfigPerCollectionManagerListener<MetadataSchemaTypes> {
private static final String SCHEMAS_CONFIG_PATH = "/schemas.xml";
private final DataStoreTypesFactory typesFactory;
private final TaxonomiesManager taxonomiesManager;
private final ConfigManager configManager;
private final CollectionsListManager collectionsListManager;
List<MetadataSchemasManagerListener> listeners = new ArrayList<MetadataSchemasManagerListener>();
private OneXMLConfigPerCollectionManager<MetadataSchemaTypes> oneXmlConfigPerCollectionManager;
private BatchProcessesManager batchProcessesManager;
private SearchServices searchServices;
private ModelLayerFactory modelLayerFactory;
private Delayed<ConstellioModulesManager> modulesManagerDelayed;
public MetadataSchemasManager(ModelLayerFactory modelLayerFactory, Delayed<ConstellioModulesManager> modulesManagerDelayed) {
this.configManager = modelLayerFactory.getDataLayerFactory().getConfigManager();
this.typesFactory = modelLayerFactory.getDataLayerFactory().newTypesFactory();
this.taxonomiesManager = modelLayerFactory.getTaxonomiesManager();
this.collectionsListManager = modelLayerFactory.getCollectionsListManager();
this.batchProcessesManager = modelLayerFactory.getBatchProcessesManager();
this.searchServices = modelLayerFactory.newSearchServices();
this.modulesManagerDelayed = modulesManagerDelayed;
this.modelLayerFactory = modelLayerFactory;
}
@Override
public void initialize() {
oneXmlConfigPerCollectionManager();
}
/**
* This cache saves a lot of time during test execution, but has not benefit during normal runtime
* It is static because it is shared by all tests.
* It is disabled by default and enabled in tests
*/
public static boolean cacheEnabled = false;
private static Map<String, MetadataSchemaTypes> typesCache = new HashMap<>();
OneXMLConfigPerCollectionManager<MetadataSchemaTypes> newOneXMLManager(ConfigManager configManager,
CollectionsListManager collectionsListManager) {
return new OneXMLConfigPerCollectionManager<MetadataSchemaTypes>(configManager,
collectionsListManager, SCHEMAS_CONFIG_PATH, xmlConfigReader(), this) {
@Override
protected MetadataSchemaTypes parse(String collection, XMLConfiguration xmlConfiguration) {
if (cacheEnabled) {
if (typesCache.containsKey(collection + xmlConfiguration.getRealHash())) {
return typesCache.get(collection + xmlConfiguration.getRealHash());
}
MetadataSchemaTypes types = super.parse(collection, xmlConfiguration);
//typesCache.put(collection + xmlConfiguration.getRealHash(), types);
return types;
} else {
return super.parse(collection, xmlConfiguration);
}
}
};
}
public void createCollectionSchemas(final String collection) {
DocumentAlteration createConfigAlteration = new DocumentAlteration() {
@Override
public void alter(Document document) {
new MetadataSchemaXMLWriter3().writeEmptyDocument(collection, document);
}
};
oneXmlConfigPerCollectionManager.createCollectionFile(collection, createConfigAlteration);
}
private XMLConfigReader<MetadataSchemaTypes> xmlConfigReader() {
return new XMLConfigReader<MetadataSchemaTypes>() {
@Override
public MetadataSchemaTypes read(String collection, Document document) {
Element rootElement = document.getRootElement();
String formatVersion = rootElement == null ? null : rootElement.getAttributeValue(FORMAT_ATTRIBUTE);
MetadataSchemaTypesBuilder typesBuilder;
if (formatVersion == null) {
typesBuilder = new MetadataSchemaXMLReader1(getClassProvider())
.read(collection, document, typesFactory, modelLayerFactory);
} else if (MetadataSchemaXMLReader2.FORMAT_VERSION.equals(formatVersion)) {
typesBuilder = new MetadataSchemaXMLReader2(getClassProvider())
.read(collection, document, typesFactory, modelLayerFactory);
} else if (MetadataSchemaXMLReader3.FORMAT_VERSION.equals(formatVersion)) {
typesBuilder = new MetadataSchemaXMLReader3(getClassProvider())
.read(collection, document, typesFactory, modelLayerFactory);
} else {
throw new ImpossibleRuntimeException("Invalid format version '" + formatVersion + "'");
}
MetadataSchemaTypes builtTypes = typesBuilder.build(typesFactory, modelLayerFactory);
return builtTypes;
}
};
}
public MetadataSchema getSchemaOf(Record record) {
return getSchemaTypes(record).getSchema(record.getSchemaCode());
}
public MetadataSchemaType getSchemaTypeOf(Record record) {
return getSchemaTypes(record).getSchemaType(record.getTypeCode());
}
public List<MetadataSchemaType> getSchemaTypes(CollectionObject collectionObject, List<String> schemaTypeCodes) {
return getSchemaTypes(collectionObject.getCollection(), schemaTypeCodes);
}
public List<MetadataSchemaType> getSchemaTypes(String collection, List<String> schemaTypeCodes) {
MetadataSchemaTypes allTypes = getSchemaTypes(collection);
List<MetadataSchemaType> types = new ArrayList<>();
for (String schemaTypeCode : schemaTypeCodes) {
types.add(allTypes.getSchemaType(schemaTypeCode));
}
return Collections.unmodifiableList(types);
}
public MetadataSchemaTypes getSchemaTypes(CollectionObject collectionObject) {
return getSchemaTypes(collectionObject.getCollection());
}
public MetadataSchemaTypes getSchemaTypes(String collection) {
MetadataSchemaTypes types = oneXmlConfigPerCollectionManager().get(collection);
if (types == null) {
throw new MetadataSchemasManagerRuntimeException_NoSuchCollection(collection);
}
return types;
}
private OneXMLConfigPerCollectionManager<MetadataSchemaTypes> oneXmlConfigPerCollectionManager() {
if (oneXmlConfigPerCollectionManager == null) {
this.oneXmlConfigPerCollectionManager = newOneXMLManager(configManager, collectionsListManager);
}
return oneXmlConfigPerCollectionManager;
}
public List<MetadataSchemaTypes> getAllCollectionsSchemaTypes() {
List<MetadataSchemaTypes> types = new ArrayList<>();
for (String collection : collectionsListManager.getCollections()) {
types.add(getSchemaTypes(collection));
}
return types;
}
public MetadataSchemaTypesBuilder modify(String collection) {
return MetadataSchemaTypesBuilder.modify(getSchemaTypes(collection), getClassProvider());
}
private ClassProvider getClassProvider() {
final DefaultClassProvider defaultClassProvider = new DefaultClassProvider();
return new ClassProvider() {
@Override
public <T> Class<T> loadClass(String name)
throws ClassNotFoundException {
try {
return defaultClassProvider.loadClass(name);
} catch (Throwable e) {
return modulesManagerDelayed.get().getModuleClass(name);
}
}
};
}
public void modify(String collection, MetadataSchemaTypesAlteration alteration) {
MetadataSchemaTypesBuilder builder = modify(collection);
alteration.alter(builder);
try {
saveUpdateSchemaTypes(builder);
} catch (OptimisticLocking optimistickLocking) {
modify(collection, alteration);
}
}
public void deleteSchemaTypes(final List<MetadataSchemaType> typesToDelete) {
if (!typesToDelete.isEmpty()) {
modify(typesToDelete.get(0).getCollection(), new MetadataSchemaTypesAlteration() {
@Override
public void alter(MetadataSchemaTypesBuilder types) {
for (MetadataSchemaType type : typesToDelete) {
types.deleteSchemaType(type, searchServices);
}
}
});
}
}
public void deleteCustomSchemas(final List<MetadataSchema> schemasToDelete) {
if (!schemasToDelete.isEmpty()) {
modify(schemasToDelete.get(0).getCollection(), new MetadataSchemaTypesAlteration() {
@Override
public void alter(MetadataSchemaTypesBuilder types) {
for (MetadataSchema schema : schemasToDelete) {
types.getSchemaType(schema.getCode().split("_")[0]).deleteSchema(schema, searchServices);
}
}
});
}
}
public MetadataSchemaTypes saveUpdateSchemaTypes(MetadataSchemaTypesBuilder schemaTypesBuilder)
throws OptimisticLocking {
MetadataSchemaTypes schemaTypes = schemaTypesBuilder.build(typesFactory, modelLayerFactory);
Document document = new MetadataSchemaXMLWriter3().write(schemaTypes);
List<SchemaTypesAlterationImpact> impacts = calculateImpactsOf(schemaTypesBuilder);
List<BatchProcess> batchProcesses = prepareBatchProcesses(impacts, schemaTypesBuilder.getCollection());
try {
saveSchemaTypesDocument(schemaTypesBuilder, document);
batchProcessesManager.markAsPending(batchProcesses);
} catch (Throwable t) {
batchProcessesManager.cancelStandByBatchProcesses(batchProcesses);
throw t;
}
return schemaTypes;
}
private List<BatchProcess> prepareBatchProcesses(List<SchemaTypesAlterationImpact> impacts, String collection) {
List<BatchProcess> batchProcesses = new ArrayList<>();
for (SchemaTypesAlterationImpact impact : impacts) {
LogicalSearchCondition condition = getBatchProcessCondition(impact, collection);
if (searchServices.hasResults(condition)) {
batchProcesses.add(batchProcessesManager
.addBatchProcessInStandby(condition, impact.getAction(), "reindex.schemasAlteration"));
}
}
return batchProcesses;
}
private LogicalSearchCondition getBatchProcessCondition(SchemaTypesAlterationImpact impact, String collection) {
MetadataSchemaType type = getSchemaTypes(collection).getSchemaType(impact.getSchemaType());
return from(type).returnAll();
}
private List<SchemaTypesAlterationImpact> calculateImpactsOf(MetadataSchemaTypesBuilder schemaTypesBuilder) {
return new SchemaTypesAlterationImpactsCalculator().calculatePotentialImpacts(schemaTypesBuilder);
}
private void saveSchemaTypesDocument(MetadataSchemaTypesBuilder schemaTypesBuilder, Document document) {
try {
String collection = schemaTypesBuilder.getCollection();
oneXmlConfigPerCollectionManager.update(collection, "" + schemaTypesBuilder.getVersion(), document);
} catch (OptimisticLockingConfiguration | NoSuchConfiguration e) {
throw new MetadataSchemasManagerRuntimeException.CannotUpdateDocument(document.toString(), e);
}
}
public void registerListener(String path, ConfigEventListener configEventListener) {
configManager.registerListener(path, configEventListener);
}
public void registerListener(MetadataSchemasManagerListener metadataSchemasManagerListener) {
listeners.add(metadataSchemasManagerListener);
}
@Override
public void onValueModified(String collection, MetadataSchemaTypes newValue) {
xmlConfigReader();
for (MetadataSchemasManagerListener listener : listeners) {
listener.onCollectionSchemasModified(collection);
}
}
@Override
public void close() {
}
}