/*********************************************************************************** * * Copyright (c) 2014 Kamil Baczkowicz * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * * Kamil Baczkowicz - initial API and implementation and/or initial documentation * */ package pl.baczkowicz.mqttspy.configuration; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pl.baczkowicz.mqttspy.common.generated.SimpleMqttMessage; import pl.baczkowicz.mqttspy.configuration.generated.Connectivity; import pl.baczkowicz.mqttspy.configuration.generated.MqttSpyConfiguration; import pl.baczkowicz.mqttspy.configuration.generated.TabbedSubscriptionDetails; import pl.baczkowicz.mqttspy.configuration.generated.UserAuthenticationOptions; import pl.baczkowicz.mqttspy.configuration.generated.UserInterfaceMqttConnectionDetails; import pl.baczkowicz.mqttspy.configuration.generated.UserInterfaceMqttConnectionDetailsV010; import pl.baczkowicz.mqttspy.connectivity.MqttAsyncConnection; import pl.baczkowicz.mqttspy.connectivity.MqttSubscription; import pl.baczkowicz.spy.common.generated.ConnectionGroup; import pl.baczkowicz.spy.common.generated.ConnectionGroupReference; import pl.baczkowicz.spy.common.generated.ConnectionReference; import pl.baczkowicz.spy.common.generated.FormatterDetails; import pl.baczkowicz.spy.common.generated.Formatting; import pl.baczkowicz.spy.configuration.BaseConfigurationUtils; import pl.baczkowicz.spy.configuration.PropertyFileLoader; import pl.baczkowicz.spy.exceptions.XMLException; import pl.baczkowicz.spy.ui.configuration.ConfiguredConnectionGroupDetails; import pl.baczkowicz.spy.ui.configuration.UiProperties; import pl.baczkowicz.spy.ui.panes.SpyPerspective; import pl.baczkowicz.spy.ui.utils.DialogFactory; import pl.baczkowicz.spy.utils.ThreadingUtils; import pl.baczkowicz.spy.utils.TimeUtils; import pl.baczkowicz.spy.xml.XMLParser; /** * Manages loading and saving configuration files. */ @SuppressWarnings({"unchecked", "rawtypes"}) public class ConfigurationManager { final static Logger logger = LoggerFactory.getLogger(ConfigurationManager.class); public static final String PACKAGE = "pl.baczkowicz.mqttspy.configuration.generated"; public static final String SCHEMA = "/mqtt-spy-configuration.xsd"; public static final String SPY_COMMON_SCHEMA = "/spy-common.xsd"; public static final String MQTT_COMMON_SCHEMA = "/mqtt-spy-common.xsd"; public static final String DEFAULT_FILE_NAME = "mqtt-spy-configuration.xml"; public static final String DEFAULT_PROPERTIES_FILE_NAME = "/mqtt-spy.properties"; public static final String UI_PROPERTIES_FILE_NAME = "/mqtt-spy-ui.properties"; public static final String DEFAULT_HOME_DIRECTORY = getDefaultHomeDirectory(); private static final String DEFAULT_HOME_DIRECTORY_NAME = "mqtt-spy"; private MqttSpyConfiguration configuration; private List<ConfiguredConnectionDetails> connections = new ArrayList<>(); private List<ConfiguredConnectionGroupDetails> connectionGroups = new ArrayList<>(); private ConfiguredConnectionGroupDetails rootGroup; private File loadedConfigurationFile; private Exception lastException; private final XMLParser parser; private final PropertyFileLoader defaultPropertyFile; private final PropertyFileLoader uiPropertyFile; public ConfigurationManager() throws XMLException { // Load the default property file from classpath this.defaultPropertyFile = new PropertyFileLoader(); this.defaultPropertyFile.readFromClassPath(DEFAULT_PROPERTIES_FILE_NAME); // Load the UI property file if (!getUiPropertiesFile().exists()) { logger.info("Creating UI property file"); ConfigurationUtils.createUiPropertyFileFromClassPath(); } this.uiPropertyFile = new PropertyFileLoader(); this.uiPropertyFile.readFromFileSystem(getUiPropertiesFile()); this.parser = new XMLParser(PACKAGE, new String[] {SPY_COMMON_SCHEMA, MQTT_COMMON_SCHEMA, SCHEMA}); // Create empty configuration this.configuration = new MqttSpyConfiguration(); this.configuration.setConnectivity(new Connectivity()); this.configuration.setFormatting(new Formatting()); } public boolean loadConfiguration(final File file) { try { clear(); configuration = (MqttSpyConfiguration) parser.loadFromFile(file); initialiseConfiguration(); loadedConfigurationFile = file; return true; } catch (XMLException e) { setLastException(e); DialogFactory.createErrorDialog("Invalid configuration file", "Cannot process the given configuration file. See the log file for more details."); logger.error("Cannot process the configuration file at " + file.getAbsolutePath(), e); // eventManager.notifyConfigurationFileReadFailure(); } catch (FileNotFoundException e) { setLastException(e); DialogFactory.createErrorDialog("Invalid configuration file", "Cannot read the given configuration file. See the log file for more details."); logger.error("Cannot read the configuration file from " + file.getAbsolutePath(), e); // eventManager.notifyConfigurationFileReadFailure(); } return false; } public void initialiseConfiguration() { createConnections(); createConnectionGroups(); createConfigurationDefaults(); } private void createConfigurationDefaults() { if (configuration.getFormatting() == null) { configuration.setFormatting(new Formatting()); } } private void createConnections() { for (final Object connectionDetails : getConfiguration().getConnectivity().getConnectionOrConnectionV2()) { ConfiguredConnectionDetails configuredConnectionDetails = null; if (connectionDetails instanceof UserInterfaceMqttConnectionDetailsV010) { final UserInterfaceMqttConnectionDetailsV010 connectionDetailsV010 = (UserInterfaceMqttConnectionDetailsV010) connectionDetails; final UserInterfaceMqttConnectionDetails details = new UserInterfaceMqttConnectionDetails(); details.setName(connectionDetailsV010.getName()); details.getServerURI().add(connectionDetailsV010.getServerURI()); details.setClientID(connectionDetailsV010.getClientID()); details.setUserCredentials(connectionDetailsV010.getUserAuthentication()); if (connectionDetailsV010.getUserAuthentication() != null) { details.setUserAuthentication(new UserAuthenticationOptions( connectionDetailsV010.getUserAuthentication().isAskForUsername(), connectionDetailsV010.getUserAuthentication().isAskForPassword())); } if (connectionDetailsV010.getLastWillAndTestament() != null) { details.setLastWillAndTestament(new SimpleMqttMessage( connectionDetailsV010.getLastWillAndTestament().getPayload(), connectionDetailsV010.getLastWillAndTestament().getTopic(), connectionDetailsV010.getLastWillAndTestament().getQoS(), connectionDetailsV010.getLastWillAndTestament().isRetained())); } details.setCleanSession(connectionDetailsV010.isCleanSession()); details.setConnectionTimeout(connectionDetailsV010.getConnectionTimeout()); details.setKeepAliveInterval(connectionDetailsV010.getKeepAliveInterval()); details.setAutoOpen(connectionDetailsV010.isAutoOpen()); details.setAutoConnect(connectionDetailsV010.isAutoConnect()); details.setFormatter(connectionDetailsV010.getFormatter()); details.setMinMessagesStoredPerTopic(connectionDetailsV010.getMinMessagesStoredPerTopic()); details.setMaxMessagesStored(connectionDetailsV010.getMaxMessagesStored()); details.setPublicationScripts(connectionDetailsV010.getPublicationScripts()); details.getPublication().addAll(connectionDetailsV010.getPublication()); details.getSubscription().addAll(connectionDetailsV010.getSubscription()); // Put the defaults at the point of loading the config, so we don't need to do it again ConfigurationUtils.populateConnectionDefaults(details); configuredConnectionDetails = new ConfiguredConnectionDetails(false, false, details); } else if (connectionDetails instanceof UserInterfaceMqttConnectionDetails) { // Put the defaults at the point of loading the config, so we don't need to do it again ConfigurationUtils.populateConnectionDefaults((UserInterfaceMqttConnectionDetails) connectionDetails); configuredConnectionDetails = new ConfiguredConnectionDetails(false, false, (UserInterfaceMqttConnectionDetails) connectionDetails); } connections.add(configuredConnectionDetails); // Populate the connection ID for referencing in XML if (configuredConnectionDetails.getID() == null) { configuredConnectionDetails.setID(generateConnectionId()); } } } public static File getDefaultConfigurationFile() { return new File(getDefaultHomeDirectory() + ConfigurationManager.DEFAULT_FILE_NAME); } public static File getUiPropertiesFile() { return new File(getDefaultHomeDirectory() + ConfigurationManager.UI_PROPERTIES_FILE_NAME); } public static File getDefaultHomeDirectoryFile() { return new File(getDefaultHomeDirectory()); } public static String getDefaultHomeDirectory() { final String filePathSeparator = System.getProperty("file.separator"); String userHomeDirectory = System.getProperty("user.home"); if (!userHomeDirectory.endsWith(filePathSeparator)) { userHomeDirectory = userHomeDirectory + filePathSeparator; } return userHomeDirectory + DEFAULT_HOME_DIRECTORY_NAME + filePathSeparator; } public boolean saveConfiguration() { if (isConfigurationWritable()) { try { configuration.getConnectivity().getConnectionOrConnectionV2().clear(); configuration.getConnectivity().getConnectionOrConnectionV2().addAll(connections); configuration.getConnectionGroups().clear(); configuration.getConnectionGroups().addAll(connectionGroups); populateMissingFormatters(configuration.getFormatting().getFormatter(), connections); // for (final ConnectionGroup group : connectionGroups) // { // if (group.getGroup() != null && group.getGroup().getReference() == null) // { // group.setGroup(null); // } // } parser.saveToFile(loadedConfigurationFile, new JAXBElement(new QName("http://baczkowicz.pl/mqtt-spy-configuration", "MqttSpyConfiguration"), MqttSpyConfiguration.class, configuration)); return true; } catch (XMLException e) { setLastException(e); logger.error("Cannot save the configuration file", e); // eventManager.notifyConfigurationFileWriteFailure(); } } return false; } private void populateMissingFormatters(final List<FormatterDetails> formatters, final List<ConfiguredConnectionDetails> connections) { for (final ConfiguredConnectionDetails connection : connections) { if (connection.getFormatter() == null) { continue; } boolean formatterFound = false; for (final FormatterDetails formatter : formatters) { if (((FormatterDetails) connection.getFormatter()).getID().equals(formatter.getID())) { formatterFound = true; } } if (!formatterFound) { formatters.add((FormatterDetails) connection.getFormatter()); } } } public void clear() { connections.clear(); configuration = null; loadedConfigurationFile = null; lastException = null; } public ConfiguredConnectionDetails getMatchingConnection(final String id) { for (final ConfiguredConnectionDetails details : getConnections()) { if (id.equals(details.getID())) { return details; } } return null; } public void updateSubscriptionConfiguration(final MqttAsyncConnection connection, final MqttSubscription subscription) { final ConfiguredConnectionDetails details = getMatchingConnection(connection.getId()); boolean matchFound = false; for (final TabbedSubscriptionDetails subscriptionDetails : details.getSubscription()) { if (subscriptionDetails.getTopic().equals(subscription.getTopic())) { subscriptionDetails.setQos(subscription.getQos()); subscriptionDetails.setCreateTab(true); subscriptionDetails.setScriptFile(subscription.getDetails().getScriptFile()); matchFound = true; break; } } // If no match found, add this subscription if (!matchFound) { final TabbedSubscriptionDetails subscriptionDetails = new TabbedSubscriptionDetails(); subscriptionDetails.setTopic(subscription.getTopic()); subscriptionDetails.setQos(subscription.getQos()); subscriptionDetails.setCreateTab(true); subscriptionDetails.setScriptFile(subscription.getDetails().getScriptFile()); details.getSubscription().add(subscriptionDetails); } saveConfiguration(); } public void deleteSubscriptionConfiguration(final MqttAsyncConnection connection, final MqttSubscription subscription) { final ConfiguredConnectionDetails details = getMatchingConnection(connection.getId()); TabbedSubscriptionDetails itemToRemove = null; for (final TabbedSubscriptionDetails subscriptionDetails : details.getSubscription()) { if (subscriptionDetails.getTopic().equals(subscription.getTopic())) { itemToRemove = subscriptionDetails; break; } } if (itemToRemove != null) { details.getSubscription().remove(itemToRemove); } saveConfiguration(); } // =============================== // === Setters and getters ======= // =============================== public Exception getLastException() { return lastException; } public void setLastException(Exception lastException) { this.lastException = lastException; } public File getLoadedConfigurationFile() { return loadedConfigurationFile; } public boolean isConfigurationWritable() { if (loadedConfigurationFile != null && loadedConfigurationFile.canWrite()) { return true; } return false; } public boolean isConfigurationReadOnly() { if (loadedConfigurationFile != null && !loadedConfigurationFile.canWrite()) { return true; } return false; } public MqttSpyConfiguration getConfiguration() { return configuration; } public List<ConfiguredConnectionDetails> getConnections() { return connections; } public List<ConfiguredConnectionDetails> getConnections(final ConfiguredConnectionGroupDetails group) { List<ConfiguredConnectionDetails> orderedConnections = new ArrayList<>(); for (final ConnectionReference connetionRef : group.getConnections()) { orderedConnections.add((ConfiguredConnectionDetails) connetionRef.getReference()); } return orderedConnections; } public List<ConfiguredConnectionDetails> getOrderedConnections() { List<ConfiguredConnectionDetails> orderedConnections = new ArrayList<>(); List<ConfiguredConnectionGroupDetails> orderedGroups = new ArrayList<>(); sortConnections(getRootGroup(), orderedGroups, orderedConnections); return orderedConnections; } public List<ConfiguredConnectionGroupDetails> getOrderedGroups() { List<ConfiguredConnectionDetails> orderedConnections = new ArrayList<>(); List<ConfiguredConnectionGroupDetails> orderedGroups = new ArrayList<>(); orderedGroups.add(getRootGroup()); sortConnections(getRootGroup(), orderedGroups, orderedConnections); return orderedGroups; } private void sortConnections(final ConfiguredConnectionGroupDetails parentGroup, final List<ConfiguredConnectionGroupDetails> orderedGroups, List<ConfiguredConnectionDetails> orderedConnections) { for (final ConnectionGroupReference reference : parentGroup.getSubgroups()) { final ConfiguredConnectionGroupDetails group = (ConfiguredConnectionGroupDetails) reference.getReference(); orderedGroups.add(group); // Recursive sortConnections(group, orderedGroups, orderedConnections); } for (final ConnectionReference reference : parentGroup.getConnections()) { final ConfiguredConnectionDetails connection = (ConfiguredConnectionDetails) reference.getReference(); orderedConnections.add(connection); } } /** * Gets the connection ID generator. * * @return the connectionIdGenerator */ // public IdGenerator getConnectionIdGenerator() // { // return connectionIdGenerator; // } /** * Gets the default property file. * * @return the defaultPropertyFile */ public PropertyFileLoader getDefaultPropertyFile() { return defaultPropertyFile; } /** * Gets the UI property file. * * @return the uiPropertyFile */ public PropertyFileLoader getUiPropertyFile() { return uiPropertyFile; } public void saveUiProperties(final double width, final double height, boolean maximized, final SpyPerspective selectedPerspective, final boolean resizeMessagePane) { uiPropertyFile.setProperty(UiProperties.WIDTH_PROPERTY, String.valueOf(width)); uiPropertyFile.setProperty(UiProperties.HEIGHT_PROPERTY, String.valueOf(height)); uiPropertyFile.setProperty(UiProperties.MAXIMIZED_PROPERTY, String.valueOf(maximized)); uiPropertyFile.setProperty(UiProperties.PERSPECTIVE_PROPERTY, selectedPerspective.toString()); uiPropertyFile.setProperty(UiProperties.MESSAGE_PANE_RESIZE_PROPERTY, String.valueOf(resizeMessagePane)); // Other properties are read-only from file try { uiPropertyFile.saveToFileSystem("mqtt-spy-ui", getUiPropertiesFile()); } catch (IOException e) { logger.error("Cannot save UI properties", e); } } public static String generateConnectionGroupId() { ThreadingUtils.sleep(1); return "cg" + TimeUtils.getMonotonicTime(); } public static String generateConnectionId() { ThreadingUtils.sleep(1); return "conn" + TimeUtils.getMonotonicTime(); } public void createConnectionGroups() { final List<ConnectionGroup> groupsWithoutParent = new ArrayList<>(configuration.getConnectionGroups()); // Clear up resources - in case something was loaded before connectionGroups.clear(); rootGroup = null; // This is expected from v0.3.0 for (final ConnectionGroup group : configuration.getConnectionGroups()) { final ConfiguredConnectionGroupDetails details = new ConfiguredConnectionGroupDetails(group, false); for (ConnectionGroupReference subgroup : group.getSubgroups()) { groupsWithoutParent.remove(subgroup.getReference()); } connectionGroups.add(details); } // Create the root if no groups present (pre v0.3.0) if (connectionGroups.isEmpty() || groupsWithoutParent.isEmpty()) { logger.debug("Creating root group called 'All connections'"); rootGroup = new ConfiguredConnectionGroupDetails(new ConnectionGroup( BaseConfigurationUtils.DEFAULT_GROUP, "All connections", new ArrayList(), new ArrayList()), false); connectionGroups.add(rootGroup); // Assign all connections to the new root for (final ConfiguredConnectionDetails connection : getConnections()) { connection.setGroup(new ConnectionGroupReference(rootGroup)); rootGroup.getConnections().add(new ConnectionReference(connection)); } rootGroup.apply(); } else { // Find the root group final String rootId = groupsWithoutParent.get(0).getID(); for (final ConfiguredConnectionGroupDetails group : connectionGroups) { if (group.getID().equals(rootId)) { rootGroup = group; break; } } // At this point, new groups link to old connection and group objects, and old connection objects to old groups // Re-wire all connections updateTree(rootGroup); } } private ConfiguredConnectionGroupDetails findMatchingGroup(final ConnectionGroup group) { for (final ConfiguredConnectionGroupDetails groupDetails : connectionGroups) { if (group.getID().equals(groupDetails.getID())) { return groupDetails; } } return null; } private ConfiguredConnectionDetails findMatchingConnection(final UserInterfaceMqttConnectionDetails connection) { for (final ConfiguredConnectionDetails connectionDetails : connections) { if (connection.getID().equals(connectionDetails.getID())) { return connectionDetails; } } return null; } public static void findConnections(final ConfiguredConnectionGroupDetails parentGroup, final List<ConfiguredConnectionDetails> connections) { for (final ConnectionGroupReference reference : parentGroup.getSubgroups()) { final ConfiguredConnectionGroupDetails groupDetails = (ConfiguredConnectionGroupDetails) reference.getReference(); // Recursive findConnections(groupDetails, connections); } for (final ConnectionReference reference : parentGroup.getConnections()) { final ConfiguredConnectionDetails connectionDetails = (ConfiguredConnectionDetails) reference.getReference(); connections.add(connectionDetails); } } private void updateTree(final ConfiguredConnectionGroupDetails parentGroup) { final List<ConnectionGroupReference> subgroups = new ArrayList<>(parentGroup.getSubgroups()); parentGroup.getSubgroups().clear(); for (final ConnectionGroupReference reference : subgroups) { final ConnectionGroup group = (ConnectionGroup) reference.getReference(); final ConfiguredConnectionGroupDetails groupDetails = findMatchingGroup(group); parentGroup.getSubgroups().add(new ConnectionGroupReference(groupDetails)); groupDetails.setGroup(new ConnectionGroupReference(parentGroup)); groupDetails.apply(); // Recursive updateTree(groupDetails); } final List<ConnectionReference> connections = new ArrayList<>(parentGroup.getConnections()); parentGroup.getConnections().clear(); for (final ConnectionReference reference : connections) { final UserInterfaceMqttConnectionDetails connection = (UserInterfaceMqttConnectionDetails) reference.getReference(); final ConfiguredConnectionDetails connectionDetails = findMatchingConnection(connection); parentGroup.getConnections().add(new ConnectionReference(connectionDetails)); connectionDetails.setGroup(new ConnectionGroupReference(parentGroup)); connectionDetails.apply(); } parentGroup.apply(); } public List<ConfiguredConnectionGroupDetails> getConnectionGrops() { return connectionGroups; } public ConfiguredConnectionGroupDetails getRootGroup() { return rootGroup; } }