package com.constellio.model.services.configs; import static com.constellio.model.services.search.query.logical.LogicalSearchQueryOperators.from; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.EnumUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.PropertiesAlteration; import com.constellio.data.dao.managers.config.events.ConfigUpdatedEventListener; import com.constellio.data.dao.managers.config.values.BinaryConfiguration; import com.constellio.data.dao.managers.config.values.PropertiesConfiguration; import com.constellio.data.io.services.facades.IOServices; import com.constellio.data.io.streamFactories.StreamFactory; import com.constellio.data.io.streamFactories.services.one.StreamOperation; import com.constellio.data.utils.Delayed; import com.constellio.data.utils.ImpossibleRuntimeException; import com.constellio.data.utils.LangUtils; import com.constellio.model.entities.batchprocess.BatchProcess; import com.constellio.model.entities.batchprocess.BatchProcessAction; import com.constellio.model.entities.calculators.dependencies.ConfigDependency; import com.constellio.model.entities.calculators.dependencies.Dependency; import com.constellio.model.entities.configs.SystemConfiguration; import com.constellio.model.entities.configs.SystemConfigurationGroup; import com.constellio.model.entities.configs.SystemConfigurationScript; import com.constellio.model.entities.configs.SystemConfigurationType; import com.constellio.model.entities.modules.Module; import com.constellio.model.entities.modules.PluginUtil; import com.constellio.model.entities.schemas.Metadata; import com.constellio.model.entities.schemas.MetadataSchemaType; import com.constellio.model.entities.schemas.MetadataSchemaTypes; import com.constellio.model.entities.schemas.entries.CalculatedDataEntry; import com.constellio.model.frameworks.validation.ValidationErrors; import com.constellio.model.services.batch.actions.ReindexMetadatasBatchProcessAction; import com.constellio.model.services.batch.manager.BatchProcessesManager; import com.constellio.model.services.configs.SystemConfigurationsManagerRuntimeException.SystemConfigurationsManagerRuntimeException_InvalidConfigValue; import com.constellio.model.services.configs.SystemConfigurationsManagerRuntimeException.SystemConfigurationsManagerRuntimeException_UpdateScriptFailed; import com.constellio.model.services.extensions.ConstellioModulesManager; import com.constellio.model.services.factories.ModelLayerFactory; import com.constellio.model.services.migrations.ConstellioEIMConfigs; import com.constellio.model.services.schemas.SchemaUtils; import com.constellio.model.services.search.SearchServices; import com.constellio.model.services.search.query.logical.condition.LogicalSearchCondition; import com.constellio.model.utils.InstanciationUtils; public class SystemConfigurationsManager implements StatefulService, ConfigUpdatedEventListener { private static Logger LOGGER = LoggerFactory.getLogger(SystemConfigurationsManager.class); IOServices ioServices; ModelLayerFactory modelLayerFactory; PropertiesConfiguration configValues; static final String CONFIG_FILE_PATH = "/systemConfigs.properties"; ConfigManager configManager; Delayed<ConstellioModulesManager> constellioModulesManagerDelayed; public SystemConfigurationsManager(ModelLayerFactory modelLayerFactory, ConfigManager configManager, Delayed<ConstellioModulesManager> constellioModulesManagerDelayed) { this.modelLayerFactory = modelLayerFactory; this.configManager = configManager; this.constellioModulesManagerDelayed = constellioModulesManagerDelayed; this.configManager.registerListener(CONFIG_FILE_PATH, this); this.ioServices = modelLayerFactory.getDataLayerFactory().getIOServicesFactory().newIOServices(); } @Override public void initialize() { configManager.createPropertiesDocumentIfInexistent(CONFIG_FILE_PATH, new PropertiesAlteration() { @Override public void alter(Map<String, String> properties) { } }); reloadConfigValues(); } private void reloadConfigValues() { configValues = configManager.getProperties(CONFIG_FILE_PATH); } @Override public void close() { } public boolean signalDefaultValueModification(final SystemConfiguration config, final Object previousDefaultValue) { String propertyKey = config.getPropertyKey(); Object currentValue = toObject(config, configValues.getProperties().get(propertyKey)); if (currentValue == null) { return reindex(config, config.getDefaultValue(), previousDefaultValue); } return false; } public boolean setValue(final SystemConfiguration config, final Object newValue) { if (config.getType() == SystemConfigurationType.BINARY) { StreamFactory<InputStream> streamFactory = (StreamFactory<InputStream>) newValue; final String configPath = "/systemConfigs/" + config.getCode(); if (configManager.exist(configPath)) { if (streamFactory == null) { configManager.delete(configPath); } else { try { ioServices.execute(new StreamOperation<InputStream>() { @Override public void execute(InputStream stream) { String hash = configManager.getBinary(configPath).getHash(); try { configManager.update(configPath, hash, stream); } catch (OptimisticLockingConfiguration e) { throw new ImpossibleRuntimeException(e); } } }, streamFactory); } catch (IOException e) { LOGGER.error("error when saving stream", e); throw new SystemConfigurationsManagerRuntimeException_InvalidConfigValue(config.getCode(), ""); } } } else { if (streamFactory != null) { try { ioServices.execute(new StreamOperation<InputStream>() { @Override public void execute(InputStream stream) { configManager.add(configPath, stream); } }, streamFactory); } catch (IOException e) { LOGGER.error("error when saving stream", e); throw new SystemConfigurationsManagerRuntimeException_InvalidConfigValue(config.getCode(), ""); } } } } else { final Object oldValue = getValue(config); ValidationErrors errors = new ValidationErrors(); validate(config, newValue, errors); if (!errors.getValidationErrors().isEmpty()) { throw new SystemConfigurationsManagerRuntimeException_InvalidConfigValue(config.getCode(), newValue); } if (config.equals(ConstellioEIMConfigs.IN_UPDATE_PROCESS)) { configManager.updateProperties(CONFIG_FILE_PATH, updateConfigValueAlteration(config, newValue)); } else { return reindex(config, newValue, oldValue); } } return false; } public boolean doesSetValueRequireReindexing(final SystemConfiguration config, final Object newValue) { if (config.getType() != SystemConfigurationType.BINARY) { final Object oldValue = getValue(config); ValidationErrors errors = new ValidationErrors(); validate(config, newValue, errors); if (!errors.getValidationErrors().isEmpty()) { throw new SystemConfigurationsManagerRuntimeException_InvalidConfigValue(config.getCode(), newValue); } if (config.equals(ConstellioEIMConfigs.IN_UPDATE_PROCESS)) { } else { return doesReindexNeedToSetFlag(config, newValue, oldValue); } } return false; } private boolean doesReindexNeedToSetFlag(SystemConfiguration config, Object newValue, Object oldValue) { BatchProcessesManager batchProcessesManager = modelLayerFactory.getBatchProcessesManager(); SystemConfigurationScript<Object> listener = getInstanciatedScriptFor(config); List<String> collections = modelLayerFactory.getCollectionsListManager().getCollections(); ConstellioModulesManager modulesManager = constellioModulesManagerDelayed.get(); Module module = config.getModule() == null ? null : modulesManager.getInstalledModule(config.getModule()); List<BatchProcess> batchProcesses = startBatchProcessesToReindex(config); int totalRecordsToReindex = 0; for (BatchProcess process : batchProcesses) { totalRecordsToReindex += process.getTotalRecordsCount(); } return totalRecordsToReindex > 10000; } private boolean reindex(SystemConfiguration config, Object newValue, Object oldValue) { BatchProcessesManager batchProcessesManager = modelLayerFactory.getBatchProcessesManager(); SystemConfigurationScript<Object> listener = getInstanciatedScriptFor(config); List<String> collections = modelLayerFactory.getCollectionsListManager().getCollections(); ConstellioModulesManager modulesManager = constellioModulesManagerDelayed.get(); Module module = config.getModule() == null ? null : modulesManager.getInstalledModule(config.getModule()); List<BatchProcess> batchProcesses = startBatchProcessesToReindex(config); int totalRecordsToReindex = 0; for (BatchProcess process : batchProcesses) { totalRecordsToReindex += process.getTotalRecordsCount(); } try { if (listener != null) { listener.onValueChanged(oldValue, newValue, modelLayerFactory); for (String collection : collections) { if (module == null || modulesManager.isModuleEnabled(collection, module)) { listener.onValueChanged(oldValue, newValue, modelLayerFactory, collection); } } } configManager.updateProperties(CONFIG_FILE_PATH, updateConfigValueAlteration(config, newValue)); boolean reindex = totalRecordsToReindex > 10000 || (config.isRequireReIndexing() && totalRecordsToReindex > 0); if (reindex) { for (BatchProcess batchProcess : batchProcesses) { batchProcessesManager.cancelStandByBatchProcess(batchProcess); } } else { for (BatchProcess batchProcess : batchProcesses) { batchProcessesManager.markAsPending(batchProcess); } } return reindex; } catch (RuntimeException e) { if (listener != null) { listener.onValueChanged(newValue, oldValue, modelLayerFactory); for (String collection : modelLayerFactory.getCollectionsListManager().getCollections()) { if (module == null || modulesManager.isModuleEnabled(collection, module)) { listener.onValueChanged(newValue, oldValue, modelLayerFactory, collection); } } } for (BatchProcess batchProcess : batchProcesses) { batchProcessesManager.cancelStandByBatchProcess(batchProcess); } throw new SystemConfigurationsManagerRuntimeException_UpdateScriptFailed(config.getCode(), newValue, e); } } private PropertiesAlteration updateConfigValueAlteration(final SystemConfiguration config, final Object newValue) { return new PropertiesAlteration() { @Override public void alter(Map<String, String> properties) { if (LangUtils.isEqual(newValue, config.getDefaultValue())) { properties.remove(config.getPropertyKey()); } else { properties.put(config.getPropertyKey(), SystemConfigurationsManager.this.toString(config, newValue)); } } }; } List<BatchProcess> startBatchProcessesToReindex(SystemConfiguration config) { List<BatchProcess> batchProcesses = new ArrayList<>(); for (String collection : modelLayerFactory.getCollectionsListManager().getCollectionsExcludingSystem()) { MetadataSchemaTypes types = modelLayerFactory.getMetadataSchemasManager().getSchemaTypes(collection); for (String typeCode : types.getSchemaTypesSortedByDependency()) { MetadataSchemaType type = types.getSchemaType(typeCode); List<Metadata> metadatasToReindex = findMetadatasToReindex(type, config); if (!metadatasToReindex.isEmpty()) { batchProcesses.addAll(startBatchProcessesToReindex(metadatasToReindex, type, config)); } } } return batchProcesses; } List<BatchProcess> startBatchProcessesToReindex(List<Metadata> metadatasToReindex, MetadataSchemaType type, SystemConfiguration config) { List<BatchProcess> batchProcesses = new ArrayList<>(); BatchProcessesManager batchProcessesManager = modelLayerFactory.getBatchProcessesManager(); SearchServices searchServices = modelLayerFactory.newSearchServices(); List<String> schemaCodes = new SchemaUtils().toMetadataCodes(metadatasToReindex); LogicalSearchCondition condition = from(type).returnAll(); if (searchServices.hasResults(condition)) { BatchProcessAction action = new ReindexMetadatasBatchProcessAction(schemaCodes); batchProcesses.add(batchProcessesManager .addBatchProcessInStandby(condition, action, "reindex.config " + config.getCode())); } return batchProcesses; } private List<Metadata> findMetadatasToReindex(MetadataSchemaType schemaType, SystemConfiguration systemConfiguration) { Set<Metadata> reindexedMetadatas = new HashSet<>(); for (Metadata metadata : schemaType.getCalculatedMetadatas()) { for (Dependency dependency : ((CalculatedDataEntry) metadata.getDataEntry()).getCalculator().getDependencies()) { if (dependency instanceof ConfigDependency) { ConfigDependency configDependency = (ConfigDependency) dependency; if (configDependency.getConfiguration().equals(systemConfiguration)) { reindexedMetadatas.add(metadata); } } } } return new ArrayList<>(reindexedMetadatas); } public void validate(SystemConfiguration config, Object newValue, ValidationErrors errors) { SystemConfigurationScript<Object> listener = getInstanciatedScriptFor(config); if (listener != null) { listener.validate(newValue, errors); } } public void reset(final SystemConfiguration config) { setValue(config, config.getDefaultValue()); configManager.updateProperties(CONFIG_FILE_PATH, new PropertiesAlteration() { @Override public void alter(Map<String, String> properties) { properties.remove(config.getPropertyKey()); } }); } private String toString(SystemConfiguration config, Object value) { if (value == null) { return null; } switch (config.getType()) { case STRING: return value.toString(); case BOOLEAN: return ((Boolean) value) ? "true" : "false"; case INTEGER: return "" + value; case ENUM: return ((Enum<?>) value).name(); } throw new ImpossibleRuntimeException("Unsupported config type : " + config.getType()); } private Object toObject(SystemConfiguration config, String value) { if (value == null) { return null; } switch (config.getType()) { case STRING: return value; case BOOLEAN: return "true".equals(value); case INTEGER: return Integer.valueOf(value); case ENUM: return EnumUtils.getEnum((Class) config.getEnumClass(), value); } throw new ImpossibleRuntimeException("Unsupported config type : " + config.getType()); } public <T> T getValue(SystemConfiguration config) { String propertyKey = config.getPropertyKey(); if (config.getType() == SystemConfigurationType.BINARY) { BinaryConfiguration binaryConfiguration = configManager.getBinary("/systemConfigs/" + config.getCode()); return binaryConfiguration == null ? null : (T) binaryConfiguration.getInputStreamFactory(); } else if (configValues.getProperties().containsKey(propertyKey)) { String value = configValues.getProperties().get(propertyKey); return (T) toObject(config, value); } else { return (T) config.getDefaultValue(); } } @Override public void onConfigUpdated(String configPath) { reloadConfigValues(); } public SystemConfigurationScript<Object> getInstanciatedScriptFor(SystemConfiguration config) { if (config.getScriptClass() != null) { return new InstanciationUtils() .instanciateWithoutExpectableExceptions(config.getScriptClass()); } else { return null; } } public List<SystemConfigurationGroup> getConfigurationGroups() { List<SystemConfigurationGroup> groups = new ArrayList<>(); for (SystemConfiguration config : getAllConfigurations()) { SystemConfigurationGroup group = new SystemConfigurationGroup(config.getModule(), config.getConfigGroupCode()); if (!groups.contains(group)) { groups.add(group); } } return groups; } public List<SystemConfiguration> getAllConfigurations() { List<SystemConfiguration> configurations = new ArrayList<>(); configurations.addAll(ConstellioEIMConfigs.getCoreConfigs()); for (Module module : constellioModulesManagerDelayed.get().getInstalledModules()) { configurations.addAll(PluginUtil.getConfigurations(module)); } return configurations; } public SystemConfiguration getConfigurationWithCode(String code) { for (SystemConfiguration config : getAllConfigurations()) { if (config.getCode().equals(code)) { return config; } } return null; } public List<SystemConfiguration> getGroupConfigurations(SystemConfigurationGroup wantedGroup) { List<SystemConfiguration> configs = new ArrayList<>(); for (SystemConfiguration config : getAllConfigurations()) { SystemConfigurationGroup group = new SystemConfigurationGroup(config.getModule(), config.getConfigGroupCode()); if (group.equals(wantedGroup)) { configs.add(config); } } return configs; } public List<SystemConfiguration> getGroupConfigurationsWithCode(String code) { List<SystemConfiguration> configs = new ArrayList<>(); for (SystemConfiguration config : getAllConfigurations()) { SystemConfigurationGroup group = new SystemConfigurationGroup(config.getModule(), config.getConfigGroupCode()); if (group.getCode().equals(code)) { configs.add(config); } } return configs; } public List<SystemConfiguration> getNonHiddenGroupConfigurationsWithCodeOrderedByName(String groupCode) { List<SystemConfiguration> nonHidden = new ArrayList<>(); for (SystemConfiguration config : getAllConfigurations()) { SystemConfigurationGroup group = new SystemConfigurationGroup(config.getModule(), config.getConfigGroupCode()); if (group.getCode().equals(groupCode)) { if (!config.isHidden()) { nonHidden.add(config); } } } Collections.sort(nonHidden, new Comparator<SystemConfiguration>() { @Override public int compare(SystemConfiguration o1, SystemConfiguration o2) { return o1.getCode().compareTo(o2.getCode()); } }); return nonHidden; } }