From b731be86e3b61900bc32f6bc234d991cb51bf84e Mon Sep 17 00:00:00 2001 From: Robert von Burg Date: Tue, 20 Jan 2015 19:43:51 +0100 Subject: [PATCH] [Bugfix] fixed bug where controllers aren't properly stopped - this occurred for controllers which had dependencies of other dependencies --- .../agent/impl/ComponentContainerImpl.java | 157 +++++------------- .../impl/ComponentContainerStateHandler.java | 140 ++++++++++++++++ .../agent/impl/ComponentController.java | 4 + .../impl/ComponentDependencyAnalyzer.java | 67 ++++---- .../ControllerDependencyTest.java | 55 +++++- 5 files changed, 266 insertions(+), 157 deletions(-) create mode 100644 li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerStateHandler.java diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java index 0ae5554cd..9fb496679 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerImpl.java @@ -52,6 +52,8 @@ public class ComponentContainerImpl implements ComponentContainer { private StrolchConfiguration strolchConfiguration; private ComponentState state; + private ComponentContainerStateHandler containerStateHandler; + public ComponentContainerImpl(StrolchAgent agent) { this.agent = agent; this.state = ComponentState.UNDEFINED; @@ -151,103 +153,6 @@ public class ComponentContainerImpl implements ComponentContainer { } } - private void initialize(Set controllers) { - - // initialize each component - for (ComponentController controller : controllers) { - if (controller.getState() == ComponentState.INITIALIZED) - continue; - - StrolchComponent component = controller.getComponent(); - String componentName = component.getName(); - ComponentConfiguration componentConfiguration = this.strolchConfiguration - .getComponentConfiguration(componentName); - - String msg = "Initializing component {0}..."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, componentName)); - component.initialize(componentConfiguration); - } - - // initialize direct downstream components - Set dependencies = this.dependencyAnalyzer - .collectDirectDownstreamDependencies(controllers); - if (!dependencies.isEmpty()) - initialize(dependencies); - } - - private void start(Set controllers) { - - // Start each component - for (ComponentController controller : controllers) { - if (controller.getState() == ComponentState.STARTED) - continue; - - StrolchComponent component = controller.getComponent(); - String msg = "Starting component {0}..."; //$NON-NLS-1$ - String componentName = component.getName(); - logger.info(MessageFormat.format(msg, componentName)); - component.start(); - } - - // Start direct downstream components - Set dependencies = this.dependencyAnalyzer - .collectDirectDownstreamDependencies(controllers); - if (!dependencies.isEmpty()) - start(dependencies); - } - - private void stop(Set controllers) { - - // Stop each component - for (ComponentController controller : controllers) { - if (controller.getState() == ComponentState.STOPPED) - continue; - - StrolchComponent component = controller.getComponent(); - String msg = "Stopping component {0}..."; //$NON-NLS-1$ - String componentName = component.getName(); - logger.info(MessageFormat.format(msg, componentName)); - try { - component.stop(); - } catch (Exception e) { - msg = "Failed to stop component {0} due to {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, componentName, e.getMessage()); - logger.error(msg, e); - } - } - - // Stop direct upstream components - Set dependencies = this.dependencyAnalyzer.collectDirectUpstreamDependencies(controllers); - if (!dependencies.isEmpty()) - stop(dependencies); - } - - private void destroy(Set controllers) { - - // Destroy each component - for (ComponentController controller : controllers) { - if (controller.getState() == ComponentState.DESTROYED) - continue; - - StrolchComponent component = controller.getComponent(); - String msg = "Destroying component {0}..."; //$NON-NLS-1$ - String componentName = component.getName(); - logger.info(MessageFormat.format(msg, componentName)); - try { - component.destroy(); - } catch (Exception e) { - msg = "Failed to destroy component {0} due to {1}"; //$NON-NLS-1$ - msg = MessageFormat.format(msg, componentName, e.getMessage()); - logger.error(msg, e); - } - } - - // Destroy direct upstream components - Set dependencies = this.dependencyAnalyzer.collectDirectUpstreamDependencies(controllers); - if (!dependencies.isEmpty()) - destroy(dependencies); - } - public void setup(StrolchConfiguration strolchConfiguration) { // set the application locale @@ -278,68 +183,84 @@ public class ComponentContainerImpl implements ComponentContainer { this.controllerMap = controllerMap; this.strolchConfiguration = strolchConfiguration; + // and configure the state handler + this.containerStateHandler = new ComponentContainerStateHandler(this.dependencyAnalyzer, + this.strolchConfiguration); + this.state = this.state.validateStateChange(ComponentState.SETUP); - String msg = "Strolch Container setup with {0} components."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.componentMap.size())); + String msg = "{0}:{1} Strolch Container setup with {2} components."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.componentMap.size())); } public void initialize(StrolchConfiguration strolchConfiguration) { // now we can initialize the components - logger.info("Initializing strolch components..."); //$NON-NLS-1$ + String msg = "{0}:{1} Initializing {2} Strolch Components..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); Set rootUpstreamComponents = this.dependencyAnalyzer.findRootUpstreamComponents(); - initialize(rootUpstreamComponents); + containerStateHandler.initialize(rootUpstreamComponents); this.state = this.state.validateStateChange(ComponentState.INITIALIZED); - String msg = "All {0} Strolch Components have been initialized."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.controllerMap.size())); + msg = "{0}:{1} All {2} Strolch Components have been initialized."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); } public void start() { - logger.info("Starting strolch components..."); //$NON-NLS-1$ + String msg = "{0}:{1} Starting {2} Strolch Components..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); Set rootUpstreamComponents = this.dependencyAnalyzer.findRootUpstreamComponents(); - start(rootUpstreamComponents); + containerStateHandler.start(rootUpstreamComponents); this.state = this.state.validateStateChange(ComponentState.STARTED); - String msg = "All {0} Strolch Components started. {1}:{2} is now ready to be used. Have fun =))"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.controllerMap.size(), getAgent().getApplicationName(), getAgent() - .getStrolchConfiguration().getRuntimeConfiguration().getEnvironment())); + msg = "{0}:{1} All {2} Strolch Components started. Strolch is now ready to be used. Have fun =))"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); logger.info(MessageFormat.format("System: {0}", SystemHelper.asString())); //$NON-NLS-1$ logger.info(MessageFormat.format("Memory: {0}", SystemHelper.getMemorySummary())); //$NON-NLS-1$ } public void stop() { - logger.info("Stopping strolch components..."); //$NON-NLS-1$ + String msg = "{0}:{1} Stopping {2} Strolch Components..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); if (this.dependencyAnalyzer == null) { - logger.info("Strolch Components have been stopped."); //$NON-NLS-1$ + logger.info("Strolch was not yet setup, nothing to stop"); //$NON-NLS-1$ } else { Set rootUpstreamComponents = this.dependencyAnalyzer.findRootDownstreamComponents(); - stop(rootUpstreamComponents); + containerStateHandler.stop(rootUpstreamComponents); this.state = this.state.validateStateChange(ComponentState.STOPPED); - String msg = "All {0} Strolch Components have been stopped."; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.controllerMap.size())); + msg = "{0}:{1} All {2} Strolch Components have been stopped."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); } } public void destroy() { - logger.info("Destroying strolch components..."); //$NON-NLS-1$ + String msg = "{0}:{1} Destroying {2} Strolch Components..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); if (this.dependencyAnalyzer == null) { - logger.info("Strolch Components have been destroyed."); //$NON-NLS-1$ + logger.info("Strolch was not yet setup, nothing to destroy"); //$NON-NLS-1$ } else { Set rootUpstreamComponents = this.dependencyAnalyzer.findRootDownstreamComponents(); - destroy(rootUpstreamComponents); + containerStateHandler.destroy(rootUpstreamComponents); this.state = this.state.validateStateChange(ComponentState.DESTROYED); - String msg = "All {0} Strolch Components have been destroyed!"; //$NON-NLS-1$ - logger.info(MessageFormat.format(msg, this.controllerMap.size())); + msg = "{0}:{1} All {2} Strolch Components have been destroyed!"; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, getAgent().getApplicationName(), getAgent().getStrolchConfiguration() + .getRuntimeConfiguration().getEnvironment(), this.controllerMap.size())); this.controllerMap.clear(); this.componentMap.clear(); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerStateHandler.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerStateHandler.java new file mode 100644 index 000000000..d4bb4f083 --- /dev/null +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentContainerStateHandler.java @@ -0,0 +1,140 @@ +/* + * Copyright 2013 Robert von Burg + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package li.strolch.agent.impl; + +import java.text.MessageFormat; +import java.util.Set; + +import li.strolch.agent.api.ComponentState; +import li.strolch.agent.api.StrolchComponent; +import li.strolch.runtime.configuration.ComponentConfiguration; +import li.strolch.runtime.configuration.StrolchConfiguration; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author Robert von Burg + */ +public class ComponentContainerStateHandler { + + private static final Logger logger = LoggerFactory.getLogger(ComponentContainerStateHandler.class); + private ComponentDependencyAnalyzer dependencyAnalyzer; + private StrolchConfiguration strolchConfiguration; + + public ComponentContainerStateHandler(ComponentDependencyAnalyzer dependencyAnalyzer, + StrolchConfiguration strolchConfiguration) { + this.dependencyAnalyzer = dependencyAnalyzer; + this.strolchConfiguration = strolchConfiguration; + } + + public void initialize(Set controllers) { + + // initialize each component + for (ComponentController controller : controllers) { + if (controller.getState() == ComponentState.INITIALIZED) + continue; + + StrolchComponent component = controller.getComponent(); + String componentName = component.getName(); + ComponentConfiguration componentConfiguration = this.strolchConfiguration + .getComponentConfiguration(componentName); + + String msg = "Initializing component {0}..."; //$NON-NLS-1$ + logger.info(MessageFormat.format(msg, componentName)); + component.initialize(componentConfiguration); + } + + // initialize direct downstream components + Set dependencies = this.dependencyAnalyzer + .collectDirectDownstreamDependencies(controllers); + if (!dependencies.isEmpty()) + initialize(dependencies); + } + + public void start(Set controllers) { + + // Start each component + for (ComponentController controller : controllers) { + if (controller.getState() == ComponentState.STARTED) + continue; + + StrolchComponent component = controller.getComponent(); + String msg = "Starting component {0}..."; //$NON-NLS-1$ + String componentName = component.getName(); + logger.info(MessageFormat.format(msg, componentName)); + component.start(); + } + + // Start direct downstream components + Set dependencies = this.dependencyAnalyzer + .collectDirectDownstreamDependencies(controllers); + if (!dependencies.isEmpty()) + start(dependencies); + } + + public void stop(Set controllers) { + + // Stop each component + for (ComponentController controller : controllers) { + if (controller.getState() == ComponentState.STOPPED) + continue; + + StrolchComponent component = controller.getComponent(); + String msg = "Stopping component {0}..."; //$NON-NLS-1$ + String componentName = component.getName(); + logger.info(MessageFormat.format(msg, componentName)); + try { + component.stop(); + } catch (Exception e) { + msg = "Failed to stop component {0} due to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, componentName, e.getMessage()); + logger.error(msg, e); + } + } + + // Stop direct upstream components + Set dependencies = this.dependencyAnalyzer.collectDirectUpstreamDependencies(controllers); + if (!dependencies.isEmpty()) + stop(dependencies); + } + + public void destroy(Set controllers) { + + // Destroy each component + for (ComponentController controller : controllers) { + if (controller.getState() == ComponentState.DESTROYED) + continue; + + StrolchComponent component = controller.getComponent(); + String msg = "Destroying component {0}..."; //$NON-NLS-1$ + String componentName = component.getName(); + logger.info(MessageFormat.format(msg, componentName)); + try { + component.destroy(); + } catch (Exception e) { + msg = "Failed to destroy component {0} due to {1}"; //$NON-NLS-1$ + msg = MessageFormat.format(msg, componentName, e.getMessage()); + logger.error(msg, e); + } + } + + // Destroy direct upstream components + Set dependencies = this.dependencyAnalyzer.collectDirectUpstreamDependencies(controllers); + if (!dependencies.isEmpty()) + destroy(dependencies); + } +} diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentController.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentController.java index d2eaf2929..d60d3a6fd 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentController.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentController.java @@ -45,6 +45,10 @@ public class ComponentController { return this.component; } + public String getComponentName() { + return this.component.getName(); + } + public Set getUpstreamDependencies() { return new HashSet<>(this.upstreamDependencies); } diff --git a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java index c312f7d35..f14863e4b 100644 --- a/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java +++ b/li.strolch.agent/src/main/java/li/strolch/agent/impl/ComponentDependencyAnalyzer.java @@ -17,7 +17,6 @@ package li.strolch.agent.impl; import java.text.MessageFormat; import java.util.HashSet; -import java.util.Iterator; import java.util.Map; import java.util.Set; @@ -62,6 +61,15 @@ public class ComponentDependencyAnalyzer { return controllers; } + private Set collectAllUpstreamDependencies(ComponentController controller) { + Set upstreamDependencies = new HashSet<>(controller.getUpstreamDependencies()); + for (ComponentController upstream : upstreamDependencies) { + upstreamDependencies.addAll(collectAllUpstreamDependencies(upstream)); + } + + return upstreamDependencies; + } + public Set collectDirectUpstreamDependencies(Set controllers) { Set directUpstreamDependencies = new HashSet<>(); @@ -72,27 +80,25 @@ public class ComponentDependencyAnalyzer { directUpstreamDependencies.addAll(upstreamDependencies); } - // prune dependencies which are a dependency of any of these dependencies - for (ComponentController controller : controllers) { - Set upstreamDependencies = controller.getUpstreamDependencies(); - - for (ComponentController upstream : upstreamDependencies) { - - Iterator iter = directUpstreamDependencies.iterator(); - while (iter.hasNext()) { - ComponentController possibleTransitiveDependency = iter.next(); - if (upstream.hasUpstreamDependency(possibleTransitiveDependency)) - continue; - - if (possibleTransitiveDependency.hasTransitiveUpstreamDependency(upstream)) - iter.remove(); - } - } + // prune any controllers which are an upstream dependency of any of these dependencies + Set tmp = new HashSet<>(directUpstreamDependencies); + for (ComponentController controller : tmp) { + Set upstreamDeps = collectAllUpstreamDependencies(controller); + directUpstreamDependencies.removeAll(upstreamDeps); } return directUpstreamDependencies; } + private Set collectAllDownstreamDependencies(ComponentController controller) { + Set downstreamDependencies = new HashSet<>(controller.getDownstreamDependencies()); + for (ComponentController downstream : downstreamDependencies) { + downstreamDependencies.addAll(collectAllDownstreamDependencies(downstream)); + } + + return downstreamDependencies; + } + public Set collectDirectDownstreamDependencies(Set controllers) { Set directDownstreamDependencies = new HashSet<>(); @@ -103,22 +109,11 @@ public class ComponentDependencyAnalyzer { directDownstreamDependencies.addAll(downstreamDependencies); } - // prune dependencies which are a dependency of any of these dependencies - for (ComponentController controller : controllers) { - Set downstreamDependencies = controller.getDownstreamDependencies(); - - for (ComponentController downstream : downstreamDependencies) { - - Iterator iter = directDownstreamDependencies.iterator(); - while (iter.hasNext()) { - ComponentController possibleTransitiveDependency = iter.next(); - if (downstream.hasUpstreamDependency(possibleTransitiveDependency)) - continue; - - if (possibleTransitiveDependency.hasTransitiveDownstreamDependency(downstream)) - iter.remove(); - } - } + // prune any controllers which are a downstream dependency of any of these dependencies + Set tmp = new HashSet<>(directDownstreamDependencies); + for (ComponentController controller : tmp) { + Set downstreamDeps = collectAllDownstreamDependencies(controller); + directDownstreamDependencies.removeAll(downstreamDeps); } return directDownstreamDependencies; @@ -141,17 +136,17 @@ public class ComponentDependencyAnalyzer { } } - logDependencies(0, findRootUpstreamComponents()); + logDependencies(1, findRootUpstreamComponents()); } /** * @param components */ private void logDependencies(int depth, Set components) { - if (depth == 0) { + if (depth == 1) { logger.info("Dependency tree:"); //$NON-NLS-1$ } - String inset = StringHelper.normalizeLength(" ", depth * 2, false, ' '); //$NON-NLS-1$ + String inset = StringHelper.normalizeLength("", depth * 2, false, ' '); //$NON-NLS-1$ for (ComponentController controller : components) { logger.info(inset + controller.getComponent().getName() + ": " + controller.getComponent().getClass().getName()); diff --git a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java index c2d253ece..b38515261 100644 --- a/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java +++ b/li.strolch.agent/src/test/java/li/strolch/runtime/configuration/ControllerDependencyTest.java @@ -20,13 +20,16 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import java.io.File; +import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; +import li.strolch.agent.api.ComponentState; import li.strolch.agent.api.StrolchComponent; import li.strolch.agent.impl.ComponentContainerImpl; +import li.strolch.agent.impl.ComponentContainerStateHandler; import li.strolch.agent.impl.ComponentController; import li.strolch.agent.impl.ComponentDependencyAnalyzer; @@ -169,9 +172,6 @@ public class ControllerDependencyTest { // - File rootPathF = new File("src/test/resources/configtest"); - this.strolchConfiguration = ConfigurationParser.parseConfiguration("dev", rootPathF); - this.controllerMap = new HashMap<>(); this.controllerMap.put("2", this.con2); @@ -195,6 +195,16 @@ public class ControllerDependencyTest { this.controllerMap.put("C2", this.conC2); this.controllerMap.put("D2", this.conD2); + // + + File rootPathF = new File("src/test/resources/configtest"); + this.strolchConfiguration = ConfigurationParser.parseConfiguration("dev", rootPathF); + for (ComponentController controller : this.controllerMap.values()) { + ComponentConfiguration componentConfiguration = new ComponentConfiguration( + this.strolchConfiguration.getRuntimeConfiguration(), controller.getComponentName(), null, null, + null, null); + this.strolchConfiguration.addConfiguration(controller.getComponentName(), componentConfiguration); + } } private void assertModel() { @@ -568,4 +578,43 @@ public class ControllerDependencyTest { assertTrue(transitiveUpstreamDependency2.hasTransitiveDownstreamDependency(controller)); } + @Test + public void shouldRunThroughModelStates() { + + ComponentDependencyAnalyzer dependencyAnalyzer = new ComponentDependencyAnalyzer(this.strolchConfiguration, + this.controllerMap); + ComponentContainerStateHandler stateHandler = new ComponentContainerStateHandler(dependencyAnalyzer, + strolchConfiguration); + + for (ComponentController controller : this.controllerMap.values()) { + assertEquals(ComponentState.UNDEFINED, controller.getState()); + + ComponentConfiguration componentConfiguration = this.strolchConfiguration + .getComponentConfiguration(controller.getComponentName()); + controller.getComponent().setup(componentConfiguration); + } + + Set rootUpstreamComponents = dependencyAnalyzer.findRootUpstreamComponents(); + + assertState(ComponentState.SETUP, this.controllerMap.values()); + + stateHandler.initialize(rootUpstreamComponents); + assertState(ComponentState.INITIALIZED, this.controllerMap.values()); + + stateHandler.start(rootUpstreamComponents); + assertState(ComponentState.STARTED, this.controllerMap.values()); + + Set rootDownstreamComponents = dependencyAnalyzer.findRootDownstreamComponents(); + stateHandler.stop(rootDownstreamComponents); + assertState(ComponentState.STOPPED, this.controllerMap.values()); + + stateHandler.destroy(rootDownstreamComponents); + assertState(ComponentState.DESTROYED, this.controllerMap.values()); + } + + private void assertState(ComponentState expectedState, Collection collection) { + for (ComponentController controller : collection) { + assertEquals(expectedState, controller.getState()); + } + } }