diff --git a/TODO b/TODO index edc1e53..9b9b9d0 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,5 @@ To Do: - * Fix initialisation / startup from Registry.register() + * Test for ComponentState.{init,destroy} * Registry tests * Registry doc * Implement "DriverFor" support @@ -8,7 +8,6 @@ To Do: * Automatically-updated singletons * Registry: get all component states * Document exceptions - * Not too fond of stop()/destroy() returning exceptions Other ideas (maybe later if needed): * Unregistering components diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java index 1d712dc..beecf83 100644 --- a/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java +++ b/src/main/java/info/ebenoit/ebul/cmp/ComponentRegistry.java @@ -216,10 +216,7 @@ public class ComponentRegistry // Activate autostart components if ( this.active ) { for ( int i = 0 ; i < nOld + nAdd ; i++ ) { - ComponentState cmp = this.components.get( i ); - if ( cmp.hasAutostart( ) ) { - startComponentWithFailure( cmp ); - } + autostartComponent( i ); } } @@ -246,41 +243,21 @@ public class ComponentRegistry } - public boolean destroy( ) - throws IllegalStateException - { - return this.destroy( null ); - } - - - public boolean destroy( Collection< Throwable > errors ) - throws IllegalStateException + public void destroy( ) + throws IllegalStateException , ComponentShutdownException , ComponentDestructionException { if ( this.failed ) { throw new IllegalStateException( "destroy() called on failed registry" ); } if ( !this.initialised ) { - return true; - } - if ( this.active && !this.stop( errors ) ) { - return false; + return; } + this.stop( ); for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) { - try { - ComponentState cmp = this.components.get( i ); - cmp.runLifecycleAction( LifecycleStage.DESTROY ); - cmp.setInitialised( false ); - } catch ( Throwable t ) { - if ( errors != null ) { - errors.add( t ); - } - this.failed = true; - } + destroyComponent( i ); } - this.initialised = false; - return !this.failed; } @@ -299,50 +276,26 @@ public class ComponentRegistry int nComponents = this.components.size( ); for ( int i = 0 ; i < nComponents ; i++ ) { - ComponentState cmp = this.components.get( i ); - if ( cmp.hasAutostart( ) && !cmp.isActive( ) ) { - startComponentWithFailure( cmp ); - } + autostartComponent( i ); } - this.initialised = true; } - public boolean stop( ) - throws IllegalStateException - { - return this.stop( null ); - } - - - public boolean stop( Collection< Throwable > errors ) - throws IllegalStateException + public void stop( ) + throws IllegalStateException , ComponentShutdownException { if ( this.failed ) { - throw new IllegalStateException( "destroy() called on failed registry" ); + throw new IllegalStateException( "stop() called on failed registry" ); } if ( !this.active ) { - return true; + return; } for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) { - try { - ComponentState cmp = this.components.get( i ); - if ( cmp.isActive( ) ) { - cmp.runLifecycleAction( LifecycleStage.STOP ); - cmp.setActive( false ); - } - } catch ( Throwable t ) { - if ( errors != null ) { - errors.add( t ); - } - this.failed = true; - } + stopComponent( i ); } - this.active = false; - return !this.failed; } @@ -455,42 +408,52 @@ public class ComponentRegistry } - private void initComponent( int i ) + private void initComponent( int index ) throws ComponentInitialisationException { try { - ComponentState cmp = this.components.get( i ); - try { - cmp.runLifecycleAction( LifecycleStage.INITIALISE ); - } catch ( ComponentInitialisationException e ) { - throw e; - } catch ( Throwable t ) { - throw new ComponentInitialisationException( t ); - } - cmp.setInitialised( true ); - } catch ( ComponentInitialisationException e ) { + this.components.get( index ).init( ); + } catch ( RuntimeException e ) { this.failed = true; throw e; } } - private void startComponentWithFailure( ComponentState cmp ) + private void autostartComponent( int index ) throws ComponentStartupException { try { - this.startComponent( cmp ); - } catch ( ComponentStartupException e ) { + ComponentState cmp = this.components.get( index ); + if ( cmp.hasAutostart( ) ) { + cmp.start( ); + } + } catch ( RuntimeException e ) { this.failed = true; throw e; } } - private void startComponent( ComponentState cmp ) - throws ComponentStartupException + private void stopComponent( int index ) + throws ComponentDestructionException { - cmp.runStartupAction( ); - cmp.setActive( true ); + try { + this.components.get( index ).init( ); + } catch ( RuntimeException e ) { + this.failed = true; + throw e; + } + } + + private void destroyComponent( int index ) + throws ComponentDestructionException + { + try { + this.components.get( index ).init( ); + } catch ( RuntimeException e ) { + this.failed = true; + throw e; + } } } \ No newline at end of file diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentRestartException.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentRestartException.java deleted file mode 100644 index 5d95599..0000000 --- a/src/main/java/info/ebenoit/ebul/cmp/ComponentRestartException.java +++ /dev/null @@ -1,34 +0,0 @@ -package info.ebenoit.ebul.cmp; - - -public class ComponentRestartException - extends ComponentException -{ - - private static final long serialVersionUID = -6812486665718289373L; - - - public ComponentRestartException( ) - { - super( ); - } - - - public ComponentRestartException( final String message , final Throwable cause ) - { - super( message , cause ); - } - - - public ComponentRestartException( final String message ) - { - super( message ); - } - - - public ComponentRestartException( final Throwable cause ) - { - super( cause ); - } - -} diff --git a/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java b/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java index 0f94658..ca963ed 100644 --- a/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java +++ b/src/main/java/info/ebenoit/ebul/cmp/ComponentState.java @@ -1,6 +1,7 @@ package info.ebenoit.ebul.cmp; +import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -210,7 +211,7 @@ public final class ComponentState /** - * Attempts to start the component + * Starts the component *

