package com.constellio.app.services.extensions;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jdom2.Document;
import org.jdom2.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.app.entities.modules.InstallableModule;
import com.constellio.app.entities.modules.InstallableSystemModule;
import com.constellio.app.entities.modules.locators.PropertiesLocatorFactory;
import com.constellio.app.entities.navigation.NavigationConfig;
import com.constellio.app.services.extensions.ConstellioModulesManagerRuntimeException.ConstellioModulesManagerRuntimeException_ModuleIsNotInstalled;
import com.constellio.app.services.extensions.plugins.ConstellioPluginManager;
import com.constellio.app.services.extensions.plugins.ConstellioPluginManagerRuntimeException.ConstellioPluginManagerRuntimeException_NoSuchModule;
import com.constellio.app.services.factories.AppLayerFactory;
import com.constellio.app.services.migrations.CoreNavigationConfiguration;
import com.constellio.app.services.migrations.MigrationServices;
import com.constellio.app.ui.i18n.i18n;
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.DocumentAlteration;
import com.constellio.data.dao.managers.config.PropertiesAlteration;
import com.constellio.data.dao.managers.config.values.XMLConfiguration;
import com.constellio.data.utils.Delayed;
import com.constellio.data.utils.KeySetMap;
import com.constellio.model.entities.CorePermissions;
import com.constellio.model.entities.modules.Module;
import com.constellio.model.entities.modules.PluginUtil;
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.utils.DependencyUtils;
public class ConstellioModulesManagerImpl implements ConstellioModulesManager, StatefulService {
@SuppressWarnings("unused") private static final Logger LOGGER = LoggerFactory.getLogger(ConstellioModulesManagerImpl.class);
private static final String FALSE = "false";
private static final String MODULES_CONFIG_PATH = "/modules.xml";
private final ConfigManager configManager;
private final AppLayerFactory appLayerFactory;
private final Delayed<MigrationServices> migrationServicesDelayed;
private final ModelLayerFactory modelLayerFactory;
private final ConstellioPluginManager constellioPluginManager;
private Set<String> startedModulesInAnyCollections = new HashSet<>();
private KeySetMap<String, String> startedModulesInCollections = new KeySetMap<>();
private Set<String> initializedResources = new HashSet<>();
public ConstellioModulesManagerImpl(AppLayerFactory appLayerFactory,
ConstellioPluginManager constellioPluginManager, Delayed<MigrationServices> migrationServicesDelayed) {
this(appLayerFactory.getModelLayerFactory().getDataLayerFactory().getConfigManager(), migrationServicesDelayed,
appLayerFactory, constellioPluginManager);
}
public ConstellioModulesManagerImpl(ConfigManager configManager, Delayed<MigrationServices> migrationServicesDelayed,
AppLayerFactory appLayerFactory,
ConstellioPluginManager constellioPluginManager) {
super();
this.appLayerFactory = appLayerFactory;
this.configManager = configManager;
this.migrationServicesDelayed = migrationServicesDelayed;
this.modelLayerFactory = appLayerFactory.getModelLayerFactory();
this.constellioPluginManager = constellioPluginManager;
}
@Override
public void initialize() {
createModulesConfigFileIfNotExist();
}
public void enableComplementaryModules() {
for (String collection : modelLayerFactory.getCollectionsListManager().getCollectionsExcludingSystem()) {
enableComplementaryModules(collection);
}
}
public List<InstallableModule> getInstalledModules() {
List<InstallableModule> installedModules = new ArrayList<>();
XMLConfiguration xmlConfig;
xmlConfig = configManager.getXML(MODULES_CONFIG_PATH);
Map<String, Element> moduleElements = parseModulesDocument(xmlConfig.getDocument());
for (InstallableModule module : getAllModules()) {
Element moduleElement = moduleElements.get(module.getId());
if (moduleElement != null) {
installedModules.add(module);
}
}
return installedModules;
}
public List<InstallableModule> getEnabledModules(String collection) {
List<InstallableModule> enabledModules = new ArrayList<>();
XMLConfiguration xmlConfig = configManager.getXML(MODULES_CONFIG_PATH);
Map<String, Element> moduleElements = parseModulesDocument(xmlConfig.getDocument());
Map<String, Set<String>> dependencies = new HashMap<>();
for (InstallableModule module : getAllModules()) {
Element moduleElement = moduleElements.get(module.getId());
if (moduleElement != null && isEnabled(moduleElement, collection)) {
enabledModules.add(module);
dependencies.put(module.getId(), new HashSet<>(module.getDependencies()));
}
}
List<String> moduleIds = new DependencyUtils<String>().sortByDependencyWithoutTieSort(dependencies);
List<InstallableModule> sortedInstallableModules = new ArrayList<>();
for (String moduleId : moduleIds) {
for (InstallableModule enabledModule : enabledModules) {
if (moduleId.equals(enabledModule.getId())) {
sortedInstallableModules.add(enabledModule);
}
}
}
return sortedInstallableModules;
}
public List<InstallableModule> getRequiredDependentModulesToInstall(String collection) {
Set<String> dependentModuleIds = new HashSet<>();
for (InstallableModule module : getBuiltinModules()) {
if (isModuleEnabled(collection, module)) {
dependentModuleIds.addAll(module.getDependencies());
}
}
for (InstallableModule module : getBuiltinModules()) {
if (isModuleEnabled(collection, module)) {
dependentModuleIds.remove(module.getId());
}
}
List<InstallableModule> dependentModules = new ArrayList<>();
for (String dependentModuleId : dependentModuleIds) {
for (InstallableModule module : getAllModules()) {
if (module.getId().equals(dependentModuleId)) {
dependentModules.add(module);
}
}
}
return dependentModules;
}
private boolean isEnabled(Element moduleElement, String collection) {
boolean enabled = false;
if (moduleElement != null) {
String enabledValue = moduleElement.getAttributeValue("enabled_in_collection_" + collection);
enabled = "true".equals(enabledValue);
}
return enabled;
}
private Map<String, Element> parseModulesDocument(Document document) {
Map<String, Element> moduleElements = new HashMap<>();
for (Element moduleElement : document.getRootElement().getChildren()) {
moduleElements.put(moduleElement.getAttributeValue("id"), moduleElement);
}
return moduleElements;
}
public List<InstallableModule> getDisabledModules(String collection) {
List<InstallableModule> disabledModules = new ArrayList<>();
XMLConfiguration xmlConfig = configManager.getXML(MODULES_CONFIG_PATH);
Map<String, Element> moduleElements = parseModulesDocument(xmlConfig.getDocument());
for (InstallableModule module : getAllModules()) {
Element moduleElement = moduleElements.get(module.getId());
if (moduleElement != null && !isEnabled(moduleElement, collection)) {
disabledModules.add(module);
}
}
return disabledModules;
}
public List<InstallableModule> getModulesAvailableForInstallation() {
List<InstallableModule> availableModules = new ArrayList<>();
XMLConfiguration xmlConfig = configManager.getXML(MODULES_CONFIG_PATH);
Map<String, Element> moduleElements = parseModulesDocument(xmlConfig.getDocument());
for (InstallableModule module : getAllModules()) {
if (!moduleElements.containsKey(module.getId())) {
availableModules.add(module);
}
}
return availableModules;
}
@Override
public List<InstallableModule> getAllModules() {
return constellioPluginManager.getRegistredModulesAndActivePlugins();
}
@Override
public List<InstallableModule> getBuiltinModules() {
return constellioPluginManager.getRegisteredModules();
}
@Override
public <T> Class<T> getModuleClass(String name)
throws ClassNotFoundException {
return constellioPluginManager.getModuleClass(name);
}
public void markAsInstalled(final Module module, CollectionsListManager collectionsListManager) {
for (String dependentModuleId : getDependencies(module)) {
InstallableModule dependentModule = getInstalledModule(dependentModuleId);
if (!isInstalled(dependentModule)) {
addModuleInConfigFile(dependentModule);
}
}
addModuleInConfigFile(module);
}
@Override
public Set<String> installValidModuleAndGetInvalidOnes(final Module module,
CollectionsListManager collectionsListManager) {
Set<String> returnList = new HashSet<>();
markAsInstalled(module, collectionsListManager);
initializePluginResources(module);
MigrationServices migrationServices = migrationServicesDelayed.get();
for (String collection : collectionsListManager.getCollections()) {
try {
returnList.addAll(migrationServices.migrate(collection, null, true));
} catch (OptimisticLockingConfiguration optimisticLockingConfiguration) {
throw new RuntimeException(optimisticLockingConfiguration);
}
}
return returnList;
}
private void addModuleInConfigFile(final Module module) {
configManager.updateXML(MODULES_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
final Element moduleElement = new Element("module");
moduleElement.setAttribute("id", module.getId());
document.getRootElement().addContent(moduleElement);
}
});
}
private void createModulesConfigFileIfNotExist() {
if (!configManager.exist(MODULES_CONFIG_PATH)) {
configManager.add(MODULES_CONFIG_PATH, new Document().setRootElement(new Element("modules")));
}
}
public boolean isModuleEnabled(String collection, Module module) {
for (InstallableModule enabledModule : getEnabledModules(collection)) {
if (enabledModule.getId().equals(module.getId())) {
return true;
}
}
return false;
}
@Override
public Set<String> enableValidModuleAndGetInvalidOnes(String collection, Module module) {
markAsEnabled(module, collection);
Set<String> returnList = applyModuleMigrations(collection, true);
if (startModule(collection, module)) {
if (!module.isComplementary()) {
returnList.addAll(enableComplementaryModules(collection));
}
} else {
returnList.add(module.getId());
}
return returnList;
}
public Set<String> enableComplementaryModules(String collection) {
Set<String> returnList = new HashSet<>();
List<String> enabledModuleIds = new ArrayList<>();
for (InstallableModule enabledModule : getEnabledModules(collection)) {
enabledModuleIds.add(enabledModule.getId());
}
boolean newModulesEnabled = false;
for (InstallableModule complementaryModule : getComplementaryModules()) {
if (enabledModuleIds.containsAll(getDependencies(complementaryModule))) {
if (!isInstalled(complementaryModule)) {
returnList.addAll(installValidModuleAndGetInvalidOnes(complementaryModule,
appLayerFactory.getModelLayerFactory().getCollectionsListManager()));
}
if (!isModuleEnabled(collection, complementaryModule)) {
returnList.addAll(enableValidModuleAndGetInvalidOnes(collection, complementaryModule));
newModulesEnabled = true;
}
}
}
if (newModulesEnabled) {
enabledModuleIds.addAll(enableComplementaryModules(collection));
}
return returnList;
}
private List<String> getDependencies(Module module) {
return PluginUtil.getDependencies(module);
}
public List<InstallableModule> getComplementaryModules() {
List<InstallableModule> complementaryModules = new ArrayList<>();
List<InstallableModule> allModules = getAllModules();
for (InstallableModule module : allModules) {
if (module.isComplementary()) {
complementaryModules.add(module);
} else {
}
}
return complementaryModules;
}
private Set<String> applyModuleMigrations(String collection, boolean newModule) {
MigrationServices migrationServices = migrationServicesDelayed.get();
try {
return migrationServices.migrate(collection, null, newModule);
} catch (OptimisticLockingConfiguration e) {
// TODO: Handle this
}
return new HashSet<>();
}
public void disableModule(String collection, final Module module) {
stopModule(collection, module);
disableModuleInConfigFile(collection, module);
}
private void disableModuleInConfigFile(final String collection, final Module module) {
configManager.updateXML(MODULES_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Map<String, Element> moduleElements = parseModulesDocument(document);
moduleElements.get(module.getId()).setAttribute("enabled_in_collection_" + collection, FALSE);
}
});
}
public boolean startModule(String collection, Module module) {
if (!startedModulesInCollections.get(collection).contains(module.getId())) {
startedModulesInCollections.add(collection, module.getId());
try {
if (!startedModulesInAnyCollections.contains(module.getId())) {
if (module instanceof InstallableSystemModule) {
((InstallableSystemModule) module).start(appLayerFactory);
}
startedModulesInAnyCollections.add(module.getId());
}
((InstallableModule) module).start(collection, appLayerFactory);
} catch (Throwable e) {
if (isPluginModule(module)) {
constellioPluginManager.handleModuleNotStartedCorrectly(module, collection, e);
return false;
} else {
throw new ConstellioModulesManagerRuntimeException.FailedToStart((InstallableModule) module, collection, e);
}
}
}
return true;
}
boolean isPluginModule(Module module) {
return constellioPluginManager.isPluginModule(module);
}
public void stopModule(String collection, Module module) {
startedModulesInCollections.get(collection).remove(module.getId());
try {
((InstallableModule) module).stop(collection, appLayerFactory);
} catch (Throwable e) {
throw new ConstellioModulesManagerRuntimeException.FailedToStop((InstallableModule) module, e);
}
if (module instanceof InstallableSystemModule) {
if (startedModulesInAnyCollections.contains(module.getId())) {
((InstallableSystemModule) module).stop(appLayerFactory);
startedModulesInAnyCollections.remove(module.getId());
}
}
}
public Set<String> startModules(String collection) {
Set<String> returnList = new HashSet<>();
for (Module module : getEnabledModules(collection)) {
if (!startModule(collection, module)) {
returnList.add(module.getId());
}
}
return returnList;
}
public void stopModules(String collection) {
for (Module module : getEnabledModules(collection)) {
stopModule(collection, module);
}
}
public boolean isInstalled(Module module) {
for (Module anInstalledModule : getInstalledModules()) {
if (anInstalledModule.getId().equals(module.getId())) {
return true;
}
}
return false;
}
public void removeCollectionFromVersionProperties(final String collection, ConfigManager configManager) {
configManager.updateProperties("/version.properties", newRemoveCollectionPropertiesAlteration(collection));
configManager.updateXML(MODULES_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Map<String, Element> moduleElements = parseModulesDocument(document);
for (Element element : moduleElements.values()) {
if (element.getAttribute("enabled_in_collection_" + collection) != null) {
element.removeAttribute("enabled_in_collection_" + collection);
}
}
}
});
}
PropertiesAlteration newRemoveCollectionPropertiesAlteration(final String collection) {
return new PropertiesAlteration() {
@Override
public void alter(Map<String, String> properties) {
if (properties.containsKey(collection + "_version")) {
properties.remove(collection + "_version");
}
if (properties.containsKey(collection + "_completedMigrations")) {
properties.remove(collection + "_completedMigrations");
}
}
};
}
@Override
public void close() {
}
public InstallableModule getInstalledModule(String id) {
for (InstallableModule module : getAllModules()) {
if (module.getId().equals(id)) {
return module;
}
}
throw new ConstellioPluginManagerRuntimeException_NoSuchModule(id);
}
public NavigationConfig getNavigationConfig(String collection) {
NavigationConfig config = new NavigationConfig();
new CoreNavigationConfiguration().configureNavigation(config);
for (InstallableModule module : getEnabledModules(collection)) {
try {
module.configureNavigation(config);
} catch (Throwable e) {
LOGGER.error("Error when configuring navigation of module " + module.getId() + " in collection " + collection, e);
}
}
return config;
}
public List<String> getPermissionGroups(String collection) {
return new ArrayList<>(getCollectionPermissions(collection).keySet());
}
public List<String> getPermissionsInGroup(String collection, String permissionGroupCode) {
return new ArrayList<>(getCollectionPermissions(collection).get(permissionGroupCode));
}
private Map<String, List<String>> getCollectionPermissions(String collection) {
Map<String, List<String>> permissions = new HashMap<>(CorePermissions.PERMISSIONS.getGrouped());
for (Module module : getEnabledModules(collection)) {
permissions.putAll(PluginUtil.getPermissions(module));
}
return permissions;
}
public void markAsEnabled(final Module module, final String collection) {
configManager.updateXML(MODULES_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Map<String, Element> moduleElements = parseModulesDocument(document);
Element moduleElement = moduleElements.get(module.getId());
if (moduleElement == null) {
throw new ConstellioModulesManagerRuntimeException_ModuleIsNotInstalled(module);
}
moduleElement.setAttribute("enabled_in_collection_" + collection, "true");
}
});
}
public void initializePluginResources(String collection) {
for (Module module : getEnabledModules(collection)) {
initializePluginResources(module);
}
}
private void initializePluginResources(Module module) {
if (!initializedResources.contains(module.getId())) {
constellioPluginManager.copyPluginResourcesToPluginsResourceFolder(module.getId());
i18n.registerBundle(PropertiesLocatorFactory.get().getModuleI18nBundle(module.getId()));
initializedResources.add(module.getId());
}
}
}