1 /** 2 This module contains the core of the Jumped framework. 3 4 It contains all the required internal functionality in order to detect and 5 resolve any dependencies. 6 7 For public usage, the main function is `jumpStart`, which will instantiate a 8 class, resolve dependencies, and executed `@startup` methods. 9 */ 10 module jumped.beans; 11 12 import jumped.introspection; 13 import jumped.attributes; 14 import jumped.errors; 15 import std.traits; 16 import std.typecons; 17 import std.meta; 18 import std.algorithm; 19 20 private struct BeanInfo(alias FactoryMethod) 21 { 22 alias Bean = ReturnType!FactoryMethod; 23 static immutable string methodName = __traits(identifier, FactoryMethod); 24 alias Parent = __traits(parent, FactoryMethod); 25 26 static if (isScalarType!bean) 27 { 28 alias isAnnotatedWith(Annotation) = Alias!false; 29 alias getAnnotations(Annotation) = AliasSeq!(); 30 } 31 else 32 { 33 alias isAnnotatedWith(Annotation) = hasAnnotation!(bean, Annotation); 34 alias getAnnotations(Annotation) = getUDAs!(bean, Annotation); 35 } 36 } 37 38 /** 39 A container is a class that can perform dependency resolution at compile-time, 40 finding any dependencies through the template parameter. 41 */ 42 class Container(T) 43 { 44 private alias beans = DiscoverBeans!T; 45 private alias DiscoverBeans(Type) = AliasSeq!(BeanInfo!createRootBean, AllBeansAccessableBy!(BeanInfo!createRootBean)); 46 private T _rootBean = null; 47 48 private T createRootBean() 49 { 50 return new T(); 51 } 52 53 private template AllBeansAccessableBy(Type...) 54 { 55 static if (Type.length == 1) 56 { 57 alias beans = BeansDirectlyAccessableBy!(Type[0].Bean); 58 static if (beans.length == 0) 59 { 60 alias AllBeansAccessableBy = AliasSeq!(); 61 } 62 else 63 { 64 alias AllBeansAccessableBy = AliasSeq!( 65 beans, 66 AllBeansAccessableBy!beans 67 ); 68 } 69 } 70 else 71 { 72 alias AllBeansAccessableBy = AliasSeq!( 73 AllBeansAccessableBy!(Type[0]), 74 AllBeansAccessableBy!(Type[1..$]) 75 ); 76 } 77 } 78 79 private template BeansDirectlyAccessableBy(Type) 80 { 81 static if (!isScalarType!Type && getMembersByAnnotation!(Type, bean).length > 0) 82 alias BeansDirectlyAccessableBy = AliasSeq!(MapReturnTypes!(getMembersByAnnotation!(Type, bean))); 83 else 84 alias BeansDirectlyAccessableBy = AliasSeq!(); 85 } 86 87 private template MapReturnTypes(Values...) 88 { 89 static if (Values.length == 1) 90 { 91 alias MapReturnTypes = Alias!(BeanInfo!(Values[0])); 92 } 93 else 94 { 95 alias MapReturnTypes = AliasSeq!( 96 MapReturnTypes!(Values[0]), 97 MapReturnTypes!(Values[1..$]) 98 ); 99 } 100 } 101 102 private template FindAnnotatedMembers(Annotation) 103 { 104 alias FindAnnotatedMembers = FindAnnotatedMembersInBeans!(Annotation, beans); 105 } 106 107 private template FindAnnotatedMembersInBeans(Annotation, Beans...) 108 { 109 static if (Beans.length == 1) 110 { 111 alias bean = Beans[0].Bean; 112 static if (isScalarType!(bean)) 113 alias FindAnnotatedMembersInBeans = AliasSeq!(); 114 else 115 alias FindAnnotatedMembersInBeans = AliasSeq!(getSymbolsByUDA!(bean, Annotation)); 116 } 117 else static if (Beans.length > 1) 118 { 119 alias FindAnnotatedMembersInBeans = AliasSeq!( 120 FindAnnotatedMembersInBeans!(Annotation, Beans[0]), 121 FindAnnotatedMembersInBeans!(Annotation, Beans[1..$]) 122 ); 123 } 124 else 125 { 126 alias FindAnnotatedMembersInBeans = AliasSeq!(); 127 } 128 } 129 130 /** 131 Executes all methods with a specific annotation. 132 Params: 133 Annotation = The annotation to filter by. 134 */ 135 void executeAll(Annotation)() 136 { 137 static foreach (member; FindAnnotatedMembers!Annotation) 138 { 139 execute!(__traits(identifier, member))(resolve!(__traits(parent, member))); 140 } 141 } 142 143 /*void executeAll(Annotation)() 144 { 145 executeAll!(Annotation, Alias!true)(); 146 }*/ 147 148 /// Executes a method, automatically resolving any required parameters. 149 /// Params: 150 /// member = The name of the member to execute. 151 /// type = The object to call the method on. 152 auto execute(string member, Type)(Type type) 153 if (Parameters!(__traits(getMember, Type, member)).length == 0) 154 { 155 cast(void) type; 156 return __traits(getMember, type, member)(); 157 } 158 159 /// Executes a method, automatically resolving any required parameters. 160 /// Params: 161 /// member = The name of the member to execute. 162 /// type = The object to call the method on. 163 auto execute(string member, Type)(Type type) 164 if (Parameters!(__traits(getMember, Type, member)).length > 0) 165 { 166 cast(void) type; 167 Tuple!(Parameters!(__traits(getMember, Type, member))) parameters; 168 static foreach (i, parameter; Parameters!(__traits(getMember, Type, member))) 169 { 170 parameters[i] = resolve!parameter; 171 } 172 return __traits(getMember, type, member)(parameters.expand); 173 } 174 175 /** 176 Resolve a types and returns an instance of that type. 177 178 This version will detect the root bean, and use special instantiation for 179 this specific bean. 180 */ 181 Type resolve(Type)() 182 if (is(Type == T)) 183 { 184 if (_rootBean is null) 185 _rootBean = createRootBean(); 186 return _rootBean; 187 } 188 189 /** 190 Resolve a types and returns an instance of that type. 191 192 Resolves a bean that has an instantiator (a method annotated with `@bean`). 193 */ 194 Type resolve(Type)() 195 if (!is(Type == T) && hasBeanInstantiator!Type) 196 { 197 static foreach (bean; beans) 198 { 199 static if (is(bean.Bean == Type)) 200 { 201 pragma(msg, "@bean: " ~ bean.Parent.stringof ~ "#" ~ bean.methodName); 202 bean.Parent parent = resolve!(bean.Parent); 203 return execute!(bean.methodName)(parent); 204 } 205 } 206 } 207 208 /** 209 Resolve a types and returns an instance of that type. 210 211 Resolves a bean that does not have an instantiator, but is annotated with 212 `@component`. 213 */ 214 Type resolve(Type)() 215 if (!is(Type == T) && !hasBeanInstantiator!Type && hasAnnotation!(component, Type)) 216 { 217 pragma(msg, "@component: " ~ Type.stringof); 218 static if (__traits(hasMember, Type, "__ctor")) 219 { 220 Tuple!(Parameters!(__traits(getMember, Type, "__ctor"))) parameters; 221 static foreach (i, parameter; Parameters!(__traits(getMember, Type, "__ctor"))) 222 { 223 parameters[i] = resolve!parameter; 224 } 225 return new Type(parameters.expand); 226 } 227 else 228 { 229 return new Type(); 230 } 231 } 232 233 /** 234 Resolve a types and returns an instance of that type. 235 236 Catch-all if the bean cannot be found. Will simply cause a compile error. 237 */ 238 template resolve(Type) 239 if (!is(Type == T) && !hasBeanInstantiator!Type && !hasAnnotation!(component, Type)) 240 { 241 static assert(0, "Could not resolve bean '" ~ Type.stringof ~ "'"); 242 } 243 244 /** 245 Checks if there is a `@bean` instantiator for the given `Type`. 246 */ 247 private template hasBeanInstantiator(Type) 248 { 249 private template isBean(Type, alias bean) 250 { 251 enum isBean = is(bean.Bean == Type); 252 } 253 254 enum hasBeanInstantiator = anySatisfy!(ApplyLeft!(isBean, Type), beans); 255 } 256 } 257 258 /** 259 Starts the application. 260 261 It will first create an instance of the application. After the application is 262 instantiated, it will resolve any dependencies required to execute all methods 263 annotated with `@startup`. Once all `@startup`-methods have finished execution, 264 it will execute all `@shutdown`-methods. 265 Params: 266 T = The startup class 267 */ 268 void jumpStart(T)() 269 { 270 auto container = new Container!T; 271 272 try 273 { 274 container.executeAll!startup(); 275 container.executeAll!(shutdownOnSuccess); 276 } 277 catch (Exception e) 278 { 279 container.executeAll!(shutdownOnFailure); 280 } 281 finally 282 { 283 container.executeAll!(shutdown); 284 } 285 }