* This method attempts to start the component. The component must be initialised and the registry must be active. * If the component is already active, nothing is done. If it is inactive, the method will try starting the @@ -242,7 +243,7 @@ public final class ComponentState /** - * Attempts to stop the component + * Stops the component *

* This method attempts to shut down the component. If the component has reverse dependencies, it will start by * shutting them down. Then it will call the configured shutdown action, if there is one. If both steps succeed, it @@ -250,86 +251,110 @@ public final class ComponentState *

* If the method is called on an inactive component, nothing will happen. * - * @return null if the method succeeds, or an exception thrown by a shutdown action. * @throws IllegalStateException * if the registry has failed. + * @throws ComponentShutdownException + * if an error occurs during shutdown */ - public Throwable stop( ) - throws IllegalStateException + public void stop( ) + throws IllegalStateException , ComponentShutdownException { if ( this.registry.hasFailed( ) ) { throw new IllegalStateException( "registry has failed" ); } this.wasActive = this.active; if ( !this.active ) { - return null; + return; } assert this.initialised; for ( final ComponentState rdepState : this.reverseDependencies ) { - final Throwable t = rdepState.stop( ); - if ( t != null ) { - return t; - } + rdepState.stop( ); } - - try { - this.runLifecycleAction( LifecycleStage.STOP ); - } catch ( final Throwable t ) { - return t; - } - + this.runLifecycleAction( LifecycleStage.STOP ); this.active = false; - return null; } /** - * Attempts to restart the component + * Restarts the component *

* This method attempts to restart a component. It will stop the component and its reverse dependencies, then start * all components that were previously active. * * @throws IllegalStateException * if the registry has failed - * @throws ComponentRestartException - * if an error occurred either during shutdown or during startup + * @throws ComponentShutdownException + * if an error occurs during shutdown + * @throws ComponentStartupException + * if an error occurs while restarting the components */ public void restart( ) - throws IllegalStateException , ComponentRestartException + throws IllegalStateException , ComponentShutdownException , ComponentStartupException { - final Throwable t = this.stop( ); - if ( t != null ) { - throw new ComponentRestartException( "failed to stop" , t ); - } - + this.stop( ); this.startIfPreviouslyActive( ); } /** - * Sets the component's initialisation flag. Internal method used by {@link ComponentRegistry}. - * - * @param initialised - * true if the component is to be marked as initialised, false if it is to be - * marked as uninitialised + * Initialises the component + *

+ * This method attempts to initialise a component. It makes sure that all of the component's dependencies have been + * initialised, then executes the lifecycle action for the {@link LifecycleStage#INITIALISE INITIALISE} stage. + * + * @throws ComponentInitialisationException + * if an error occurs while executing an initialisation action + * @throws IllegalStateException + * if the registry has failed */ - void setInitialised( final boolean initialised ) + void init( ) + throws ComponentInitialisationException , IllegalStateException { - this.initialised = initialised; + if ( this.registry.hasFailed( ) ) { + throw new IllegalStateException( "registry has failed" ); + } + if ( this.initialised ) { + return; + } + + for ( final ComponentState depState : this.dependencies ) { + depState.init( ); + } + this.runLifecycleAction( LifecycleStage.INITIALISE ); + this.initialised = true; } /** - * Sets the component's activity flag. Internal method used by {@link ComponentRegistry}. + * Destroys the component + *

+ * This method attempts to destroy a component. It makes sure that all of the component's reverse dependencies have + * been destroyed, then executes the lifecycle action for the {@link LifecycleStage#DESTROY DESTROY} stage. * - * @param active - * true if the component is to be marked as active, false if it is to be marked - * as inactive + * @throws ComponentDestructionException + * if an error occurs while executing a destruction action + * @throws IllegalStateException + * if the registry has failed or if the component is active */ - void setActive( final boolean active ) + void destroy( ) + throws IllegalStateException , ComponentDestructionException { - this.active = active; + if ( this.registry.hasFailed( ) ) { + throw new IllegalStateException( "registry has failed" ); + } + if ( this.active ) { + throw new IllegalStateException( "attempting to destroy active component" ); + } + if ( !this.initialised ) { + return; + } + + for ( final ComponentState rdepState : this.reverseDependencies ) { + rdepState.destroy( ); + } + this.runLifecycleAction( LifecycleStage.DESTROY ); + this.initialised = false; } @@ -358,47 +383,42 @@ public final class ComponentState * * @param stage * the lifecycle stage whose action must be executed - * @throws Throwable - * any exception thrown by the lifecycle action + * @throws ComponentLifecycleException + * a component lifecycle exception corresponding to the specified stage if anything goes wrong when + * executing the action */ - void runLifecycleAction( final LifecycleStage stage ) - throws Throwable + private void runLifecycleAction( final LifecycleStage stage ) + throws ComponentLifecycleException { @SuppressWarnings( "unchecked" ) final ThrowingConsumer< Object > tc = this.lcActions[ stage.ordinal( ) ]; if ( tc != null ) { try { - tc.accept( this.component ); - } catch ( final FunctionException e ) { - throw e.getCause( ); + try { + tc.accept( this.component ); + } catch ( final FunctionException e ) { + throw e.getCause( ); + } + } catch ( Throwable t ) { + if ( t instanceof ComponentLifecycleException ) { + throw (ComponentLifecycleException) t; + } + if ( t instanceof Error ) { + throw (Error) t; + } + + try { + throw stage.exceptionType.getConstructor( Throwable.class ).newInstance( t ); + } catch ( NoSuchMethodException | InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e ) { + throw new RuntimeException( "internal error while handling lifecycle action exception" , e ); + } } } } - /** - * Internal method to run the component's startup action - *

- * Runs the action for {@link LifecycleStage#START}, wrapping exceptions that are not - * {@link ComponentStartupException} into a {@link ComponentStartupException} instance. - * - * @throws ComponentStartupException - * if the start action throws an exception - */ - void runStartupAction( ) - throws ComponentStartupException - { - try { - this.runLifecycleAction( LifecycleStage.START ); - } catch ( final ComponentStartupException e ) { - throw e; - } catch ( final Throwable t ) { - throw new ComponentStartupException( t ); - } - } - - /** * Internal component startup method. *

@@ -418,7 +438,7 @@ public final class ComponentState for ( final ComponentState depState : this.dependencies ) { depState.startNoChecks( ); } - runStartupAction( ); + this.runLifecycleAction( LifecycleStage.START ); this.active = true; } @@ -428,25 +448,19 @@ public final class ComponentState *

* Attempts to restart a component if it was active before {@link #restart()} was called. * - * @throws ComponentRestartException + * @throws ComponentStartupException * if an exception occurs while running the startup action for this component or for one of its reverse * dependencies */ private void startIfPreviouslyActive( ) - throws ComponentRestartException + throws ComponentStartupException { if ( this.wasActive ) { - try { - runStartupAction( ); - } catch ( final ComponentStartupException e ) { - throw new ComponentRestartException( "failed to restart " + this , e ); - } - + this.runLifecycleAction( LifecycleStage.START ); for ( final ComponentState rDepState : this.reverseDependencies ) { rDepState.startIfPreviouslyActive( ); } } - } } diff --git a/src/test/java/info/ebenoit/ebul/cmp/TestComponentState.java b/src/test/java/info/ebenoit/ebul/cmp/TestComponentState.java index 610b230..24b7878 100644 --- a/src/test/java/info/ebenoit/ebul/cmp/TestComponentState.java +++ b/src/test/java/info/ebenoit/ebul/cmp/TestComponentState.java @@ -1,6 +1,8 @@ package info.ebenoit.ebul.cmp; +import java.lang.reflect.Field; + import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -116,8 +118,20 @@ public class TestComponentState final boolean initialised , final boolean active ) { final ComponentState cs = new ComponentState( reg , ci ); - cs.setInitialised( initialised ); - cs.setActive( active ); + try { + if ( initialised ) { + Field f = ComponentState.class.getDeclaredField( "initialised" ); + f.setAccessible( true ); + f.setBoolean( cs , true ); + } + if ( active ) { + Field f = ComponentState.class.getDeclaredField( "active" ); + f.setAccessible( true ); + f.setBoolean( cs , true ); + } + } catch ( NoSuchFieldException | IllegalAccessException e ) { + throw new RuntimeException( e ); + } return cs; } @@ -250,27 +264,6 @@ public class TestComponentState } - /** Test: {@link ComponentState} copies lifecycle actions and can execute them */ - @Test - public void testInitialiseLCA( ) - throws Throwable - { - final LCATest lca = new LCATest( ); - final NewComponentInfo< LCATest > ci = new NewComponentInfo< LCATest >( lca ); - for ( final LifecycleStage stage : LifecycleStage.values( ) ) { - ci.setLifecycleAction( stage , ( o ) -> { - o.stage = stage; - } ); - } - final ComponentState cs = new ComponentState( this.reg , ci ); - - for ( final LifecycleStage stage : LifecycleStage.values( ) ) { - cs.runLifecycleAction( stage ); - Assert.assertSame( lca.stage , stage ); - } - } - - /** Test: {@link ComponentState#toString()} on anonymous components */ @Test public void testToStringAnon( ) @@ -327,8 +320,7 @@ public class TestComponentState public void testStartFailedRegistry( ) { final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); - final ComponentState cs = new ComponentState( this.reg , ci ); - cs.setInitialised( true ); + final ComponentState cs = makeState( ci , reg , true , false ); this.reg.failed = true; cs.start( ); } @@ -339,8 +331,7 @@ public class TestComponentState public void testStartInactiveRegistry( ) { final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); - final ComponentState cs = new ComponentState( this.reg , ci ); - cs.setInitialised( true ); + final ComponentState cs = makeState( ci , reg , true , false ); cs.start( ); } @@ -350,8 +341,7 @@ public class TestComponentState public void testStartNoAction( ) { final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); - final ComponentState cs = new ComponentState( this.reg , ci ); - cs.setInitialised( true ); + final ComponentState cs = makeState( ci , reg , true , false ); this.reg.active = true; cs.start( ); Assert.assertTrue( cs.isActive( ) ); @@ -367,8 +357,7 @@ public class TestComponentState .setLifecycleAction( LifecycleStage.START , ( o ) -> { o.stage = LifecycleStage.START; } ); - final ComponentState cs = new ComponentState( this.reg , ci ); - cs.setInitialised( true ); + final ComponentState cs = makeState( ci , reg , true , false ); this.reg.active = true; cs.start( ); Assert.assertSame( LifecycleStage.START , lcaTest.stage ); @@ -473,7 +462,7 @@ public class TestComponentState final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); this.reg.active = true; - Assert.assertNull( cs.stop( ) ); + cs.stop( ); Assert.assertFalse( cs.isActive( ) ); } @@ -489,25 +478,31 @@ public class TestComponentState } ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); this.reg.active = true; - Assert.assertNull( cs.stop( ) ); + cs.stop( ); Assert.assertFalse( cs.isActive( ) ); Assert.assertSame( LifecycleStage.STOP , lcaTest.stage ); } - /** Test: {@link ComponentState#stop()} returns an exception thrown by the configured stop action */ + /** Test: {@link ComponentState#stop()} transmits an exception thrown by the configured stop action */ @Test public void testStopActionFailure( ) { - final Exception failure = new Exception( ); + final ComponentShutdownException failure = new ComponentShutdownException( ); final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ) // .setLifecycleAction( LifecycleStage.STOP , ( o ) -> { throw failure; } ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); this.reg.active = true; - Assert.assertSame( failure , cs.stop( ) ); - Assert.assertTrue( cs.isActive( ) ); + try { + cs.stop( ); + } catch ( ComponentShutdownException e ) { + Assert.assertSame( failure , e ); + Assert.assertTrue( cs.isActive( ) ); + return; + } + Assert.fail( "expected ComponentShutdownException" ); } @@ -523,7 +518,7 @@ public class TestComponentState cs2.addDependency( cs1 ); this.reg.active = true; - Assert.assertNull( cs1.stop( ) ); + cs1.stop( ); Assert.assertFalse( cs2.isActive( ) ); } @@ -565,7 +560,7 @@ public class TestComponentState } - /** Test: {@link ComponentState#restart()} throws a {@link ComponentRestartException} when stop fails */ + /** Test: {@link ComponentState#restart()} throws a {@link ComponentShutdownException} when stop fails */ @Test public void testRestartWhenStopFails( ) { @@ -578,20 +573,20 @@ public class TestComponentState this.reg.active = true; try { cs.restart( ); - } catch ( final ComponentRestartException e ) { + } catch ( final ComponentShutdownException e ) { Assert.assertSame( failure , e.getCause( ) ); Assert.assertTrue( cs.isActive( ) ); return; } - Assert.fail( "no ComponentRestartException" ); + Assert.fail( "no ComponentShutdownException" ); } - /** Test: {@link ComponentState#restart()} throws a {@link ComponentRestartException} when start fails */ + /** Test: {@link ComponentState#restart()} throws a {@link ComponentStartupException} when start fails */ @Test public void testRestartWhenStartFails( ) { - final ComponentStartupException failure = new ComponentStartupException( ); + final Exception failure = new Exception( ); final NewComponentInfo< RestartTest > ci = RestartTest.getDef( ); ci.setLifecycleAction( LifecycleStage.START , ( o ) -> { throw failure; @@ -600,12 +595,12 @@ public class TestComponentState this.reg.active = true; try { cs.restart( ); - } catch ( final ComponentRestartException e ) { + } catch ( final ComponentStartupException e ) { Assert.assertSame( failure , e.getCause( ) ); Assert.assertFalse( cs.isActive( ) ); return; } - Assert.fail( "no ComponentRestartException" ); + Assert.fail( "no ComponentStartupException" ); }