Component state and registry - fixes and refactoring

* Errors during shutdown or destruction will be thrown as exceptions.
Returning the collection of errors was clumsy, at best - if something
goes wrong, the registry has failed anyway.
* Recursive initialisation and destruction methods in ComponentState
* Use ComponentState's methods while initialising, starting up, shutting
down or destroying the registry.
This commit is contained in:
Emmanuel BENOîT 2015-09-17 10:18:09 +02:00
parent 7f919349e9
commit 81ea62047e
5 changed files with 174 additions and 237 deletions

3
TODO
View file

@ -1,5 +1,5 @@
To Do: To Do:
* Fix initialisation / startup from Registry.register() * Test for ComponentState.{init,destroy}
* Registry tests * Registry tests
* Registry doc * Registry doc
* Implement "DriverFor" support * Implement "DriverFor" support
@ -8,7 +8,6 @@ To Do:
* Automatically-updated singletons * Automatically-updated singletons
* Registry: get all component states * Registry: get all component states
* Document exceptions * Document exceptions
* Not too fond of stop()/destroy() returning exceptions
Other ideas (maybe later if needed): Other ideas (maybe later if needed):
* Unregistering components * Unregistering components

View file

@ -216,10 +216,7 @@ public class ComponentRegistry
// Activate autostart components // Activate autostart components
if ( this.active ) { if ( this.active ) {
for ( int i = 0 ; i < nOld + nAdd ; i++ ) { for ( int i = 0 ; i < nOld + nAdd ; i++ ) {
ComponentState cmp = this.components.get( i ); autostartComponent( i );
if ( cmp.hasAutostart( ) ) {
startComponentWithFailure( cmp );
}
} }
} }
@ -246,41 +243,21 @@ public class ComponentRegistry
} }
public boolean destroy( ) public void destroy( )
throws IllegalStateException throws IllegalStateException , ComponentShutdownException , ComponentDestructionException
{
return this.destroy( null );
}
public boolean destroy( Collection< Throwable > errors )
throws IllegalStateException
{ {
if ( this.failed ) { if ( this.failed ) {
throw new IllegalStateException( "destroy() called on failed registry" ); throw new IllegalStateException( "destroy() called on failed registry" );
} }
if ( !this.initialised ) { if ( !this.initialised ) {
return true; return;
}
if ( this.active && !this.stop( errors ) ) {
return false;
} }
this.stop( );
for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) { for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) {
try { destroyComponent( i );
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;
}
} }
this.initialised = false; this.initialised = false;
return !this.failed;
} }
@ -299,50 +276,26 @@ public class ComponentRegistry
int nComponents = this.components.size( ); int nComponents = this.components.size( );
for ( int i = 0 ; i < nComponents ; i++ ) { for ( int i = 0 ; i < nComponents ; i++ ) {
ComponentState cmp = this.components.get( i ); autostartComponent( i );
if ( cmp.hasAutostart( ) && !cmp.isActive( ) ) {
startComponentWithFailure( cmp );
}
} }
this.initialised = true; this.initialised = true;
} }
public boolean stop( ) public void stop( )
throws IllegalStateException throws IllegalStateException , ComponentShutdownException
{
return this.stop( null );
}
public boolean stop( Collection< Throwable > errors )
throws IllegalStateException
{ {
if ( this.failed ) { if ( this.failed ) {
throw new IllegalStateException( "destroy() called on failed registry" ); throw new IllegalStateException( "stop() called on failed registry" );
} }
if ( !this.active ) { if ( !this.active ) {
return true; return;
} }
for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) { for ( int i = this.components.size( ) - 1 ; i >= 0 ; i-- ) {
try { stopComponent( i );
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;
}
} }
this.active = false; 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 throws ComponentInitialisationException
{ {
try { try {
ComponentState cmp = this.components.get( i ); this.components.get( index ).init( );
try { } catch ( RuntimeException e ) {
cmp.runLifecycleAction( LifecycleStage.INITIALISE );
} catch ( ComponentInitialisationException e ) {
throw e;
} catch ( Throwable t ) {
throw new ComponentInitialisationException( t );
}
cmp.setInitialised( true );
} catch ( ComponentInitialisationException e ) {
this.failed = true; this.failed = true;
throw e; throw e;
} }
} }
private void startComponentWithFailure( ComponentState cmp ) private void autostartComponent( int index )
throws ComponentStartupException throws ComponentStartupException
{ {
try { try {
this.startComponent( cmp ); ComponentState cmp = this.components.get( index );
} catch ( ComponentStartupException e ) { if ( cmp.hasAutostart( ) ) {
cmp.start( );
}
} catch ( RuntimeException e ) {
this.failed = true; this.failed = true;
throw e; throw e;
} }
} }
private void startComponent( ComponentState cmp ) private void stopComponent( int index )
throws ComponentStartupException throws ComponentDestructionException
{ {
cmp.runStartupAction( ); try {
cmp.setActive( true ); 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;
}
} }
} }

