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" ); }