1 /** 2 This module provides fluent api based configuration of components with custom 3 configuration errors. 4 5 License: 6 Boost Software License - Version 1.0 - August 17th, 2003 7 8 Permission is hereby granted, free of charge, to any person or organization 9 obtaining a copy of the software and accompanying documentation covered by 10 this license (the "Software") to use, reproduce, display, distribute, 11 execute, and transmit the Software, and to prepare derivative works of the 12 Software, and to permit third-parties to whom the Software is furnished to 13 do so, all subject to the following: 14 15 The copyright notices in the Software and this entire statement, including 16 the above license grant, this restriction and the following disclaimer, 17 must be included in all copies of the Software, in whole or in part, and 18 all derivative works of the Software, unless such copies or derivative 19 works are solely in the form of machine-executable object code generated by 20 a source language processor. 21 22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 25 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 26 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 27 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 28 DEALINGS IN THE SOFTWARE. 29 30 Authors: 31 Alexandru Ermicioi 32 **/ 33 module aermicioi.aedi.configurer.register.factory_configurer; 34 35 36 import aermicioi.aedi.configurer.register.configuration_context_factory; 37 import aermicioi.aedi.configurer.register.context : ValueRegistrationContext; 38 import aermicioi.aedi.container.container; 39 import aermicioi.aedi.container.proxy_container; 40 import aermicioi.aedi.exception; 41 import aermicioi.aedi.factory; 42 import aermicioi.aedi.storage.allocator_aware; 43 import aermicioi.aedi.storage.decorator; 44 import aermicioi.aedi.storage.locator; 45 import aermicioi.aedi.storage.storage; 46 import aermicioi.aedi.util.traits; 47 public import aermicioi.aedi.factory.reference : lref, anonymous; 48 49 import std.meta; 50 import std.traits; 51 import std.algorithm; 52 import std.range; 53 54 @safe: 55 56 /** 57 Construct component using args. 58 59 Constructs component using args, that are passed to function. 60 The function will attempt to find at least one construct that 61 can accept passed argument list. If it fails, compiler will 62 produce error, with respective problems. 63 The argument list can contain beside simple values, references 64 to other components in locator. Arguments that are references to other components 65 won't be type checked. 66 67 Params: 68 factory = the factory which will call constructor with passed arguments. 69 args = a list of arguments that will be passed to constructor. 70 71 Returns: 72 Z. 73 **/ 74 75 Z construct(Z : InstanceFactoryAware!T, T, Args...)(Z factory, auto ref Args args) { 76 factory.setInstanceFactory(constructorBasedFactory!T(args)); 77 78 return factory; 79 } 80 81 /** 82 Invoke T's method to create component of type X. 83 84 Configures component's factory to call method of factoryMethod with args, 85 in order to create component of type X. 86 In case when method is not a static member, the function requires to 87 pass a instance of factoryMethod or a reference to it. 88 The algorithm will check for args compatiblity with parameters of 89 factory method. No type check is done for arguments that are references 90 at compile time. 91 92 Params: 93 factory = component's factory that is configured to call factoryMethod methods to spawn component 94 factoryMethod = instance of factory method that will be used to instantiate component 95 args = a list of arguments passed to factory method 96 T = type of factoryMethod 97 method = the method that is called from T to instantiate component 98 W = either LocatorReference or T 99 X = the return type of T.method member 100 **/ 101 Z factoryMethod(T, string method, Z : InstanceFactoryAware!X, X, W, Args...)(Z factory, auto ref W factoryMethod, auto ref Args args) 102 if ( 103 isNonStaticMethodCompatible!(T, method, Args) && 104 (is(W : T) || is(W : RuntimeReference)) 105 ) { 106 factory.setInstanceFactory(factoryMethodBasedFactory!(T, method)(factoryMethod, args)); 107 108 return factory; 109 } 110 111 /** 112 ditto 113 **/ 114 Z factoryMethod(T, string method, Z : InstanceFactoryAware!X, X, Args...)(Z factory, auto ref Args args) 115 if ( 116 isStaticMethodCompatible!(T, method, Args) 117 ) { 118 119 factory.setInstanceFactory(factoryMethodBasedFactory!(T, method)(args)); 120 return factory; 121 } 122 123 /** 124 ditto 125 **/ 126 Z factoryMethod(Dg, Z : InstanceFactoryAware!X, X, Args...)(Z factory, Dg func, auto ref Args args) 127 if (isSomeFunction!Dg && is(ReturnType!Dg == X)) { 128 129 factory.setInstanceFactory(functionInstanceFactory(func, args)); 130 return factory; 131 } 132 133 /** 134 Invoke component's method with supplied args. 135 136 Configures component's factory to call specified method with passed args. 137 The function will check if the arguments passed to it are compatible with at 138 least one method from possible overload set. 139 The args list can contain references to other objects in locator as well, though 140 no type compatibility checks will be performed at compile time. 141 142 Params: 143 factory = the factory which will be configured to invoke method. 144 args = the arguments that will be used to invoke method on the new object. 145 146 Returns: 147 Z. 148 **/ 149 Z set(string property, Z : PropertyConfigurersAware!T, T, Args...)(Z factory, auto ref Args args) 150 if (!isField!(T, property)) { 151 mixin assertObjectMethodCompatible!(T, property, Args); 152 153 factory.addPropertyConfigurer(methodConfigurer!(property, T)(args)); 154 155 return factory; 156 } 157 158 /** 159 Set component's public field to passed arg. 160 161 Configures component's factory to set specified field to passed arg. 162 The function will check if passed argument is type compatible with specified field. 163 The argument can be a reference as well. In case of argument being reference to another component 164 in container, no type compatiblity checking will be done. 165 166 Params 167 factory = the factory which will be configured to set property. 168 arg = the value of property to be set, or reference to component in container. 169 170 Returns: 171 Z. 172 **/ 173 Z set(string property, Z : PropertyConfigurersAware!T, T, Arg)(Z factory, auto ref Arg arg) 174 if (isField!(T, property)) { 175 mixin assertFieldCompatible!(T, property, Arg); 176 177 factory.addPropertyConfigurer(fieldConfigurer!(property, T)(arg)); 178 179 return factory; 180 } 181 182 /** 183 Construct component using a delegate. 184 185 Constructs component using a delegate, and a list of arguments passed to delegate. 186 187 Params: 188 factory = the factory which will use delegate to construct component. 189 dg = the delegate that is responsible for creating component, given a list of arguments. 190 args = the arguments that will be used by delegate to construct component. 191 192 Returns: 193 Z. 194 **/ 195 Z callback(Z : InstanceFactoryAware!T, T, Args...)(Z factory, T delegate(RCIAllocator, Locator!(), Args) dg, auto ref Args args) { 196 factory.setInstanceFactory(callbackFactory!T(dg, args)); 197 198 return factory; 199 } 200 201 /** 202 ditto 203 **/ 204 Z callback(Z : InstanceFactoryAware!T, T, Args...)(Z factory, T function(RCIAllocator, Locator!(), Args) dg, auto ref Args args) { 205 factory.setInstanceFactory(callbackFactory!T(dg, args)); 206 207 return factory; 208 } 209 210 /** 211 Call dg on a component that is in configuration phase. 212 213 Call dg on component to perform some modifications, using args as input. 214 215 Params: 216 factory = factory which will call dg with args. 217 dg = delegate that will perform some modifications on component using passed args. 218 args = a list of arguments passed to dg. 219 220 Returns: 221 Z 222 **/ 223 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void delegate(Locator!(), T, Args) dg, auto ref Args args) { 224 factory.addPropertyConfigurer(callbackConfigurer!T(dg, args)); 225 226 return factory; 227 } 228 229 /** 230 ditto 231 **/ 232 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void function(Locator!(), T, Args) dg, auto ref Args args) { 233 factory.addPropertyConfigurer(callbackConfigurer!T(dg, args)); 234 235 return factory; 236 } 237 238 /** 239 ditto 240 **/ 241 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void delegate(Locator!(), ref T, Args) dg, auto ref Args args) { 242 factory.addPropertyConfigurer(callbackConfigurer!T(dg, args)); 243 244 return factory; 245 } 246 247 /** 248 ditto 249 **/ 250 Z callback(Z : PropertyConfigurersAware!T, T, Args...)(Z factory, void function(Locator!(), ref T, Args) dg, auto ref Args args) { 251 factory.addPropertyConfigurer(callbackConfigurer!T(dg, args)); 252 253 return factory; 254 } 255 256 /** 257 Autowire a constructor, field or a method. 258 259 Autowire a constructor, field or a method. 260 To autowire a constructor simply call autowire with no template arguments. 261 For autowiring a field or a method, call autowire with the name of field or argument as first template argument. 262 Autowiring process will attempt to find all parameters by their name first, and by type afterwards, in order to 263 inject the dependencies into the component. Failing to find a suitable candidate for injection, will fail in an exception 264 during container instantiation. 265 Note: There are no guarantees as of which method or constructor from an overload set will be selected 266 as such it is advised to use autowire only on members that are not part of an overload set. 267 268 Params: 269 T = the component type 270 member = field or method of component T 271 factory = ConfigurationContextFactory where to inject the constructor or method configurer 272 273 Returns: 274 Z 275 **/ 276 Z autowire(Z : InstanceFactoryAware!T, T)(Z factory) 277 if (getMembersWithProtection!(T, "__ctor", "public").length > 0) { 278 279 alias ctor = getMembersWithProtection!(T, "__ctor", "public")[0]; 280 281 return factory.construct(makeFunctionParameterReferences!ctor.expand); 282 } 283 284 /** 285 ditto 286 **/ 287 Z autowire(string member, Z : PropertyConfigurersAware!T, T)(Z factory) 288 if (getMembersWithProtection!(T, member, "public").length > 0) { 289 alias method = getMembersWithProtection!(T, member, "public")[0]; 290 291 auto arguments = makeFunctionParameterReferences!method; 292 293 static if (arguments.length == 1) { 294 arguments[0] = __traits(identifier, method).lref.alternate(arguments[0]); 295 } 296 297 return factory.set!(member)(arguments.expand); 298 } 299 300 /** 301 ditto 302 **/ 303 Z autowire(string member, Z : PropertyConfigurersAware!T, T)(Z factory) 304 if (isField!(T, member)) { 305 306 alias field = getMember!(T, member); 307 RuntimeReference reference = __traits(identifier, field).lref.alternate(lref!(typeof(field))); 308 309 return factory.set!(member)(reference); 310 } 311 312 /** 313 Instantiates a component using a value as basis. 314 315 Instantiates a component using a value as basis. 316 As a consequence, any reference based type will 317 point to same content when it is instantiated 318 multiple times. 319 320 Params: 321 T = the component type 322 factory = ConfigurationContextFactory where to inject the constructor or method configurer 323 value = default value used to instantiate component 324 **/ 325 Z value(Z : InstanceFactoryAware!T, T)(Z factory, auto ref T value) { 326 factory.setInstanceFactory(new ValueInstanceFactory!T(value)); 327 328 return factory; 329 } 330 331 /** 332 Instantiates a component using as basis some third party factory. 333 334 Params: 335 T = the type of component that is to be configured 336 factory = factory that uses the parent factory for component instantiation 337 delegated = the factory used by factory to instantiate an object. 338 **/ 339 Z parent(Z : InstanceFactoryAware!T, T, X : Factory!W, W : T)(Z factory, X delegated) { 340 factory.setInstanceFactory(new DelegatingInstanceFactory!(T, W)(delegated)); 341 342 return factory; 343 } 344 /** 345 Tag constructed component with some information. 346 347 Tag constructed component with some information. 348 Description 349 350 Params: 351 factory = factory for constructed component 352 tag = tag with which to tag factory. 353 354 Returns: 355 factory 356 **/ 357 W tag(Z, W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory, auto ref Z tag) { 358 static if (ContainsPolicy!(WrapperStorePolicy, Policies)) { 359 Taggable!Z taggable; 360 361 import std.range : chain, only; 362 import aermicioi.aedi.util.range : filterByInterface; 363 364 auto candidates = factory.wrapper 365 .decorators!ObjectFactory 366 .filterByInterface!(Taggable!Z) 367 .chain(factory.decorated.only.filterByInterface!(Taggable!Z)); 368 369 if (candidates.empty) { 370 auto taggableDecorator = new TaggableFactoryDecorator!(Object, Z); 371 taggableDecorator.decorated = factory.wrapper; 372 factory.wrapper = taggableDecorator; 373 374 taggable = taggableDecorator; 375 factory.storage.set(factory.wrapper, factory.identity); 376 } else { 377 taggable = candidates.front; 378 } 379 380 taggable.tag(tag); 381 382 return factory; 383 } else { 384 static assert(false, "Cannot tag component, expected for configurable factory to implement " ~ fullyQualifiedName!WrapperStorePolicy); 385 } 386 } 387 388 /** 389 Register a description for component 390 391 Params: 392 factory = configuration context for component 393 instance = configuration context for comopnent that is already insantiated 394 title = title for component 395 description = description for component 396 397 Returns: 398 Configuration context 399 **/ 400 W describe(W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory, string title, string description = null) { 401 static if (ContainsPolicy!(StoragePolicy, Policies)) { 402 import aermicioi.aedi.container.describing_container : IdentityDescriber; 403 404 IdentityDescriber!() describer = factory.locator.locate!(IdentityDescriber!()); 405 describer.register(factory.identity, title, description); 406 407 return factory; 408 } else { 409 static assert(false, "Cannot describe component, expected for configurable factory to implement " ~ fullyQualifiedName!StoragePolicy); 410 } 411 } 412 413 /** 414 ditto 415 **/ 416 ValueRegistrationContext.ValueContext describe(ValueRegistrationContext.ValueContext instance, string title, string description = null) { 417 import aermicioi.aedi.container.describing_container : IdentityDescriber; 418 419 IdentityDescriber!() describer = instance.locator.locate!(IdentityDescriber!()); 420 describer.register(instance.identity, title, description); 421 422 return instance; 423 } 424 425 /** 426 Run annotation processor over a component and scan it for configuration or new components. 427 428 Params: 429 factory = component factory to run scanning over 430 431 Returns: 432 factory 433 **/ 434 W scan(W : GenericFactory!T, T)(W factory) { 435 import aermicioi.aedi.configurer.annotation.component_scan : ConfiguratorPolicyImpl; 436 ConfiguratorPolicyImpl.configure(factory, factory.locator); 437 438 return factory; 439 } 440 441 /** 442 ditto 443 **/ 444 W scan(W : ConfigurableContainer!(T, Policies), T, Policies...)(W factory) { 445 static if (ContainsPolicy!(StoragePolicy, Policies)) { 446 import aermicioi.aedi.configurer.annotation.component_scan : ContainerAdderImpl, ConfiguratorPolicyImpl; 447 ContainerAdderImpl!().scan!T(factory.locator, factory.storage); 448 ConfiguratorPolicyImpl.configure(factory, factory.locator); 449 450 return factory; 451 } else { 452 static assert(false, "Cannot scan component, expected for configurable factory to implement " ~ fullyQualifiedName!StoragePolicy); 453 } 454 } 455 456 /** 457 Mark constructed object to be provided through a proxy instead of directly doing so. 458 Object will be proxied only in case when the storage where it is stored support 459 storing of proxy object factories. 460 461 Params: 462 factory = factory for constructed object 463 464 Returns: 465 factory 466 **/ 467 W proxy(W : ConfigurableFactory!(T, Policies), T, Policies...)(W factory) @trusted { 468 import aermicioi.aedi.factory.proxy_factory : ProxyFactory, ProxyObjectFactory; 469 import aermicioi.aedi.container.proxy_container : ProxyContainer; 470 import aermicioi.aedi.util.range : filterByInterface; 471 472 auto candidates = factory.storage.decorators!Container.filterByInterface!ProxyContainer; 473 474 if (!candidates.empty) { 475 candidates.front.set( 476 new ProxyObjectWrappingFactory!T( 477 new ProxyFactory!T(factory.identity, candidates.front.decorated) 478 ), 479 factory.identity, 480 ); 481 } 482 483 return factory; 484 } 485 486 /** 487 Use delegate T for destruction of component. 488 489 Params: 490 factory = component factory which will use delegate to destroy component 491 dg = destruction delegate 492 args = optional arguments to delegate 493 494 Returns: 495 factory 496 **/ 497 Z destructor(Z : InstanceDestructorAware!T, T, Args...)( 498 Z factory, 499 void delegate(RCIAllocator, ref T, Args) dg, 500 Args args 501 ) { 502 factory.setInstanceDestructor(callbackInstanceDestructor!T(dg, args)); 503 504 return factory; 505 } 506 507 /** 508 Use method of destructor to destroy component. 509 510 Use method of destructor to destroy component. By convention it is assumed that first argument is destroyed compnent followed by 511 optional arguments. 512 513 Params: 514 method = destructor's method used to destroy component 515 factory = component factory 516 destructor = actual destructor that will destroy object 517 args = arguments passed to destructor 518 Returns: 519 factory 520 **/ 521 Z destructor(string method, X, Z : InstanceDestructorAware!T, T, Args...)( 522 Z factory, 523 X destructor, 524 Args args 525 ) { 526 factory.setInstanceDestructor(factoryMethodInstanceDestructor!(T, method, X, Args)(destructor, args)); 527 528 return factory; 529 } 530 531 /** 532 ditto 533 **/ 534 Z destructor(string method, X, Z : InstanceDestructorAware!T, T, Args...)( 535 Z factory, 536 Args args 537 ) { 538 factory.setInstanceDestructor(factoryMethodInstanceDestructor!(T, method, X, Args)(args)); 539 540 return factory; 541 }