View file

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

View file

@ -1,6 +1,7 @@
package info.ebenoit.ebul.cmp; package info.ebenoit.ebul.cmp;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -210,7 +211,7 @@ public final class ComponentState
/** /**
* Attempts to start the component * Starts the component
* <p> * <p>
* This method attempts to start the component. The component must be initialised and the registry must be active. * 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 * 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
* <p> * <p>
* This method attempts to shut down the component. If the component has reverse dependencies, it will start by * 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 * 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
* <p> * <p>
* If the method is called on an inactive component, nothing will happen. * If the method is called on an inactive component, nothing will happen.
* *
* @return <code>null</code> if the method succeeds, or an exception thrown by a shutdown action.
* @throws IllegalStateException * @throws IllegalStateException
* if the registry has failed. * if the registry has failed.
* @throws ComponentShutdownException
* if an error occurs during shutdown
*/ */
public Throwable stop( ) public void stop( )
throws IllegalStateException throws IllegalStateException , ComponentShutdownException
{ {
if ( this.registry.hasFailed( ) ) { if ( this.registry.hasFailed( ) ) {
throw new IllegalStateException( "registry has failed" ); throw new IllegalStateException( "registry has failed" );
} }
this.wasActive = this.active; this.wasActive = this.active;
if ( !this.active ) { if ( !this.active ) {
return null; return;
} }
assert this.initialised; assert this.initialised;
for ( final ComponentState rdepState : this.reverseDependencies ) { for ( final ComponentState rdepState : this.reverseDependencies ) {
final Throwable t = rdepState.stop( ); rdepState.stop( );
if ( t != null ) {
return t;
}
} }
this.runLifecycleAction( LifecycleStage.STOP );
try {
this.runLifecycleAction( LifecycleStage.STOP );
} catch ( final Throwable t ) {
return t;
}
this.active = false; this.active = false;
return null;
} }
/** /**
* Attempts to restart the component * Restarts the component
* <p> * <p>
* This method attempts to restart a component. It will stop the component and its reverse dependencies, then start * This method attempts to restart a component. It will stop the component and its reverse dependencies, then start
* all components that were previously active. * all components that were previously active.
* *
* @throws IllegalStateException * @throws IllegalStateException
* if the registry has failed * if the registry has failed
* @throws ComponentRestartException * @throws ComponentShutdownException
* if an error occurred either during shutdown or during startup * if an error occurs during shutdown
* @throws ComponentStartupException
* if an error occurs while restarting the components
*/ */
public void restart( ) public void restart( )
throws IllegalStateException , ComponentRestartException throws IllegalStateException , ComponentShutdownException , ComponentStartupException
{ {
final Throwable t = this.stop( ); this.stop( );
if ( t != null ) {
throw new ComponentRestartException( "failed to stop" , t );
}
this.startIfPreviouslyActive( ); this.startIfPreviouslyActive( );
} }
/** /**
* Sets the component's initialisation flag. Internal method used by {@link ComponentRegistry}. * Initialises the component
* * <p>
* @param initialised * This method attempts to initialise a component. It makes sure that all of the component's dependencies have been
* <code>true</code> if the component is to be marked as initialised, <code>false</code> if it is to be * initialised, then executes the lifecycle action for the {@link LifecycleStage#INITIALISE INITIALISE} stage.
* marked as uninitialised *
* @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
* <p>
* 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 * @throws ComponentDestructionException
* <code>true</code> if the component is to be marked as active, <code>false</code> if it is to be marked * if an error occurs while executing a destruction action
* as inactive * @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 * @param stage
* the lifecycle stage whose action must be executed * the lifecycle stage whose action must be executed
* @throws Throwable * @throws ComponentLifecycleException
* any exception thrown by the lifecycle action * a component lifecycle exception corresponding to the specified stage if anything goes wrong when
* executing the action
*/ */
void runLifecycleAction( final LifecycleStage stage ) private void runLifecycleAction( final LifecycleStage stage )
throws Throwable throws ComponentLifecycleException
{ {
@SuppressWarnings( "unchecked" ) @SuppressWarnings( "unchecked" )
final ThrowingConsumer< Object > tc = this.lcActions[ stage.ordinal( ) ]; final ThrowingConsumer< Object > tc = this.lcActions[ stage.ordinal( ) ];
if ( tc != null ) { if ( tc != null ) {
try { try {
tc.accept( this.component ); try {
} catch ( final FunctionException e ) { tc.accept( this.component );
throw e.getCause( ); } 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
* <p>
* 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. * Internal component startup method.
* <p> * <p>
@ -418,7 +438,7 @@ public final class ComponentState
for ( final ComponentState depState : this.dependencies ) { for ( final ComponentState depState : this.dependencies ) {
depState.startNoChecks( ); depState.startNoChecks( );
} }
runStartupAction( ); this.runLifecycleAction( LifecycleStage.START );
this.active = true; this.active = true;
} }
@ -428,25 +448,19 @@ public final class ComponentState
* <p> * <p>
* Attempts to restart a component if it was active before {@link #restart()} was called. * 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 * if an exception occurs while running the startup action for this component or for one of its reverse
* dependencies * dependencies
*/ */
private void startIfPreviouslyActive( ) private void startIfPreviouslyActive( )
throws ComponentRestartException throws ComponentStartupException
{ {
if ( this.wasActive ) { if ( this.wasActive ) {
try { this.runLifecycleAction( LifecycleStage.START );
runStartupAction( );
} catch ( final ComponentStartupException e ) {
throw new ComponentRestartException( "failed to restart " + this , e );
}
for ( final ComponentState rDepState : this.reverseDependencies ) { for ( final ComponentState rDepState : this.reverseDependencies ) {
rDepState.startIfPreviouslyActive( ); rDepState.startIfPreviouslyActive( );
} }
} }
} }
} }

View file

@ -1,6 +1,8 @@
package info.ebenoit.ebul.cmp; package info.ebenoit.ebul.cmp;
import java.lang.reflect.Field;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -116,8 +118,20 @@ public class TestComponentState
final boolean initialised , final boolean active ) final boolean initialised , final boolean active )
{ {
final ComponentState cs = new ComponentState( reg , ci ); final ComponentState cs = new ComponentState( reg , ci );
cs.setInitialised( initialised ); try {
cs.setActive( active ); 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; 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: {@link ComponentState#toString()} on anonymous components */
@Test @Test
public void testToStringAnon( ) public void testToStringAnon( )
@ -327,8 +320,7 @@ public class TestComponentState
public void testStartFailedRegistry( ) public void testStartFailedRegistry( )
{ {
final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) );
final ComponentState cs = new ComponentState( this.reg , ci ); final ComponentState cs = makeState( ci , reg , true , false );
cs.setInitialised( true );
this.reg.failed = true; this.reg.failed = true;
cs.start( ); cs.start( );
} }
@ -339,8 +331,7 @@ public class TestComponentState
public void testStartInactiveRegistry( ) public void testStartInactiveRegistry( )
{ {
final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) );
final ComponentState cs = new ComponentState( this.reg , ci ); final ComponentState cs = makeState( ci , reg , true , false );
cs.setInitialised( true );
cs.start( ); cs.start( );
} }
@ -350,8 +341,7 @@ public class TestComponentState
public void testStartNoAction( ) public void testStartNoAction( )
{ {
final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) );
final ComponentState cs = new ComponentState( this.reg , ci ); final ComponentState cs = makeState( ci , reg , true , false );
cs.setInitialised( true );
this.reg.active = true; this.reg.active = true;
cs.start( ); cs.start( );
Assert.assertTrue( cs.isActive( ) ); Assert.assertTrue( cs.isActive( ) );
@ -367,8 +357,7 @@ public class TestComponentState
.setLifecycleAction( LifecycleStage.START , ( o ) -> { .setLifecycleAction( LifecycleStage.START , ( o ) -> {
o.stage = LifecycleStage.START; o.stage = LifecycleStage.START;
} ); } );
final ComponentState cs = new ComponentState( this.reg , ci ); final ComponentState cs = makeState( ci , reg , true , false );
cs.setInitialised( true );
this.reg.active = true; this.reg.active = true;
cs.start( ); cs.start( );
Assert.assertSame( LifecycleStage.START , lcaTest.stage ); Assert.assertSame( LifecycleStage.START , lcaTest.stage );
@ -473,7 +462,7 @@ public class TestComponentState
final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ); final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) );
final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true );
this.reg.active = true; this.reg.active = true;
Assert.assertNull( cs.stop( ) ); cs.stop( );
Assert.assertFalse( cs.isActive( ) ); Assert.assertFalse( cs.isActive( ) );
} }
@ -489,25 +478,31 @@ public class TestComponentState
} ); } );
final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true );
this.reg.active = true; this.reg.active = true;
Assert.assertNull( cs.stop( ) ); cs.stop( );
Assert.assertFalse( cs.isActive( ) ); Assert.assertFalse( cs.isActive( ) );
Assert.assertSame( LifecycleStage.STOP , lcaTest.stage ); 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 @Test
public void testStopActionFailure( ) public void testStopActionFailure( )
{ {
final Exception failure = new Exception( ); final ComponentShutdownException failure = new ComponentShutdownException( );
final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ) // final NewComponentInfo< Object > ci = new NewComponentInfo< Object >( new Object( ) ) //
.setLifecycleAction( LifecycleStage.STOP , ( o ) -> { .setLifecycleAction( LifecycleStage.STOP , ( o ) -> {
throw failure; throw failure;
} ); } );
final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true ); final ComponentState cs = TestComponentState.makeState( ci , this.reg , true , true );
this.reg.active = true; this.reg.active = true;
Assert.assertSame( failure , cs.stop( ) ); try {
Assert.assertTrue( cs.isActive( ) ); 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 ); cs2.addDependency( cs1 );
this.reg.active = true; this.reg.active = true;
Assert.assertNull( cs1.stop( ) ); cs1.stop( );
Assert.assertFalse( cs2.isActive( ) ); 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 @Test
public void testRestartWhenStopFails( ) public void testRestartWhenStopFails( )
{ {
@ -578,20 +573,20 @@ public class TestComponentState
this.reg.active = true; this.reg.active = true;
try { try {
cs.restart( ); cs.restart( );
} catch ( final ComponentRestartException e ) { } catch ( final ComponentShutdownException e ) {
Assert.assertSame( failure , e.getCause( ) ); Assert.assertSame( failure , e.getCause( ) );
Assert.assertTrue( cs.isActive( ) ); Assert.assertTrue( cs.isActive( ) );
return; 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 @Test
public void testRestartWhenStartFails( ) public void testRestartWhenStartFails( )
{ {
final ComponentStartupException failure = new ComponentStartupException( ); final Exception failure = new Exception( );
final NewComponentInfo< RestartTest > ci = RestartTest.getDef( ); final NewComponentInfo< RestartTest > ci = RestartTest.getDef( );
ci.setLifecycleAction( LifecycleStage.START , ( o ) -> { ci.setLifecycleAction( LifecycleStage.START , ( o ) -> {
throw failure; throw failure;
@ -600,12 +595,12 @@ public class TestComponentState
this.reg.active = true; this.reg.active = true;
try { try {
cs.restart( ); cs.restart( );
} catch ( final ComponentRestartException e ) { } catch ( final ComponentStartupException e ) {
Assert.assertSame( failure , e.getCause( ) ); Assert.assertSame( failure , e.getCause( ) );
Assert.assertFalse( cs.isActive( ) ); Assert.assertFalse( cs.isActive( ) );
return; return;
} }
Assert.fail( "no ComponentRestartException" ); Assert.fail( "no ComponentStartupException" );
} }