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