1 /*
2  * Hunt - A high-level D Programming Language Web framework that encourages rapid development and clean, pragmatic design.
3  *
4  * Copyright (C) 2015-2019, HuntLabs
5  *
6  * Website: https://www.huntlabs.net/
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.framework.controller.Controller;
13 
14 import hunt.framework.application.Application;
15 import hunt.framework.auth;
16 import hunt.framework.breadcrumb.BreadcrumbsManager;
17 import hunt.framework.controller.RestController;
18 import hunt.framework.middleware.AuthMiddleware;
19 import hunt.framework.middleware.Middleware;
20 import hunt.framework.middleware.MiddlewareInfo;
21 import hunt.framework.middleware.MiddlewareInterface;
22 
23 import hunt.framework.http.Request;
24 import hunt.framework.http.Form;
25 import hunt.framework.i18n.I18n;
26 import hunt.framework.provider;
27 import hunt.framework.Simplify;
28 import hunt.framework.view;
29 
30 public import hunt.framework.http.Response;
31 public import hunt.http.server;
32 public import hunt.http.routing;
33 import hunt.http.HttpConnection;
34 
35 import hunt.cache;
36 import hunt.entity.EntityManagerFactory;
37 import hunt.logging.Logger;
38 import hunt.redis.Redis;
39 import hunt.redis.RedisPool;
40 import hunt.validation;
41 
42 import poodinis;
43 
44 import core.memory;
45 import core.thread;
46 
47 import std.algorithm;
48 import std.exception;
49 import std.string;
50 import std.traits;
51 import std.variant;
52 import std.meta : Filter;
53 
54 struct Action {
55 }
56 
57 private enum string TempVarName = "__var";
58 // private enum string IndentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";  // 16 tabs
59 private enum string IndentString = "                                ";  // 32 spaces
60 
61 string indent(size_t number) {
62     assert(number>0 && IndentString.length, "Out of range");
63     return IndentString[0..number];
64 }
65 
66 class ControllerBase(T) : Controller {
67     // mixin MakeController;
68     mixin HuntDynamicCallFun!(T, moduleName!T);
69 }
70 
71 /**
72  * 
73  */
74 abstract class Controller
75 {
76     private Request _request;
77     private Response _response;
78 
79     private MiddlewareInfo[] _allowedMiddlewares;
80     private MiddlewareInfo[] _skippedMiddlewares;
81 
82     protected
83     {
84         RoutingContext _routingContext;
85         View _view;
86         ///called before all actions
87         MiddlewareInterface[string] middlewares;
88     }
89 
90     this() {
91     }
92 
93     RoutingContext routingContext() {
94         if(_routingContext is null) {
95             throw new Exception("Can't call this method in the constructor.");
96         }
97         return _routingContext;
98     }
99 
100     Request request() {
101         return createRequest(false);
102     }
103 
104     protected Request createRequest(bool isRestful = false) {
105         if(_request is null) {
106             RoutingContext context = routingContext();
107 
108             Variant itemAtt = context.getAttribute(RouterContex.stringof);
109             RouterContex routeContex;
110             if(itemAtt.hasValue()) {
111                 routeContex = cast(RouterContex)itemAtt.get!(Object);
112             }
113 
114             HttpConnection httpConnection = context.httpConnection();
115             if(httpConnection is null) 
116                 throw new Error("The http connection is null!");
117 
118             _request = new Request(context.getRequest(), 
119                                     httpConnection.getRemoteAddress(),
120                                     routeContex);
121 
122             _request.isRestful = isRestful;
123         }
124         return _request;
125     }
126 
127     final @property Response response() {
128         if(_response is null) {
129             _response = new Response(routingContext().getResponse());
130         }
131         return _response;
132     }
133 
134     // reset to a new response
135     @property void response(Response r) {
136         assert(r !is null, "The response can't be null");
137         _response = r;
138         routingContext().response = r.httpResponse;
139     }
140 
141     /**
142      * Handle the auth status
143      */
144     Auth auth() {
145         return this.request().auth();
146     }
147 
148     /**
149      * Get the currently authenticated user.
150      */
151     Identity user() {
152         return this.request().auth().user();
153     }
154 
155     @property View view()
156     {
157         if (_view is null)
158         {
159             Request req = this.request();
160             _view = serviceContainer.resolve!View();
161             _view.setRouteGroup(routingContext().groupName());
162             _view.setLocale(req.locale());
163             _view.env().request = req;
164             
165             // _view.assign("input", req.input());
166         }
167 
168         return _view;
169     }
170 
171     private RouteConfigManager routeManager() {
172         return serviceContainer.resolve!(RouteConfigManager);
173     }
174 
175 
176     /// called before action  return true is continue false is finish
177     bool before()
178     {
179         return true;
180     }
181 
182     /// called after action  return true is continue false is finish
183     bool after()
184     {
185         return true;
186     }
187 
188     ///add middleware
189     ///return true is ok, the named middleware is already exist return false
190     bool addMiddleware(MiddlewareInterface m)
191     {
192         if(m is null || this.middlewares.get(m.name(), null) !is null)
193         {
194             return false;
195         }
196 
197         this.middlewares[m.name()]= m;
198         return true;
199     }
200 
201     /**
202      * 
203      */
204     void addAcceptedMiddleware(string fullName, string actionName, string controllerName, string moduleName) {
205         MiddlewareInfo info = new MiddlewareInfo(fullName, actionName, controllerName, moduleName);
206         _allowedMiddlewares ~= info;
207     }
208     
209     /**
210      * 
211      */
212     void addSkippedMiddleware(string fullName, string actionName, string controllerName, string moduleName) {
213         MiddlewareInfo info = new MiddlewareInfo(fullName, actionName, controllerName, moduleName);
214         _skippedMiddlewares ~= info;
215     }
216 
217     // All the middlewares defined in the route group
218     protected MiddlewareInterface[] getAcceptedMiddlewaresInRouteGroup(string routeGroup) {
219         MiddlewareInterface[] result;
220 
221         TypeInfo_Class[] routeMiddlewares;
222         routeMiddlewares = routeManager().group(routeGroup).allowedMiddlewares();
223         foreach(TypeInfo_Class info; routeMiddlewares) {
224             version(HUNT_AUTH_DEBUG) tracef("routeGroup: %s, fullName: %s", routeGroup, info.name);
225 
226             MiddlewareInterface middleware = cast(MiddlewareInterface)info.create();
227             if(middleware is null) {
228                 warningf("%s is not a MiddlewareInterface", info.name);
229             } else {              
230                 result ~= middleware;
231             }
232         }
233 
234         return result;
235     }
236 
237     // All the middlewares defined in the route item
238     protected MiddlewareInterface[] getAcceptedMiddlewaresInRouteItem(string routeGroup, string actionId) {
239         MiddlewareInterface[] result;
240         TypeInfo_Class[] routeMiddlewares;
241 
242         RouteItem routeItem = routeManager().get(routeGroup, actionId);
243             if(routeItem !is null) {
244             routeMiddlewares = routeItem.allowedMiddlewares();
245             foreach(TypeInfo_Class info; routeMiddlewares) {
246                 warningf("actionId: %s, fullName: %s", actionId, info.name);
247 
248                 MiddlewareInterface middleware = cast(MiddlewareInterface)info.create();
249                 if(middleware is null) {
250                     warningf("%s is not a MiddlewareInterface", info.name);
251                 } else {
252                     result ~= middleware;
253                 }
254             } 
255         }
256 
257         return result;
258     }
259 
260     // All the middlewares defined this Controller's action
261     protected MiddlewareInterface[] getAcceptedMiddlewaresInController(string actionName) {
262         MiddlewareInterface[] result;
263 
264         auto middlewares = _allowedMiddlewares.filter!( m => m.action == actionName);
265         // auto middlewares = _allowedMiddlewares.filter!( (m) { 
266         //     trace(m.action, " == ", name);
267         //     return m.action == name;
268         // });
269 
270         foreach(MiddlewareInfo info; middlewares) {
271             // warningf("fullName: %s, action: %s", info.fullName, info.action);
272 
273             MiddlewareInterface middleware = cast(MiddlewareInterface)Object.factory(info.fullName);
274             if(middleware is null) {
275                 warningf("%s is not a MiddlewareInterface", info.fullName);
276             } else {             
277                 result ~= middleware;
278             }
279         }
280 
281         return result;
282     }
283 
284     // All the middlewares defined in the route group
285     protected bool isSkippedMiddlewareInRouteGroup(string fullName, string routeGroup) {
286         TypeInfo_Class[] routeMiddlewares = routeManager().group(routeGroup).skippedMiddlewares();
287         foreach(TypeInfo_Class typeInfo; routeMiddlewares) {
288             if(typeInfo.name == fullName) return true;
289         }
290 
291         return false;
292     }
293     
294     // All the middlewares defined in the route item
295     protected bool isSkippedMiddlewareInRouteItem(string fullName, string routeGroup, string actionId) {
296         RouteItem routeItem = routeManager().getRoute(routeGroup, actionId);
297         if(routeItem !is null) {
298             TypeInfo_Class[] routeMiddlewares = routeItem.skippedMiddlewares();
299             foreach(TypeInfo_Class typeInfo; routeMiddlewares) {
300                 if(typeInfo.name == fullName) return true;
301             }
302         }
303 
304         return false;
305     }
306 
307     // All the middlewares defined this Controller's action
308     protected bool isSkippedMiddlewareInControllerAction(string actionName, string middlewareName) {
309         bool r = _skippedMiddlewares.canFind!(m => m.fullName == middlewareName && m.action == actionName);
310         return r;
311     }
312 
313     // get all middleware
314     MiddlewareInterface[string] getMiddlewares()
315     {
316         return this.middlewares;
317     }
318 
319     protected final Response handleMiddlewares(string actionName) {
320         Request req = this.request();
321         string actionId = req.actionId();
322         string routeGroup = req.routeGroup();
323         
324         version (HUNT_DEBUG) {
325             infof("middlware: routeGroup=%s, path=%s, method=%s, actionId=%s, actionName=%s", 
326                routeGroup, req.path(),  req.method, actionId, actionName);
327         }
328 
329         /////////////
330         // Checking all the allowed middlewares.
331         /////////////
332 
333         // Allowed middlewares in Controller's Action
334         MiddlewareInterface[] allowedMiddlewares = getAcceptedMiddlewaresInController(actionName);
335         
336         // Allowed middlewares in RouteItem
337         allowedMiddlewares ~= getAcceptedMiddlewaresInRouteItem(routeGroup, actionId);
338 
339         foreach(MiddlewareInterface m; allowedMiddlewares) {
340             string name = m.name();
341             version (HUNT_DEBUG) tracef("The %s is processing ...", name);
342 
343             auto response = m.onProcess(req, this.response);
344             if (response is null) {
345                 continue;
346             }
347 
348             version (HUNT_DEBUG) infof("The access is blocked by %s.", name);
349             return response;
350         }
351         
352         // Allowed middlewares in RouteGroup
353         allowedMiddlewares = getAcceptedMiddlewaresInRouteGroup(routeGroup);
354         foreach(MiddlewareInterface m; allowedMiddlewares) {
355             string name = m.name();
356             version (HUNT_DEBUG) tracef("The %s is processing ...", name);
357 
358             if(isSkippedMiddlewareInControllerAction(actionName, name)) {
359                 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name);
360                 return null;
361             }
362 
363             if(isSkippedMiddlewareInRouteItem(name, routeGroup, actionId)) {
364                 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name);
365                 return null;
366             }
367 
368             auto response = m.onProcess(req, this.response);
369             if (response is null) {
370                 continue;
371             }
372 
373             version (HUNT_DEBUG) infof("The access is blocked by %s.", m.name);
374             return response;
375         }
376 
377         /////////////
378         // Checking all the directly registed middlewares in Controller.
379         /////////////
380 
381         foreach (m; middlewares) {
382             string name = m.name();
383             version (HUNT_DEBUG) tracef("The %s is processing ...", name);
384             if(isSkippedMiddlewareInControllerAction(actionName, name)) {
385                 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name);
386                 return null;
387             }
388 
389             if(isSkippedMiddlewareInRouteItem(name, routeGroup, actionId)) {
390                 version (HUNT_DEBUG) infof("A middleware [%s] is skipped ...", name);
391                 return null;
392             }
393 
394             auto response = m.onProcess(req, this.response);
395             if (response is null) {
396                 continue;
397             }
398 
399             version (HUNT_DEBUG) tracef("The access is blocked by %s.", name);
400             return response;
401         }
402 
403         return null;
404     }
405 
406     Response processResponse(Response res)
407     {
408         // TODO: Tasks pending completion -@zhangxueping at 2020-01-06T14:01:43+08:00
409         // 
410         // have ResponseHandler binding?
411         // if (res.httpResponse() is null)
412         // {
413         //     res.setHttpResponse(request.responseHandler());
414         // }
415 
416         return res;
417     }
418 
419     ConstraintValidatorContext validate() {
420         if(_context is null) {
421             // assert(!_currentActionName.empty(), "No currentActionName found!");
422             _context = new DefaultConstraintValidatorContext();
423 
424             auto itemPtr = _currentActionName in _actionValidators;
425             // assert(itemPtr !is null, format("No handler found for action: %s!", _currentActionName));
426             if(itemPtr is null) {
427                 warning(format("No validator found for action: %s.", _currentActionName));
428             } else {
429                 try {
430                     (*itemPtr)(_context);  
431                 } catch(Exception ex) {
432                     warning(ex.msg);
433                     version(HUNT_DEBUG) warning(ex);
434                 }
435             }          
436         }
437 
438         return _context;
439     }
440     private ConstraintValidatorContext _context;
441     protected string _currentActionName;
442     protected QueryParameterValidator[string] _actionValidators;
443 
444     protected void raiseError(Response response) {
445         this.response = onError(response);
446     }
447 
448     protected Response onError(Response response) {
449         return response;
450     }
451 
452     protected void done() {
453         Request req = request();
454         Response resp = response();
455         HttpSession session = req.session(false);
456         if (session !is null ) // && session.isNewSession()
457         {
458             resp.withCookie(new Cookie(DefaultSessionIdName, session.getId(), session.getMaxInactiveInterval(), 
459                     "/", null, false, false));
460 
461             // session.reflash();
462             session.save();
463         }
464         req.flush(); // assure the sessiondata flushed;
465 
466         resp.header("Date", date("Y-m-d H:i:s"));
467         resp.header(HttpHeader.X_POWERED_BY, HUNT_X_POWERED_BY);
468         resp.header(HttpHeader.SERVER, HUNT_FRAMEWORK_SERVER);
469 
470         if(!resp.getFields().contains(HttpHeader.CONTENT_TYPE)) {
471             resp.header(HttpHeader.CONTENT_TYPE, MimeType.TEXT_HTML_VALUE);
472         }
473 
474         handleCors();
475         handleAuthResponse();
476     }
477 
478     protected void handleCors() {
479         /**
480         CORS support
481         http://www.cnblogs.com/feihong84/p/5678895.html
482         https://stackoverflow.com/questions/10093053/add-header-in-ajax-request-with-jquery
483         */
484         ApplicationConfig.HttpConf httpConf = config().http;
485         if(httpConf.enableCors) {
486             response.setHeader("Access-Control-Allow-Origin", httpConf.allowOrigin);
487             response.setHeader("Access-Control-Allow-Methods", httpConf.allowMethods);
488             response.setHeader("Access-Control-Allow-Headers", httpConf.allowHeaders);
489         }        
490     }
491 
492     protected void handleAuthResponse() {
493         Request req = request();
494         Auth auth = req.auth();
495 
496         version(HUNT_AUTH_DEBUG) {
497             tracef("Path: %s, isAuthEnabled: %s", 
498                 req.path,  auth.isEnabled());
499         }
500 
501         if(!auth.isEnabled()) 
502             return;
503 
504         AuthenticationScheme authScheme = auth.scheme();
505         string tokenCookieName = auth.tokenCookieName;
506         version(HUNT_AUTH_DEBUG) {
507             warningf("tokenCookieName: %s, authScheme: %s, isAuthenticated: %s, isLogout: %s", 
508                 tokenCookieName, authScheme, auth.user().isAuthenticated, auth.isLogout());
509         }
510 
511         Cookie tokenCookie;
512 
513         if(auth.canRememberMe() || auth.isTokenRefreshed()) {
514             ApplicationConfig appConfig = app().config();
515             int tokenExpiration = appConfig.auth.tokenExpiration;
516 
517             if(authScheme != AuthenticationScheme.None) {
518                 string authToken = auth.token();
519                 tokenCookie = new Cookie(tokenCookieName, authToken, tokenExpiration);
520                 auth.touchSession();
521             } 
522 
523         } else if(auth.isLogout()) {
524             if(authScheme != AuthenticationScheme.None) {
525                 tokenCookie = new Cookie(tokenCookieName, "", 0);
526             }
527         } else if(authScheme != AuthenticationScheme.None) {
528             ApplicationConfig appConfig = app().config();
529             int tokenExpiration = appConfig.auth.tokenExpiration;
530             string authToken = auth.token();
531             if(authToken.empty()) {
532                 version(HUNT_AUTH_DEBUG) warning("The auth token is empty!");
533             } else {
534                 tokenCookie = new Cookie(tokenCookieName, authToken, tokenExpiration);
535                 auth.touchSession();
536             }
537         }
538 
539         if(tokenCookie !is null) {
540             response().withCookie(tokenCookie);
541         }
542     }
543 
544     void dispose() {
545         version(HUNT_FM_DEBUG) trace("Do nothing");
546     }
547 }
548 
549 mixin template MakeController(string moduleName = __MODULE__)
550 {
551     mixin HuntDynamicCallFun!(typeof(this), moduleName);
552 }
553 
554 mixin template HuntDynamicCallFun(T, string moduleName) // if(is(T : Controller))
555 {
556 public:
557 
558     // Middleware
559     // pragma(msg, handleMiddlewareAnnotation!(T, moduleName));
560 
561     mixin(handleMiddlewareAnnotation!(T, moduleName));
562 
563     // Actions
564     // enum allActions = __createCallActionMethod!(T, moduleName);
565     // version (HUNT_DEBUG) 
566     // pragma(msg, __createCallActionMethod!(T, moduleName));
567 
568     mixin(__createCallActionMethod!(T, moduleName));
569     
570     shared static this()
571     {
572         enum routemap = __createRouteMap!(T, moduleName);
573         // pragma(msg, routemap);
574         mixin(routemap);
575     }
576 }
577 
578 private
579 {
580     // Predefined characteristic name for a default Action method.
581     enum actionName = "Action";
582     enum actionNameLength = actionName.length;
583 
584     bool isActionMember(string name)
585     {
586         return name.length > actionNameLength && name[$ - actionNameLength .. $] == actionName;
587     }
588 }
589 
590 
591 /// 
592 string handleMiddlewareAnnotation(T, string moduleName)() {
593     import std.traits;
594     import std.format;
595     import std.string;
596     import std.conv;
597     import hunt.framework.middleware.MiddlewareInterface;
598 
599     string str = `
600     void initializeMiddlewares() {
601     `;
602     
603     foreach (memberName; __traits(allMembers, T)) {
604         alias currentMember = __traits(getMember, T, memberName);
605         enum _isActionMember = isActionMember(memberName);
606 
607         static if(isFunction!(currentMember)) {
608 
609             static if (hasUDA!(currentMember, Action) || _isActionMember) {
610                 static if(hasUDA!(currentMember, Middleware)) {
611                     enum middlewareUDAs = getUDAs!(currentMember, Middleware);
612 
613                     foreach(uda; middlewareUDAs) {
614                         foreach(middlewareName; uda.names) {
615                             str ~= indent(4) ~ generateAcceptedMiddleware(middlewareName, memberName, T.stringof, moduleName);
616                             // str ~= indent(4) ~ format(`this.addAcceptedMiddleware("%s", "%s", "%s", "%s");`, 
617                             //     middlewareName, memberName, T.stringof, moduleName) ~ "\n";
618                         }
619                     }
620                 } 
621                 
622                 static if(hasUDA!(currentMember, WithoutMiddleware)) {
623                     enum skippedMiddlewareUDAs = getUDAs!(currentMember, WithoutMiddleware);
624                     foreach(uda; skippedMiddlewareUDAs) {
625                         foreach(middlewareName; uda.names) {
626                             str ~= indent(4) ~ generateAddSkippedMiddleware(middlewareName, memberName, T.stringof, moduleName);
627                             // str ~= indent(4) ~ format(`this.addSkippedMiddleware("%s", "%s", "%s", "%s");`, 
628                             //     middlewareName, memberName, T.stringof, moduleName) ~ "\n";
629                         }
630                     }
631                 }
632             }
633         }
634     }
635     
636     str ~= `
637     }    
638     `;
639 
640     return str;
641 }
642 
643 private string generateAcceptedMiddleware(string name, string actionName, string controllerName, string moduleName) {
644     string str;
645 
646     str = `
647         try {
648             TypeInfo_Class typeInfo = MiddlewareInterface.get("%s");
649             string fullName = typeInfo.name;
650             this.addAcceptedMiddleware(fullName, "%s", "%s", "%s");
651         } catch(Exception ex) {
652             warning(ex.msg);
653         }
654     `;
655 
656     str = format(str, name, actionName, controllerName, moduleName);
657     return str;
658 }
659 
660 private string generateAddSkippedMiddleware(string name, string actionName, string controllerName, string moduleName) {
661     string str;
662 
663     str = `
664         try {
665             TypeInfo_Class typeInfo = MiddlewareInterface.get("%s");
666             string fullName = typeInfo.name;
667             this.addSkippedMiddleware(fullName, "%s", "%s", "%s");
668         } catch(Exception ex) {
669             warning(ex.msg);
670         }
671     `;
672 
673     str = format(str, name, actionName, controllerName, moduleName);
674     return str;
675 }
676 
677 string __createCallActionMethod(T, string moduleName)()
678 {
679     import std.traits;
680     import std.format;
681     import std.string;
682     import std.conv;
683     
684 
685     string str = `
686         import hunt.http.server.HttpServerRequest;
687         import hunt.http.server.HttpServerResponse;
688         import hunt.http.routing.RoutingContext;
689         import hunt.http.HttpBody;
690         import hunt.logging;
691         import hunt.validation.ConstraintValidatorContext;
692         import hunt.framework.middleware.MiddlewareInterface;
693         import std.demangle;
694 
695         void callActionMethod(string methodName, RoutingContext context) {
696             _routingContext = context;
697             HttpBody rb;
698             version (HUNT_FM_DEBUG) logDebug("methodName=", methodName);
699             import std.conv;
700 
701             switch(methodName){
702     `;
703 
704     foreach (memberName; __traits(allMembers, T))
705     {
706         // TODO: Tasks pending completion -@zhangxueping at 2019-09-24T11:47:45+08:00
707         // Can't detect the error: void test(error);
708         // pragma(msg, "memberName: ", memberName);
709         static if (is(typeof(__traits(getMember, T, memberName)) == function))
710         {
711             // pragma(msg, "got: ", memberName);
712 
713             enum _isActionMember = isActionMember(memberName);
714             static foreach (currentMethod; __traits(getOverloads, T, memberName))
715             {
716                 // alias RT = ReturnType!(t);
717 
718                 //alias pars = ParameterTypeTuple!(t);
719                 static if (hasUDA!(currentMethod, Action) || _isActionMember) {
720                     str ~= indent(2) ~ "case \"" ~ memberName ~ "\": {\n";
721                     str ~= indent(4) ~ "_currentActionName = \"" ~ currentMethod.mangleof ~ "\";";
722 
723                     // middleware
724                     str ~= `auto middleResponse = this.handleMiddlewares("`~ memberName ~ `");`;
725 
726                     //before
727                     str ~= q{
728                         if (middleResponse !is null) {
729                             // _routingContext.response = response.httpResponse;
730                             response = middleResponse;
731                             return;
732                         }
733 
734                         if (!this.before()) {
735                             // _routingContext.response = response.httpResponse;
736                             // response = middleResponse;
737                             return;
738                         }
739                     };
740 
741                     // Action parameters
742                     auto params = ParameterIdentifierTuple!currentMethod;
743                     string paramString = "";
744 
745                     static if (params.length > 0) {
746                         import std.conv : to;
747 
748                         string varName = "";
749                         alias paramsType = Parameters!currentMethod;
750                         alias paramsDefaults = ParameterDefaults!currentMethod;
751 
752                         static foreach (int i; 0..params.length)
753                         {{
754                             varName = TempVarName ~ i.to!string;
755                             enum currentParamType = paramsType[i].stringof;
756                             
757                             alias paramsUDAs = ParameterUDAs!(currentMethod, AliasField, i);
758                             static if(paramsUDAs.length > 0) {
759                                 enum actualParameter = paramsUDAs[0].name;
760                             } else {
761                                 alias actualParameter = params[i];
762                             }
763 
764                             static if (is(paramsType[i] == string)) {
765                                 static if(is(paramsDefaults[i] == void)) {
766                                     str ~= indent(3) ~ "string " ~ varName ~ " = request.get(\"" ~ actualParameter ~ "\");\n";
767                                 } else {
768                                     str ~= indent(3) ~ "string " ~ varName ~ " = request.get(\"" ~ actualParameter ~ 
769                                         "\", " ~ paramsDefaults[i].stringof ~ ");\n";
770                                 }
771                             } else static if(is(paramsType[i] : Form)) {
772                                 str ~= indent(3) ~ "auto " ~ varName ~ " = request.bindForm!" ~ currentParamType ~ "();\n";
773                             } else {
774                                 static if(is(paramsDefaults[i] == void)) {
775                                     str ~= indent(3) ~ "auto " ~ varName ~ " = request.get!(" ~ 
776                                             currentParamType ~ ")(\"" ~ actualParameter ~ "\");\n";
777                                 } else {
778                                     str ~= indent(3) ~ "auto " ~ varName ~ " = request.get!(" ~ 
779                                             currentParamType ~ ")(\"" ~ actualParameter ~ 
780                                             "\", " ~ paramsDefaults[i].stringof ~ ");\n";
781                                 }
782                             }
783 
784                             paramString ~= i == 0 ? varName : ", " ~ varName;
785                             // varName = "";
786                         }}
787                     }
788 
789                     // Parameters validation
790                     // https://forum.dlang.org/post/bbgwqvvausncrkukzpui@forum.dlang.org
791                     str ~= indent(3) ~ `_actionValidators["` ~ currentMethod.mangleof ~ 
792                         `"] = (ConstraintValidatorContext context) {` ~ "\n";
793 
794                     static if(is(typeof(currentMethod) allParams == __parameters)) {
795                         str ~= indent(4) ~ "version(HUNT_DEBUG) info(`Validating in " ~  memberName ~ 
796                             ", the prototype is " ~ typeof(currentMethod).stringof ~ ". `); " ~ "\n";
797                         // str ~= indent(4) ~ `version(HUNT_DEBUG) infof("Validating in %s", demangle(_currentActionName)); ` ~ "\n";                        
798 
799                         static foreach(i, _; allParams) {{
800                             alias thisParameter = allParams[i .. i + 1]; 
801                             alias udas =  __traits(getAttributes, thisParameter);
802                             // enum ident = __traits(identifier, thisParameter);
803                             
804                             alias paramsUDAs = Filter!(isDesiredUDA!AliasField, udas); 
805                             static if(paramsUDAs.length > 0) {
806                                 enum actualParameter = paramsUDAs[0].name;
807                             } else {
808                                 enum actualParameter = __traits(identifier, thisParameter);
809                             }
810 
811                             str ~= "\n" ~ makeParameterValidation!(TempVarName ~ i.to!string, actualParameter, 
812                                 thisParameter, udas) ~ "\n"; 
813                          }}
814                     }
815 
816                     str ~= indent(3) ~ "};\n";
817 
818                     // Call the Action
819                     static if (is(ReturnType!currentMethod == void)) {
820                         str ~= "\t\tthis." ~ memberName ~ "(" ~ paramString ~ ");\n";
821                     } else {
822                         str ~= "\t\t" ~ ReturnType!currentMethod.stringof ~ " result = this." ~ 
823                                 memberName ~ "(" ~ paramString ~ ");\n";
824 
825                         static if (is(ReturnType!currentMethod : Response)) {
826                             str ~= "\t\t this.response = result;\n";
827                         } else {
828                             static if(is(T : RestController)) {
829                                 str ~="\t\tthis.response.setRestContent(result);";
830                             } else {
831                                 str ~="\t\tthis.response.setContent(result);";
832                             }
833                         }
834                     }
835 
836                     static if(hasUDA!(currentMethod, Action) || _isActionMember) {
837                         str ~= "\n\t\tthis.after();\n";
838                     }
839 
840                     str ~= "\n\t\tbreak;\n\t}\n";
841                 }
842             }
843         }
844     }
845 
846     str ~= "\tdefault:\n\tbreak;\n\t}\n\n";
847     str ~= "}";
848     return str;
849 }
850 
851 
852 template isDesiredUDA(alias attribute)
853 {
854     template isDesiredUDA(alias toCheck)
855     {
856         static if (is(typeof(attribute)) && !__traits(isTemplate, attribute))
857         {
858             static if (__traits(compiles, toCheck == attribute))
859                 enum isDesiredUDA = toCheck == attribute;
860             else
861                 enum isDesiredUDA = false;
862         }
863         else static if (is(typeof(toCheck)))
864         {
865             static if (__traits(isTemplate, attribute))
866                 enum isDesiredUDA =  isInstanceOf!(attribute, typeof(toCheck));
867             else
868                 enum isDesiredUDA = is(typeof(toCheck) == attribute);
869         }
870         else static if (__traits(isTemplate, attribute))
871             enum isDesiredUDA = isInstanceOf!(attribute, toCheck);
872         else
873             enum isDesiredUDA = is(toCheck == attribute);
874     }
875 }
876 
877 
878 template ParameterUDAs(alias func, A, int i=0) {
879     static if(is(FunctionTypeOf!func PT == __parameters)) {
880         alias thisParameter = PT[i .. i + 1]; 
881         alias udas =  __traits(getAttributes, thisParameter);
882 
883         alias ParameterUDAs = Filter!(isDesiredUDA!A, udas);
884     } else {
885         static assert(0, func.stringof ~ " is not a function");
886     }
887 }
888 
889 
890 string makeParameterValidation(string varName, string paraName, paraType, UDAs ...)() {
891     string str;
892     // = "\ninfof(\"" ~ symbol.stringof ~ "\");";
893 
894     version(HUNT_FM_DEBUG) {
895         str ~= `
896             tracef("varName=`~ varName ~`, paraName=` ~ paraName ~ `");
897         `;
898     }
899 
900     static foreach(uda; UDAs) {
901         static if(is(typeof(uda) == Max)) {
902             str ~= `{
903                 MaxValidator validator = new MaxValidator();
904                 validator.initialize(` ~ uda.stringof ~ `);
905                 validator.setPropertyName("` ~ paraName ~ `");
906                 validator.isValid(`~ varName ~`, context);
907             }`;
908         }
909 
910         static if(is(typeof(uda) == Min)) {
911             str ~= `{
912                 MinValidator validator = new MinValidator();
913                 validator.initialize(` ~ uda.stringof ~ `);
914                 validator.setPropertyName("` ~ paraName ~ `");
915                 validator.isValid(`~ varName ~`, context);
916             }`;
917         }
918 
919         static if(is(typeof(uda) == AssertFalse)) {
920             str ~= `{
921                 AssertFalseValidator validator = new AssertFalseValidator();
922                 validator.initialize(` ~ uda.stringof ~ `);
923                 validator.setPropertyName("` ~ paraName ~ `");
924                 validator.isValid(`~ varName ~`, context);
925             }`;
926         }
927 
928         static if(is(typeof(uda) == AssertTrue)) {
929             str ~= `{
930                 AssertTrueValidator validator = new AssertTrueValidator();
931                 validator.initialize(` ~ uda.stringof ~ `);
932                 validator.setPropertyName("` ~ paraName ~ `");
933                 validator.isValid(`~ varName ~`, context);
934             }`;
935         }
936 
937         static if(is(typeof(uda) == Email)) {
938             str ~= `{
939                 EmailValidator validator = new EmailValidator();
940                 validator.initialize(` ~ uda.stringof ~ `);
941                 validator.setPropertyName("` ~ paraName ~ `");
942                 validator.isValid(`~ varName ~`, context);
943             }`;
944         }
945 
946         static if(is(typeof(uda) == Length)) {
947             str ~= `{
948                 LengthValidator validator = new LengthValidator();
949                 validator.initialize(` ~ uda.stringof ~ `);
950                 validator.setPropertyName("` ~ paraName ~ `");
951                 validator.isValid(`~ varName ~`, context);
952             }`;
953         }
954 
955         static if(is(typeof(uda) == NotBlank)) {
956             str ~= `{
957                 NotBlankValidator validator = new NotBlankValidator();
958                 validator.initialize(` ~ uda.stringof ~ `);
959                 validator.setPropertyName("` ~ paraName ~ `");
960                 validator.isValid(`~ varName ~`, context);
961             }`;
962         }
963 
964         static if(is(typeof(uda) == NotEmpty)) {
965             str ~= `{
966                 auto validator = new NotEmptyValidator!` ~ paraType.stringof ~`();
967                 validator.initialize(` ~ uda.stringof ~ `);
968                 validator.setPropertyName("` ~ paraName ~ `");
969                 validator.isValid(`~ varName ~`, context);
970             }`;
971         }
972 
973         static if(is(typeof(uda) == Pattern)) {
974             str ~= `{
975                 PatternValidator validator = new PatternValidator();
976                 validator.initialize(` ~ uda.stringof ~ `);
977                 validator.setPropertyName("` ~ paraName ~ `");
978                 validator.isValid(`~ varName ~`, context);
979             }`;
980         }
981 
982         static if(is(typeof(uda) == Size)) {
983             str ~= `{
984                 SizeValidator validator = new SizeValidator();
985                 validator.initialize(` ~ uda.stringof ~ `);
986                 validator.setPropertyName("` ~ paraName ~ `");
987                 validator.isValid(`~ varName ~`, context);
988             }`;
989         }
990 
991         static if(is(typeof(uda) == Range)) {
992             str ~= `{
993                 RangeValidator validator = new RangeValidator();
994                 validator.initialize(` ~ uda.stringof ~ `);
995                 validator.setPropertyName("` ~ paraName ~ `");
996                 validator.isValid(`~ varName ~`, context);
997             }`;
998         }
999     }
1000 
1001     return str;
1002 }
1003 
1004 alias QueryParameterValidator = void delegate(ConstraintValidatorContext);
1005 
1006 string __createRouteMap(T, string moduleName)()
1007 {
1008 
1009     enum len = "Controller".length;
1010     enum controllerName = moduleName[0..$-len];
1011 
1012     // The format: 
1013     // 1) app.controller.[{group}.]{name}controller
1014     //      app.controller.admin.IndexController
1015     //      app.controller.IndexController
1016     // 
1017     // 2) app.component.{component-name}.controller.{group}.{name}controller
1018     //      app.component.system.controller.admin.DashboardController
1019     enum string[] parts = moduleName.split(".");
1020     // string groupName = "default";
1021 
1022     static if(parts.length == 4) {
1023         // app.controller.admin.DashboardController
1024         enum GroupName = parts[2];
1025     } else static if(parts.length == 6) {
1026         // app.component.system.controller.admin.DashboardController
1027         enum GroupName = parts[4];
1028     } else {
1029         enum GroupName = "default";
1030     }
1031 
1032     string str = "";
1033     foreach (memberName; __traits(allMembers, T))
1034     {
1035         // pragma(msg, "memberName: ", memberName);
1036 
1037         static if (is(typeof(__traits(getMember, T, memberName)) == function)) {
1038             foreach (t; __traits(getOverloads, T, memberName)) {
1039                 static if (hasUDA!(t, Action)) {
1040                     enum string MemberName = memberName;
1041                 } else static if (isActionMember(memberName)) {
1042                     enum string MemberName = memberName[0 .. $ - actionNameLength];
1043                 } else {
1044                     enum string MemberName = "";
1045                 }
1046 
1047                 static if(MemberName.length > 0) {
1048                     str ~= "\n\tregisterRouteHandler(\"" ~ controllerName ~ "." ~ T.stringof ~ "." ~ MemberName
1049                         ~ "\", (context) { 
1050                             context.groupName = \"" ~ GroupName ~ "\";
1051                             callHandler!(" ~ T.stringof ~ ",\"" ~ memberName ~ "\")(context);
1052                     });\n";
1053                 }
1054             }
1055         }
1056     }
1057 
1058     return str;
1059 }
1060 
1061 void callHandler(T, string method)(RoutingContext context)
1062         if (is(T == class) || (is(T == struct) && hasMember!(T, "__CALLACTION__")))
1063 {
1064     // req.action = method;
1065     // auto req = context.getRequest();
1066     // warningf("group name: %s, Threads: %d", context.groupName(), Thread.getAll().length);
1067 
1068     T controller = new T();
1069 
1070     scope(exit) {
1071         import hunt.util.ResoureManager;
1072         ApplicationConfig appConfig = app().config();
1073         if(appConfig.http.workerThreads == 0) {
1074             collectResoure();
1075         }
1076         controller.dispose();
1077         // HUNT_THREAD_DEBUG
1078         version(HUNT_DEBUG) {
1079             warningf("Threads: %d, allocatedInCurrentThread: %d bytes", 
1080                 Thread.getAll().length, GC.stats().allocatedInCurrentThread);
1081         }
1082         // GC.collect();
1083     }
1084 
1085     try {
1086         controller.initializeMiddlewares();
1087         controller.callActionMethod(method, context);
1088         controller.done();
1089     } catch (Throwable t) {
1090         error(t);
1091         app().logger().error(t);
1092         Response errorRes = new Response();
1093         errorRes.doError(HttpStatus.INTERNAL_SERVER_ERROR_500, t);
1094         controller.raiseError(errorRes); 
1095     }
1096     
1097     context.end();
1098 }
1099 
1100 RoutingHandler getRouteHandler(string str)
1101 {
1102     return _actions.get(str, null);
1103 }
1104 
1105 void registerRouteHandler(string str, RoutingHandler method)
1106 {
1107     // key: app.controller.Index.IndexController.showString
1108     version (HUNT_FM_DEBUG) trace("Add route handler: ", str);
1109     _actions[str.toLower] = method;
1110 }
1111 
1112 __gshared RoutingHandler[string] _actions;