package com.constellio.app.services.extensions.plugins;
import static com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginStatus.DISABLED;
import static com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginStatus.ENABLED;
import static com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginStatus.INVALID;
import static com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginStatus.READY_TO_INSTALL;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.joda.time.LocalDate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.constellio.app.services.extensions.plugins.ConstellioPluginConfigurationManagerRuntimeException.ConstellioPluginConfigurationManagerRuntimeException_CouldNotDisableInvalidPlugin;
import com.constellio.app.services.extensions.plugins.ConstellioPluginConfigurationManagerRuntimeException.ConstellioPluginConfigurationManagerRuntimeException_CouldNotDisableReadyToInstallPlugin;
import com.constellio.app.services.extensions.plugins.ConstellioPluginConfigurationManagerRuntimeException.ConstellioPluginConfigurationManagerRuntimeException_CouldNotEnableInvalidPlugin;
import com.constellio.app.services.extensions.plugins.ConstellioPluginConfigurationManagerRuntimeException.ConstellioPluginConfigurationManagerRuntimeException_InvalidPluginWithNoStatus;
import com.constellio.app.services.extensions.plugins.ConstellioPluginConfigurationManagerRuntimeException.ConstellioPluginConfigurationManagerRuntimeException_NoSuchPlugin;
import com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginInfo;
import com.constellio.app.services.extensions.plugins.pluginInfo.ConstellioPluginStatus;
import com.constellio.data.dao.managers.config.ConfigManager;
import com.constellio.data.dao.managers.config.DocumentAlteration;
import com.constellio.data.dao.managers.config.values.XMLConfiguration;
import com.constellio.data.utils.TimeProvider;
public class ConstellioPluginConfigurationManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConstellioPluginConfigurationManager.class);
public static final String PLUGINS_CONFIG_PATH = "/plugins.xml";
public static final String STATUS_ATTRIBUTE = "status";
public static final String TITLE = "title";
public static final String LAST_VERSION_ATTRIBUTE = "lastVersion";
public static final String LAST_VERSION_INSTALLATION_DATE = "lastVersionInstallDate";
public static final String REQUIRED_CONSTELLIO_VERSION = "requiredConstellioVersion";
public static final String TRUE = "true";
public static final String FALSE = "false";
private static final String FAILURE_CAUSE = "failureCause";
private static final String STACK_TRACE = "stackTrace";
private final ConfigManager configManager;
public ConstellioPluginConfigurationManager(ConfigManager configManager) {
this.configManager = configManager;
}
public List<String> getActivePluginsIds() {
return getPluginsWithStatus(ENABLED);
}
private List<String> getPluginsWithStatus(ConstellioPluginStatus status) {
List<String> selectedPluginsIds = new ArrayList<>();
if (status != null) {
XMLConfiguration xmlConfig = configManager.getXML(PLUGINS_CONFIG_PATH);
for (Element pluginElement : xmlConfig.getDocument().getRootElement().getChildren()) {
ConstellioPluginInfo pluginInfo = populateInfoFromElement(pluginElement);
if (pluginInfo.getPluginStatus() != null && pluginInfo.getPluginStatus().equals(status)) {
selectedPluginsIds.add(pluginInfo.getCode());
}
}
}
return selectedPluginsIds;
}
public void markPluginAsEnabled(String pluginId)
throws ConstellioPluginConfigurationManagerRuntimeException {
ConstellioPluginStatus status = prValidateModule(pluginId);
switch (status) {
case INVALID:
throw new ConstellioPluginConfigurationManagerRuntimeException_CouldNotEnableInvalidPlugin(pluginId);
case READY_TO_INSTALL:
case DISABLED:
break;
case ENABLED:
return;
default:
throw new RuntimeException("Unsupported status " + status);
}
setPluginAttributeValue(pluginId, STATUS_ATTRIBUTE, ENABLED.toString());
setPluginAttributeValue(pluginId, STACK_TRACE, "");
}
private ConstellioPluginStatus prValidateModule(String pluginId)
throws ConstellioPluginConfigurationManagerRuntimeException {
ConstellioPluginInfo pluginInfo = getPluginInfo(pluginId);
if (pluginInfo == null) {
throw new ConstellioPluginConfigurationManagerRuntimeException_NoSuchPlugin(pluginId);
}
ConstellioPluginStatus status = pluginInfo.getPluginStatus();
if (status == null) {
throw new ConstellioPluginConfigurationManagerRuntimeException_InvalidPluginWithNoStatus(pluginId);
}
return status;
}
public void markPluginAsDisabled(String pluginId)
throws ConstellioPluginConfigurationManagerRuntimeException {
ConstellioPluginStatus status = prValidateModule(pluginId);
switch (status) {
case INVALID:
throw new ConstellioPluginConfigurationManagerRuntimeException_CouldNotDisableInvalidPlugin(pluginId);
case READY_TO_INSTALL:
throw new ConstellioPluginConfigurationManagerRuntimeException_CouldNotDisableReadyToInstallPlugin(pluginId);
case ENABLED:
break;
case DISABLED:
return;
default:
throw new RuntimeException("Unsupported status " + status);
}
setPluginAttributeValue(pluginId, STATUS_ATTRIBUTE, DISABLED.toString());
}
private void setPluginAttributeValue(final String pluginId, final String attributeName, final String attributeValue) {
configManager.updateXML(PLUGINS_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Element pluginElement = document.getRootElement().getChild(pluginId);
if (pluginElement == null) {
throw new RuntimeException("Invalid plugin id " + pluginId);
}
pluginElement.setAttribute(attributeName, attributeValue);
}
});
}
public ConstellioPluginInfo getPluginInfo(String pluginId) {
XMLConfiguration xmlConfig = configManager.getXML(PLUGINS_CONFIG_PATH);
Element pluginElement = xmlConfig.getDocument().getRootElement().getChild(pluginId);
if (pluginElement == null) {
return null;
} else {
return populateInfoFromElement(pluginElement);
}
}
public void installPlugin(String pluginId, String pluginTitle, String version, String requiredConstellioVersion) {
//LOGGER.info("Detected plugin : " + pluginId + "-" + version + " (" + pluginTitle + ")");
final ConstellioPluginInfo pluginInfo = new ConstellioPluginInfo()
.setLastInstallDate(TimeProvider.getLocalDate())
.setPluginStatus(READY_TO_INSTALL)
.setCode(pluginId)
.setTitle(pluginTitle)
.setRequiredConstellioVersion(requiredConstellioVersion)
.setVersion(version);
addOrUpdatePlugin(pluginInfo);
}
void addOrUpdatePlugin(final ConstellioPluginInfo pluginInfo) {
if (pluginInfo != null && StringUtils.isNotBlank(pluginInfo.getCode())) {
String id = pluginInfo.getCode();
configManager.updateXML(PLUGINS_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Element pluginInfoElement = document.getRootElement().getChild(pluginInfo.getCode());
if (pluginInfoElement == null) {
pluginInfoElement = new Element(pluginInfo.getCode());
} else {
document.getRootElement().removeChild(pluginInfo.getCode());
}
pluginInfoElement = populateElementFromInfo(pluginInfoElement, pluginInfo);
document.getRootElement().addContent(pluginInfoElement);
}
});
}
}
private Element populateElementFromInfo(Element pluginInfoElement, ConstellioPluginInfo pluginInfo) {
pluginInfoElement = setAttributeValue(pluginInfoElement, STATUS_ATTRIBUTE, pluginInfo.getPluginStatus());
pluginInfoElement = setAttributeValue(pluginInfoElement, TITLE, pluginInfo.getTitle());
pluginInfoElement = setAttributeValue(pluginInfoElement, LAST_VERSION_ATTRIBUTE, pluginInfo.getVersion());
pluginInfoElement = setAttributeValue(pluginInfoElement, REQUIRED_CONSTELLIO_VERSION,
pluginInfo.getRequiredConstellioVersion());
pluginInfoElement = setAttributeValue(pluginInfoElement, LAST_VERSION_INSTALLATION_DATE, pluginInfo.getLastInstallDate());
pluginInfoElement = setAttributeValue(pluginInfoElement, FAILURE_CAUSE, pluginInfo.getPluginActivationFailureCause());
pluginInfoElement = setAttributeValue(pluginInfoElement, STACK_TRACE, pluginInfo.getStackTrace());
return pluginInfoElement;
}
private ConstellioPluginInfo populateInfoFromElement(Element pluginElement) {
String statusAsString = pluginElement.getAttributeValue(STATUS_ATTRIBUTE);
ConstellioPluginStatus pluginStatus = null;
if (StringUtils.isNotBlank(statusAsString)) {
pluginStatus = ConstellioPluginStatus.valueOf(statusAsString);
}
String version = pluginElement.getAttributeValue(LAST_VERSION_ATTRIBUTE);
if (StringUtils.isBlank(version)) {
version = null;
}
String constellioVersion = pluginElement.getAttributeValue(REQUIRED_CONSTELLIO_VERSION);
if (StringUtils.isBlank(constellioVersion)) {
constellioVersion = null;
}
String title = pluginElement.getAttributeValue(TITLE);
String installationDateAsString = pluginElement.getAttributeValue(LAST_VERSION_INSTALLATION_DATE);
LocalDate lastInstallDate = null;
if (StringUtils.isNotBlank(installationDateAsString)) {
lastInstallDate = LocalDate.parse(installationDateAsString);
}
PluginActivationFailureCause cause = null;
String causeAsString = pluginElement.getAttributeValue(FAILURE_CAUSE);
if (StringUtils.isNotBlank(causeAsString)) {
cause = PluginActivationFailureCause.valueOf(causeAsString);
}
String stackTrace = pluginElement.getAttributeValue(STACK_TRACE);
if (StringUtils.isBlank(stackTrace)) {
stackTrace = null;
}
String code = pluginElement.getName();
if (StringUtils.isBlank(title)) {
title = code;
}
return new ConstellioPluginInfo().setCode(code).setTitle(title).setPluginStatus(pluginStatus).setVersion(version)
.setRequiredConstellioVersion(constellioVersion).setLastInstallDate(lastInstallDate)
.setPluginActivationFailureCause(cause).setStackTrace(stackTrace);
}
private Element setAttributeValue(Element element, String attributeName, Object value) {
if (value != null && StringUtils.isNotBlank(value.toString())) {
element.setAttribute(attributeName, value.toString());
} else {
element.setAttribute(attributeName, "");
}
return element;
}
public void createConfigFileIfNotExist() {
if (!configManager.exist(PLUGINS_CONFIG_PATH)) {
configManager.add(PLUGINS_CONFIG_PATH, new Document().setRootElement(new Element("plugins")));
}
}
List<ConstellioPluginInfo> getPlugins(ConstellioPluginStatus status) {
List<ConstellioPluginInfo> returnList = new ArrayList<>();
if (status != null) {
XMLConfiguration xmlConfig = configManager.getXML(PLUGINS_CONFIG_PATH);
for (Element pluginInfoElement : xmlConfig.getDocument().getRootElement().getChildren()) {
ConstellioPluginInfo pluginInfo = populateInfoFromElement(pluginInfoElement);
if (pluginInfo.getPluginStatus() != null && pluginInfo.getPluginStatus().equals(status)) {
returnList.add(pluginInfo);
}
}
}
return returnList;
}
void invalidateModule(final String pluginId, final PluginActivationFailureCause cause, final Throwable throwable) {
if (pluginId != null) {
configManager.updateXML(PLUGINS_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Element pluginElement = document.getRootElement().getChild(pluginId);
if (pluginElement == null) {
throw new RuntimeException("Invalid plugin id " + pluginId, throwable);
}
pluginElement.setAttribute(STATUS_ATTRIBUTE, INVALID.toString());
if (cause != null) {
String stackTrace = "";
if (throwable != null) {
stackTrace = stackTraceToString(throwable);
}
pluginElement.setAttribute(FAILURE_CAUSE, cause.toString());
pluginElement.setAttribute(STACK_TRACE, stackTrace);
} else {
pluginElement.setAttribute(FAILURE_CAUSE, "");
pluginElement.setAttribute(STACK_TRACE, "");
}
}
});
}
}
private String stackTraceToString(Throwable throwable) {
return ExceptionUtils.getStackTrace(throwable);
// StringBuilder sb = new StringBuilder();
// for (StackTraceElement element : throwable.getStackTrace()) {
// sb.append(element.toString());
// sb.append("\n");
// }
// sb.append(throwable.getMessage());
// sb.append("\n");
// sb.append(throwable.getLocalizedMessage());
// sb.append("\n");
// return sb.toString();
}
public void removePlugin(final String pluginId) {
if (pluginId != null) {
configManager.updateXML(PLUGINS_CONFIG_PATH, new DocumentAlteration() {
@Override
public void alter(Document document) {
Element rootElement = document.getRootElement();
rootElement.removeChild(pluginId);
}
});
}
}
public List<String> getAllPluginsCodes() {
List<String> returnList = new ArrayList<>();
XMLConfiguration xmlConfig = configManager.getXML(PLUGINS_CONFIG_PATH);
for (Element pluginInfoElement : xmlConfig.getDocument().getRootElement().getChildren()) {
returnList.add(pluginInfoElement.getName());
}
return returnList;
}
}