Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / router.go
diff --git a/src/foundation/api/revel/router.go b/src/foundation/api/revel/router.go
new file mode 100644 (file)
index 0000000..54fb6e2
--- /dev/null
@@ -0,0 +1,846 @@
+// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
+// Revel Framework source code and usage is governed by a MIT style
+// license that can be found in the LICENSE file.
+
+package revel
+
+import (
+       "encoding/csv"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "net/url"
+       "path/filepath"
+       "regexp"
+       "strings"
+
+       "os"
+       "sync"
+
+       "github.com/revel/pathtree"
+       "github.com/revel/revel/logger"
+)
+
+const (
+       httpStatusCode = "404"
+)
+
+type Route struct {
+       ModuleSource        *Module         // Module name of route
+       Method              string          // e.g. GET
+       Path                string          // e.g. /app/:id
+       Action              string          // e.g. "Application.ShowApp", "404"
+       ControllerNamespace string          // e.g. "testmodule.",
+       ControllerName      string          // e.g. "Application", ""
+       MethodName          string          // e.g. "ShowApp", ""
+       FixedParams         []string        // e.g. "arg1","arg2","arg3" (CSV formatting)
+       TreePath            string          // e.g. "/GET/app/:id"
+       TypeOfController    *ControllerType // The controller type (if route is not wild carded)
+
+       routesPath string // e.g. /Users/robfig/gocode/src/myapp/conf/routes
+       line       int    // e.g. 3
+}
+
+type RouteMatch struct {
+       Action           string // e.g. 404
+       ControllerName   string // e.g. Application
+       MethodName       string // e.g. ShowApp
+       FixedParams      []string
+       Params           map[string][]string // e.g. {id: 123}
+       TypeOfController *ControllerType     // The controller type
+       ModuleSource     *Module             // The module
+}
+
+type ActionPathData struct {
+       Key                 string            // The unique key
+       ControllerNamespace string            // The controller namespace
+       ControllerName      string            // The controller name
+       MethodName          string            // The method name
+       Action              string            // The action
+       ModuleSource        *Module           // The module
+       Route               *Route            // The route
+       FixedParamsByName   map[string]string // The fixed parameters
+       TypeOfController    *ControllerType   // The controller type
+}
+
+var (
+       // Used to store decoded action path mappings
+       actionPathCacheMap = map[string]*ActionPathData{}
+       // Used to prevent concurrent writes to map
+       actionPathCacheLock = sync.Mutex{}
+       // The path returned if not found
+       notFound = &RouteMatch{Action: "404"}
+)
+
+var routerLog = RevelLog.New("section", "router")
+
+func init() {
+       AddInitEventHandler(func(typeOf Event, value interface{}) (responseOf EventResponse) {
+               // Add in an
+               if typeOf == ROUTE_REFRESH_REQUESTED {
+                       // Clear the actionPathCacheMap cache
+                       actionPathCacheLock.Lock()
+                       defer actionPathCacheLock.Unlock()
+                       actionPathCacheMap = map[string]*ActionPathData{}
+               }
+               return
+       })
+}
+
+// NewRoute prepares the route to be used in matching.
+func NewRoute(moduleSource *Module, method, path, action, fixedArgs, routesPath string, line int) (r *Route) {
+       // Handle fixed arguments
+       argsReader := strings.NewReader(string(namespaceReplace([]byte(fixedArgs), moduleSource)))
+       csvReader := csv.NewReader(argsReader)
+       csvReader.TrimLeadingSpace = true
+       fargs, err := csvReader.Read()
+       if err != nil && err != io.EOF {
+               routerLog.Error("NewRoute: Invalid fixed parameters for string ", "error", err, "fixedargs", fixedArgs)
+       }
+
+       r = &Route{
+               ModuleSource: moduleSource,
+               Method:       strings.ToUpper(method),
+               Path:         path,
+               Action:       string(namespaceReplace([]byte(action), moduleSource)),
+               FixedParams:  fargs,
+               TreePath:     treePath(strings.ToUpper(method), path),
+               routesPath:   routesPath,
+               line:         line,
+       }
+
+       // URL pattern
+       if !strings.HasPrefix(r.Path, "/") {
+               routerLog.Error("NewRoute: Absolute URL required.")
+               return
+       }
+
+       // Ignore the not found status code
+       if action != httpStatusCode {
+               routerLog.Debugf("NewRoute: New splitActionPath path:%s action:%s", path, action)
+               pathData, found := splitActionPath(&ActionPathData{ModuleSource: moduleSource, Route: r}, r.Action, false)
+               if found {
+                       if pathData.TypeOfController != nil {
+                               // Assign controller type to avoid looking it up based on name
+                               r.TypeOfController = pathData.TypeOfController
+                               // Create the fixed parameters
+                               if l := len(pathData.Route.FixedParams); l > 0 && len(pathData.FixedParamsByName) == 0 {
+                                       methodType := pathData.TypeOfController.Method(pathData.MethodName)
+                                       if methodType != nil {
+                                               pathData.FixedParamsByName = make(map[string]string, l)
+                                               for i, argValue := range pathData.Route.FixedParams {
+                                                       Unbind(pathData.FixedParamsByName, methodType.Args[i].Name, argValue)
+                                               }
+                                       } else {
+                                               routerLog.Panicf("NewRoute: Method %s not found for controller %s", pathData.MethodName, pathData.ControllerName)
+                                       }
+                               }
+                       }
+                       r.ControllerNamespace = pathData.ControllerNamespace
+                       r.ControllerName = pathData.ControllerName
+                       r.ModuleSource = pathData.ModuleSource
+                       r.MethodName = pathData.MethodName
+
+                       // The same action path could be used for multiple routes (like the Static.Serve)
+               } else {
+                       routerLog.Panicf("NewRoute: Failed to find controller for route path action %s \n%#v\n", path+"?"+r.Action, actionPathCacheMap)
+               }
+       }
+       return
+}
+
+func (route *Route) ActionPath() string {
+       return route.ModuleSource.Namespace() + route.ControllerName
+}
+
+func treePath(method, path string) string {
+       if method == "*" {
+               method = ":METHOD"
+       }
+       return "/" + method + path
+}
+
+type Router struct {
+       Routes []*Route
+       Tree   *pathtree.Node
+       Module string // The module the route is associated with
+       path   string // path to the routes file
+}
+
+func (router *Router) Route(req *Request) (routeMatch *RouteMatch) {
+       // Override method if set in header
+       if method := req.GetHttpHeader("X-HTTP-Method-Override"); method != "" && req.Method == "POST" {
+               req.Method = method
+       }
+
+       leaf, expansions := router.Tree.Find(treePath(req.Method, req.GetPath()))
+       if leaf == nil {
+               return nil
+       }
+
+       // Create a map of the route parameters.
+       var params url.Values
+       if len(expansions) > 0 {
+               params = make(url.Values)
+               for i, v := range expansions {
+                       params[leaf.Wildcards[i]] = []string{v}
+               }
+       }
+       var route *Route
+       var controllerName, methodName string
+
+       // The leaf value is now a list of possible routes to match, only a controller
+       routeList := leaf.Value.([]*Route)
+       var typeOfController *ControllerType
+
+       //INFO.Printf("Found route for path %s %#v", req.URL.Path, len(routeList))
+       for index := range routeList {
+               route = routeList[index]
+               methodName = route.MethodName
+
+               // Special handling for explicit 404's.
+               if route.Action == httpStatusCode {
+                       route = nil
+                       break
+               }
+
+               // If wildcard match on method name use the method name from the params
+               if methodName[0] == ':' {
+                       if methodKey, found := params[methodName[1:]]; found && len(methodKey) > 0 {
+                               methodName = strings.ToLower(methodKey[0])
+                       } else {
+                               routerLog.Fatal("Route: Failure to find method name in parameters", "params", params, "methodName", methodName)
+                       }
+               }
+
+               // If the action is variablized, replace into it with the captured args.
+               controllerName = route.ControllerName
+               if controllerName[0] == ':' {
+                       controllerName = strings.ToLower(params[controllerName[1:]][0])
+                       if typeOfController = route.ModuleSource.ControllerByName(controllerName, methodName); typeOfController != nil {
+                               break
+                       }
+               } else {
+                       typeOfController = route.TypeOfController
+                       break
+               }
+               route = nil
+       }
+
+       if route == nil {
+               routeMatch = notFound
+       } else {
+
+               routeMatch = &RouteMatch{
+                       ControllerName:   route.ControllerNamespace + controllerName,
+                       MethodName:       methodName,
+                       Params:           params,
+                       FixedParams:      route.FixedParams,
+                       TypeOfController: typeOfController,
+                       ModuleSource:     route.ModuleSource,
+               }
+       }
+
+       return
+}
+
+// Refresh re-reads the routes file and re-calculates the routing table.
+// Returns an error if a specified action could not be found.
+func (router *Router) Refresh() (err *Error) {
+       RaiseEvent(ROUTE_REFRESH_REQUESTED, nil)
+       router.Routes, err = parseRoutesFile(appModule, router.path, "", true)
+       RaiseEvent(ROUTE_REFRESH_COMPLETED, nil)
+       if err != nil {
+               return
+       }
+       err = router.updateTree()
+       return
+}
+
+func (router *Router) updateTree() *Error {
+       router.Tree = pathtree.New()
+       pathMap := map[string][]*Route{}
+
+       allPathsOrdered := []string{}
+       // It is possible for some route paths to overlap
+       // based on wildcard matches,
+       // TODO when pathtree is fixed (made to be smart enough to not require a predefined intake order) keeping the routes in order is not necessary
+       for _, route := range router.Routes {
+               if _, found := pathMap[route.TreePath]; !found {
+                       pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+                       allPathsOrdered = append(allPathsOrdered, route.TreePath)
+               } else {
+                       pathMap[route.TreePath] = append(pathMap[route.TreePath], route)
+               }
+       }
+       for _, path := range allPathsOrdered {
+               routeList := pathMap[path]
+               err := router.Tree.Add(path, routeList)
+
+               // Allow GETs to respond to HEAD requests.
+               if err == nil && routeList[0].Method == "GET" {
+                       err = router.Tree.Add(treePath("HEAD", routeList[0].Path), routeList)
+               }
+
+               // Error adding a route to the pathtree.
+               if err != nil {
+                       return routeError(err, path, fmt.Sprintf("%#v", routeList), routeList[0].line)
+               }
+       }
+       return nil
+}
+
+// Returns the controller namespace and name, action and module if found from the actionPath specified
+func splitActionPath(actionPathData *ActionPathData, actionPath string, useCache bool) (pathData *ActionPathData, found bool) {
+       actionPath = strings.ToLower(actionPath)
+       if pathData, found = actionPathCacheMap[actionPath]; found && useCache {
+               return
+       }
+       var (
+               controllerNamespace, controllerName, methodName, action string
+               foundModuleSource                                       *Module
+               typeOfController                                        *ControllerType
+               log                                                     = routerLog.New("actionPath", actionPath)
+       )
+       actionSplit := strings.Split(actionPath, ".")
+       if actionPathData != nil {
+               foundModuleSource = actionPathData.ModuleSource
+       }
+       if len(actionSplit) == 2 {
+               controllerName, methodName = strings.ToLower(actionSplit[0]), strings.ToLower(actionSplit[1])
+               if i := strings.Index(methodName, "("); i > 0 {
+                       methodName = methodName[:i]
+               }
+               log = log.New("controller", controllerName, "method", methodName)
+               log.Debug("splitActionPath: Check for namespace")
+               if i := strings.Index(controllerName, namespaceSeperator); i > -1 {
+                       controllerNamespace = controllerName[:i+1]
+                       if moduleSource, found := ModuleByName(controllerNamespace[:len(controllerNamespace)-1]); found {
+                               log.Debug("Found module namespace")
+                               foundModuleSource = moduleSource
+                               controllerNamespace = moduleSource.Namespace()
+                       } else {
+                               log.Warnf("splitActionPath: Unable to find module %s for action: %s", controllerNamespace[:len(controllerNamespace)-1], actionPath)
+                       }
+                       controllerName = controllerName[i+1:]
+                       // Check for the type of controller
+                       typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+                       found = typeOfController != nil
+               } else if controllerName[0] != ':' {
+                       // First attempt to find the controller in the module source
+                       if foundModuleSource != nil {
+                               typeOfController = foundModuleSource.ControllerByName(controllerName, methodName)
+                               if typeOfController != nil {
+                                       controllerNamespace = typeOfController.Namespace
+                               }
+                       }
+                       log.Info("Found controller for path", "controllerType", typeOfController)
+
+                       if typeOfController == nil {
+                               // Check to see if we can determine the controller from only the controller name
+                               // an actionPath without a moduleSource will only come from
+                               // Scan through the controllers
+                               matchName := controllerName
+                               for key, controller := range controllers {
+                                       // Strip away the namespace from the controller. to be match
+                                       regularName := key
+                                       if i := strings.Index(key, namespaceSeperator); i > -1 {
+                                               regularName = regularName[i+1:]
+                                       }
+                                       if regularName == matchName {
+                                               // Found controller match
+                                               typeOfController = controller
+                                               controllerNamespace = typeOfController.Namespace
+                                               controllerName = typeOfController.ShortName()
+                                               foundModuleSource = typeOfController.ModuleSource
+                                               found = true
+                                               break
+                                       }
+                               }
+                       } else {
+                               found = true
+                       }
+               } else {
+                       // If wildcard assign the route the controller namespace found
+                       controllerNamespace = actionPathData.ModuleSource.Name + namespaceSeperator
+                       foundModuleSource = actionPathData.ModuleSource
+                       found = true
+               }
+               action = actionSplit[1]
+       } else {
+               foundPaths := ""
+               for path := range actionPathCacheMap {
+                       foundPaths += path + ","
+               }
+               log.Warnf("splitActionPath: Invalid action path %s found paths %s", actionPath, foundPaths)
+               found = false
+       }
+
+       // Make sure no concurrent map writes occur
+       if found {
+               actionPathCacheLock.Lock()
+               defer actionPathCacheLock.Unlock()
+               if actionPathData != nil {
+                       actionPathData.ControllerNamespace = controllerNamespace
+                       actionPathData.ControllerName = controllerName
+                       actionPathData.MethodName = methodName
+                       actionPathData.Action = action
+                       actionPathData.ModuleSource = foundModuleSource
+                       actionPathData.TypeOfController = typeOfController
+               } else {
+                       actionPathData = &ActionPathData{
+                               ControllerNamespace: controllerNamespace,
+                               ControllerName:      controllerName,
+                               MethodName:          methodName,
+                               Action:              action,
+                               ModuleSource:        foundModuleSource,
+                               TypeOfController:    typeOfController,
+                       }
+               }
+               actionPathData.TypeOfController = foundModuleSource.ControllerByName(controllerName, "")
+               if actionPathData.TypeOfController == nil && actionPathData.ControllerName[0] != ':' {
+                       log.Warnf("splitActionPath: No controller found for %s %#v", foundModuleSource.Namespace()+controllerName, controllers)
+               }
+
+               pathData = actionPathData
+               if pathData.Route != nil && len(pathData.Route.FixedParams) > 0 {
+                       // If there are fixed params on the route then add them to the path
+                       // This will give it a unique path and it should still be usable for a reverse lookup provided the name is matchable
+                       // for example
+                       // GET   /test/                     Application.Index("Test", "Test2")
+                       // {{url "Application.Index(test,test)" }}
+                       // should be parseable
+                       actionPath = actionPath + "(" + strings.ToLower(strings.Join(pathData.Route.FixedParams, ",")) + ")"
+               }
+               if actionPathData.Route != nil {
+                       log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", actionPath, actionPathData.Route)
+               }
+               pathData.Key = actionPath
+               actionPathCacheMap[actionPath] = pathData
+               if !strings.Contains(actionPath, namespaceSeperator) && pathData.TypeOfController != nil {
+                       actionPathCacheMap[strings.ToLower(pathData.TypeOfController.Namespace)+actionPath] = pathData
+                       log.Debugf("splitActionPath: Split Storing recognized action path %s for route  %#v ", strings.ToLower(pathData.TypeOfController.Namespace)+actionPath, actionPathData.Route)
+               }
+       }
+       return
+}
+
+// parseRoutesFile reads the given routes file and returns the contained routes.
+func parseRoutesFile(moduleSource *Module, routesPath, joinedPath string, validate bool) ([]*Route, *Error) {
+       contentBytes, err := ioutil.ReadFile(routesPath)
+       if err != nil {
+               return nil, &Error{
+                       Title:       "Failed to load routes file",
+                       Description: err.Error(),
+               }
+       }
+       return parseRoutes(moduleSource, routesPath, joinedPath, string(contentBytes), validate)
+}
+
+// parseRoutes reads the content of a routes file into the routing table.
+func parseRoutes(moduleSource *Module, routesPath, joinedPath, content string, validate bool) ([]*Route, *Error) {
+       var routes []*Route
+
+       // For each line..
+       for n, line := range strings.Split(content, "\n") {
+               line = strings.TrimSpace(line)
+               if len(line) == 0 || line[0] == '#' {
+                       continue
+               }
+
+               const modulePrefix = "module:"
+
+               // Handle included routes from modules.
+               // e.g. "module:testrunner" imports all routes from that module.
+               if strings.HasPrefix(line, modulePrefix) {
+                       moduleRoutes, err := getModuleRoutes(line[len(modulePrefix):], joinedPath, validate)
+                       if err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+                       routes = append(routes, moduleRoutes...)
+                       continue
+               }
+
+               // A single route
+               method, path, action, fixedArgs, found := parseRouteLine(line)
+               if !found {
+                       continue
+               }
+
+               // this will avoid accidental double forward slashes in a route.
+               // this also avoids pathtree freaking out and causing a runtime panic
+               // because of the double slashes
+               if strings.HasSuffix(joinedPath, "/") && strings.HasPrefix(path, "/") {
+                       joinedPath = joinedPath[0 : len(joinedPath)-1]
+               }
+               path = strings.Join([]string{AppRoot, joinedPath, path}, "")
+
+               // This will import the module routes under the path described in the
+               // routes file (joinedPath param). e.g. "* /jobs module:jobs" -> all
+               // routes' paths will have the path /jobs prepended to them.
+               // See #282 for more info
+               if method == "*" && strings.HasPrefix(action, modulePrefix) {
+                       moduleRoutes, err := getModuleRoutes(action[len(modulePrefix):], path, validate)
+                       if err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+                       routes = append(routes, moduleRoutes...)
+                       continue
+               }
+
+               route := NewRoute(moduleSource, method, path, action, fixedArgs, routesPath, n)
+               routes = append(routes, route)
+
+               if validate {
+                       if err := validateRoute(route); err != nil {
+                               return nil, routeError(err, routesPath, content, n)
+                       }
+               }
+       }
+
+       return routes, nil
+}
+
+// validateRoute checks that every specified action exists.
+func validateRoute(route *Route) error {
+       // Skip 404s
+       if route.Action == httpStatusCode {
+               return nil
+       }
+
+       // Skip variable routes.
+       if route.ControllerName[0] == ':' || route.MethodName[0] == ':' {
+               return nil
+       }
+
+       // Precheck to see if controller exists
+       if _, found := controllers[route.ControllerNamespace+route.ControllerName]; !found {
+               // Scan through controllers to find module
+               for _, c := range controllers {
+                       controllerName := strings.ToLower(c.Type.Name())
+                       if controllerName == route.ControllerName {
+                               route.ControllerNamespace = c.ModuleSource.Name + namespaceSeperator
+                               routerLog.Warn("validateRoute: Matched empty namespace route for %s to this namespace %s for the route %s", controllerName, c.ModuleSource.Name, route.Path)
+                       }
+               }
+       }
+
+       // TODO need to check later
+       // does it do only validation or validation and instantiate the controller.
+       var c Controller
+       return c.SetTypeAction(route.ControllerNamespace+route.ControllerName, route.MethodName, route.TypeOfController)
+}
+
+// routeError adds context to a simple error message.
+func routeError(err error, routesPath, content string, n int) *Error {
+       if revelError, ok := err.(*Error); ok {
+               return revelError
+       }
+       // Load the route file content if necessary
+       if content == "" {
+               if contentBytes, er := ioutil.ReadFile(routesPath); er != nil {
+                       routerLog.Error("routeError: Failed to read route file ", "file", routesPath, "error", er)
+               } else {
+                       content = string(contentBytes)
+               }
+       }
+       return &Error{
+               Title:       "Route validation error",
+               Description: err.Error(),
+               Path:        routesPath,
+               Line:        n + 1,
+               SourceLines: strings.Split(content, "\n"),
+               Stack:       fmt.Sprintf("%s", logger.NewCallStack()),
+       }
+}
+
+// getModuleRoutes loads the routes file for the given module and returns the
+// list of routes.
+func getModuleRoutes(moduleName, joinedPath string, validate bool) (routes []*Route, err *Error) {
+       // Look up the module.  It may be not found due to the common case of e.g. the
+       // testrunner module being active only in dev mode.
+       module, found := ModuleByName(moduleName)
+       if !found {
+               routerLog.Debug("getModuleRoutes: Skipping routes for inactive module", "module", moduleName)
+               return nil, nil
+       }
+       routePath := filepath.Join(module.Path, "conf", "routes")
+       if _, e := os.Stat(routePath); e == nil {
+               routes, err = parseRoutesFile(module, routePath, joinedPath, validate)
+       }
+       if err == nil {
+               for _, route := range routes {
+                       route.ModuleSource = module
+               }
+       }
+
+       return routes, err
+}
+
+// Groups:
+// 1: method
+// 4: path
+// 5: action
+// 6: fixedargs
+var routePattern = regexp.MustCompile(
+       "(?i)^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD|WS|PROPFIND|MKCOL|COPY|MOVE|PROPPATCH|LOCK|UNLOCK|TRACE|PURGE|\\*)" +
+               "[(]?([^)]*)(\\))?[ \t]+" +
+               "(.*/[^ \t]*)[ \t]+([^ \t(]+)" +
+               `\(?([^)]*)\)?[ \t]*$`)
+
+func parseRouteLine(line string) (method, path, action, fixedArgs string, found bool) {
+       matches := routePattern.FindStringSubmatch(line)
+       if matches == nil {
+               return
+       }
+       method, path, action, fixedArgs = matches[1], matches[4], matches[5], matches[6]
+       found = true
+       return
+}
+
+func NewRouter(routesPath string) *Router {
+       return &Router{
+               Tree: pathtree.New(),
+               path: routesPath,
+       }
+}
+
+type ActionDefinition struct {
+       Host, Method, URL, Action string
+       Star                      bool
+       Args                      map[string]string
+}
+
+func (a *ActionDefinition) String() string {
+       return a.URL
+}
+
+func (router *Router) Reverse(action string, argValues map[string]string) (ad *ActionDefinition) {
+       log := routerLog.New("action", action)
+       pathData, found := splitActionPath(nil, action, true)
+       if !found {
+               routerLog.Error("splitActionPath: Failed to find reverse route", "action", action, "arguments", argValues)
+               return nil
+       }
+
+       log.Debug("Checking for route", "pathdataRoute", pathData.Route)
+       if pathData.Route == nil {
+               var possibleRoute *Route
+               // If the route is nil then we need to go through the routes to find the first matching route
+               // from this controllers namespace, this is likely a wildcard route match
+               for _, route := range router.Routes {
+                       // Skip routes that are not wild card or empty
+                       if route.ControllerName == "" || route.MethodName == "" {
+                               continue
+                       }
+                       if route.ModuleSource == pathData.ModuleSource && route.ControllerName[0] == ':' {
+                               // Wildcard match in same module space
+                               pathData.Route = route
+                               break
+                       } else if route.ActionPath() == pathData.ModuleSource.Namespace()+pathData.ControllerName &&
+                               (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+                               // Action path match
+                               pathData.Route = route
+                               break
+                       } else if route.ControllerName == pathData.ControllerName &&
+                               (route.Method[0] == ':' || route.Method == pathData.MethodName) {
+                               // Controller name match
+                               possibleRoute = route
+                       }
+               }
+               if pathData.Route == nil && possibleRoute != nil {
+                       pathData.Route = possibleRoute
+                       routerLog.Warnf("Reverse: For a url reverse a match was based on  %s matched path to route %#v ", action, possibleRoute)
+               }
+               if pathData.Route != nil {
+                       routerLog.Debugf("Reverse: Reverse Storing recognized action path %s for route %#v\n", action, pathData.Route)
+               }
+       }
+
+       // Likely unknown route because of a wildcard, perform manual lookup
+       if pathData.Route != nil {
+               route := pathData.Route
+
+               // If the controller or method are wildcards we need to populate the argValues
+               controllerWildcard := route.ControllerName[0] == ':'
+               methodWildcard := route.MethodName[0] == ':'
+
+               // populate route arguments with the names
+               if controllerWildcard {
+                       argValues[route.ControllerName[1:]] = pathData.ControllerName
+               }
+               if methodWildcard {
+                       argValues[route.MethodName[1:]] = pathData.MethodName
+               }
+               // In theory all routes should be defined and pre-populated, the route controllers may not be though
+               // with wildcard routes
+               if pathData.TypeOfController == nil {
+                       if controllerWildcard || methodWildcard {
+                               if controller := ControllerTypeByName(pathData.ControllerNamespace+pathData.ControllerName, route.ModuleSource); controller != nil {
+                                       // Wildcard match boundary
+                                       pathData.TypeOfController = controller
+                                       // See if the path exists in the module based
+                               } else {
+                                       routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+                                       return
+                               }
+                       }
+               }
+
+               if pathData.TypeOfController == nil {
+                       routerLog.Errorf("Reverse: Controller %s not found in reverse lookup", pathData.ControllerNamespace+pathData.ControllerName)
+                       return
+               }
+               var (
+                       queryValues  = make(url.Values)
+                       pathElements = strings.Split(route.Path, "/")
+               )
+               for i, el := range pathElements {
+                       if el == "" || (el[0] != ':' && el[0] != '*') {
+                               continue
+                       }
+                       val, ok := pathData.FixedParamsByName[el[1:]]
+                       if !ok {
+                               val, ok = argValues[el[1:]]
+                       }
+                       if !ok {
+                               val = "<nil>"
+                               routerLog.Error("Reverse: reverse route missing route argument ", "argument", el[1:])
+                       }
+                       pathElements[i] = val
+                       delete(argValues, el[1:])
+                       continue
+               }
+
+               // Add any args that were not inserted into the path into the query string.
+               for k, v := range argValues {
+                       queryValues.Set(k, v)
+               }
+
+               // Calculate the final URL and Method
+               urlPath := strings.Join(pathElements, "/")
+               if len(queryValues) > 0 {
+                       urlPath += "?" + queryValues.Encode()
+               }
+
+               method := route.Method
+               star := false
+               if route.Method == "*" {
+                       method = "GET"
+                       star = true
+               }
+
+               //INFO.Printf("Reversing action %s to %s Using Route %#v",action,url,pathData.Route)
+
+               return &ActionDefinition{
+                       URL:    urlPath,
+                       Method: method,
+                       Star:   star,
+                       Action: action,
+                       Args:   argValues,
+                       Host:   "TODO",
+               }
+       }
+
+       routerLog.Error("Reverse: Failed to find controller for reverse route", "action", action, "arguments", argValues)
+       return nil
+}
+
+func RouterFilter(c *Controller, fc []Filter) {
+       // Figure out the Controller/Action
+       route := MainRouter.Route(c.Request)
+       if route == nil {
+               c.Result = c.NotFound("No matching route found: " + c.Request.GetRequestURI())
+               return
+       }
+
+       // The route may want to explicitly return a 404.
+       if route.Action == httpStatusCode {
+               c.Result = c.NotFound("(intentionally)")
+               return
+       }
+
+       // Set the action.
+       if err := c.SetTypeAction(route.ControllerName, route.MethodName, route.TypeOfController); err != nil {
+               c.Result = c.NotFound(err.Error())
+               return
+       }
+
+       // Add the route and fixed params to the Request Params.
+       c.Params.Route = route.Params
+       // Assign logger if from module
+       if c.Type.ModuleSource != nil && c.Type.ModuleSource != appModule {
+               c.Log = c.Type.ModuleSource.Log.New("ip", c.ClientIP,
+                       "path", c.Request.URL.Path, "method", c.Request.Method)
+       }
+
+       // Add the fixed parameters mapped by name.
+       // TODO: Pre-calculate this mapping.
+       for i, value := range route.FixedParams {
+               if c.Params.Fixed == nil {
+                       c.Params.Fixed = make(url.Values)
+               }
+               if i < len(c.MethodType.Args) {
+                       arg := c.MethodType.Args[i]
+                       c.Params.Fixed.Set(arg.Name, value)
+               } else {
+                       routerLog.Warn("RouterFilter: Too many parameters to action", "action", route.Action, "value", value)
+                       break
+               }
+       }
+
+       fc[0](c, fc[1:])
+}
+
+// HTTPMethodOverride overrides allowed http methods via form or browser param
+func HTTPMethodOverride(c *Controller, fc []Filter) {
+       // An array of HTTP verbs allowed.
+       verbs := []string{"POST", "PUT", "PATCH", "DELETE"}
+
+       method := strings.ToUpper(c.Request.Method)
+
+       if method == "POST" {
+               param := ""
+               if f, err := c.Request.GetForm(); err == nil {
+                       param = strings.ToUpper(f.Get("_method"))
+               }
+
+               if len(param) > 0 {
+                       override := false
+                       // Check if param is allowed
+                       for _, verb := range verbs {
+                               if verb == param {
+                                       override = true
+                                       break
+                               }
+                       }
+
+                       if override {
+                               c.Request.Method = param
+                       } else {
+                               c.Response.Status = 405
+                               c.Result = c.RenderError(&Error{
+                                       Title:       "Method not allowed",
+                                       Description: "Method " + param + " is not allowed (valid: " + strings.Join(verbs, ", ") + ")",
+                               })
+                               return
+                       }
+
+               }
+       }
+
+       fc[0](c, fc[1:]) // Execute the next filter stage.
+}
+
+func init() {
+       OnAppStart(func() {
+               MainRouter = NewRouter(filepath.Join(BasePath, "conf", "routes"))
+               err := MainRouter.Refresh()
+               if MainWatcher != nil && Config.BoolDefault("watch.routes", true) {
+                       MainWatcher.Listen(MainRouter, MainRouter.path)
+               } else if err != nil {
+                       // Not in dev mode and Route loading failed, we should crash.
+                       routerLog.Panic("init: router initialize error", "error", err)
+               }
+       })
+}