diff --git a/system/ioc/Injector.cfc b/system/ioc/Injector.cfc index 54f8145ca..e4b3ad3ce 100644 --- a/system/ioc/Injector.cfc +++ b/system/ioc/Injector.cfc @@ -575,16 +575,9 @@ component serializable="false" accessors="true" { // Check if the mapping has been discovered yet, and if it hasn't it must be autowired enabled in order to process. if ( NOT mapping.isDiscovered() ) { - try { - // process inspection of instance - mapping.process( binder = variables.binder, injector = this ); - } catch ( any e ) { - // Remove bad mapping - var mappings = variables.binder.getMappings(); - mappings.delete( name ); - // rethrow - throw( object = e ); - } + // Read the mapped object's metadata. + // Keep the mapping after an error so the next lookup can try again. + mapping.process( binder = variables.binder, injector = this ); } // Request object from scope now, we now have it from the scope created, initialized and wired diff --git a/system/ioc/config/Binder.cfc b/system/ioc/config/Binder.cfc index 855d4b50c..d21e3e351 100644 --- a/system/ioc/config/Binder.cfc +++ b/system/ioc/config/Binder.cfc @@ -1364,11 +1364,10 @@ component accessors="true" { } ) .each( function( key, thisMapping ){ try { - // process the metadata + // Read the mapped object's metadata. arguments.thisMapping.process( binder = this, injector = variables.injector ); } catch ( any e ) { - // Remove bad mapping - variables.mappings.delete( key ); + // Keep the mapping so the next lookup can try again. mappingError = e; } } ); diff --git a/tests/specs/ioc/InjectorLiveTest.cfc b/tests/specs/ioc/InjectorLiveTest.cfc index 1c44cdc0d..eb3b757f3 100644 --- a/tests/specs/ioc/InjectorLiveTest.cfc +++ b/tests/specs/ioc/InjectorLiveTest.cfc @@ -130,6 +130,114 @@ component extends="tests.resources.BaseIntegrationTest" { } ); } ); } ); + + feature( "Keep mappings after a processing error (COLDBOX-1420)", function(){ + beforeEach( function( currentSpec ){ + variables.injector1420 = new coldbox.system.ioc.Injector(); + variables.ghostPath1420 = expandPath( "/tests/resources/Ghost1420.cfc" ); + } ); + + afterEach( function( currentSpec ){ + if ( fileExists( variables.ghostPath1420 ) ) { + fileDelete( variables.ghostPath1420 ); + } + } ); + + story( "Keep explicit mappings after a processing error", function(){ + given( "a mapping for a component that does not exist", function(){ + then( "each lookup reports a component error and keeps the mapping", function(){ + injector1420 + .getBinder() + .map( "ghost1420@demo" ) + .to( "tests.resources.DoesNotExist1420" ); + + var firstErrorType = "NONE"; + try { + injector1420.getInstance( "ghost1420@demo" ); + } catch ( any e ) { + firstErrorType = e.type; + } + expect( firstErrorType ).notToBe( "NONE", "The first lookup should report an error" ); + expect( firstErrorType ).notToBe( "Injector.InstanceNotFoundException" ); + + // The failed lookup must not remove the mapping. + expect( injector1420.getBinder().mappingExists( "ghost1420@demo" ) ).toBeTrue(); + + // The second lookup must try to read the component again. + var secondErrorType = "NONE"; + try { + injector1420.getInstance( "ghost1420@demo" ); + } catch ( any e ) { + secondErrorType = e.type; + } + expect( secondErrorType ).notToBe( "NONE", "The second lookup should report an error" ); + expect( secondErrorType ).notToBe( "Injector.InstanceNotFoundException" ); + } ); + } ); + + given( "a mapped component added after the first lookup", function(){ + then( "the second lookup creates the instance", function(){ + injector1420 + .getBinder() + .map( "ghostFile1420@demo" ) + .to( "tests.resources.Ghost1420" ); + + // The first lookup fails because the component file does not exist. + var firstErrorType = "NONE"; + try { + injector1420.getInstance( "ghostFile1420@demo" ); + } catch ( any e ) { + firstErrorType = e.type; + } + expect( firstErrorType ).notToBe( "NONE", "The first lookup should report an error" ); + expect( injector1420.getBinder().mappingExists( "ghostFile1420@demo" ) ).toBeTrue(); + + // Add the component file and try the same mapping again. + fileWrite( variables.ghostPath1420, "component {}" ); + var instance = injector1420.getInstance( "ghostFile1420@demo" ); + expect( isObject( instance ) ).toBeTrue(); + } ); + } ); + + given( "one missing component mapped under two names", function(){ + then( "a failed lookup keeps both names", function(){ + injector1420 + .getBinder() + .map( [ "aliasA1420", "aliasB1420" ] ) + .to( "tests.resources.DoesNotExist1420" ); + + try { + injector1420.getInstance( "aliasA1420" ); + } catch ( any e ) { + // The missing component error is expected. + } + + expect( injector1420.getBinder().mappingExists( "aliasA1420" ) ).toBeTrue(); + expect( injector1420.getBinder().mappingExists( "aliasB1420" ) ).toBeTrue(); + } ); + } ); + } ); + + story( "Keep failed mappings during processMappings()", function(){ + given( "a mapping for a component that does not exist", function(){ + then( "processMappings() reports the error and keeps the mapping", function(){ + injector1420 + .getBinder() + .map( "bad1420" ) + .to( "tests.resources.DoesNotExist1420" ); + + var errorType = "NONE"; + try { + injector1420.getBinder().processMappings(); + } catch ( any e ) { + errorType = e.type; + } + expect( errorType ).notToBe( "NONE", "processMappings() should report an error" ); + expect( injector1420.getBinder().mappingExists( "bad1420" ) ).toBeTrue(); + } ); + } ); + } ); + } ); } }