--- /dev/null
+// 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 (
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/revel/revel/logger"
+ "github.com/revel/revel/session"
+ "github.com/revel/revel/utils"
+)
+
+// Controller Revel's controller structure that gets embedded in user defined
+// controllers
+type Controller struct {
+ Name string // The controller name, e.g. "Application"
+ Type *ControllerType // A description of the controller type.
+ MethodName string // The method name, e.g. "Index"
+ MethodType *MethodType // A description of the invoked action type.
+ AppController interface{} // The controller that was instantiated. embeds revel.Controller
+ Action string // The fully qualified action name, e.g. "App.Index"
+ ClientIP string // holds IP address of request came from
+
+ Request *Request
+ Response *Response
+ Result Result
+
+ Flash Flash // User cookie, cleared after 1 request.
+ Session session.Session // Session, stored using the session engine specified
+ Params *Params // Parameters from URL and form (including multipart).
+ Args map[string]interface{} // Per-request scratch space.
+ ViewArgs map[string]interface{} // Variables passed to the template.
+ Validation *Validation // Data validation helpers
+ Log logger.MultiLogger // Context Logger
+}
+
+// The map of controllers, controllers are mapped by using the namespace|controller_name as the key
+var controllers = make(map[string]*ControllerType)
+var controllerLog = RevelLog.New("section", "controller")
+
+// NewController returns new controller instance for Request and Response
+func NewControllerEmpty() *Controller {
+ return &Controller{Request: NewRequest(nil), Response: NewResponse(nil)}
+}
+
+// New controller, creates a new instance wrapping the request and response in it
+func NewController(context ServerContext) *Controller {
+ c := NewControllerEmpty()
+ c.SetController(context)
+ return c
+}
+
+// Sets the request and the response for the controller
+func (c *Controller) SetController(context ServerContext) {
+
+ c.Request.SetRequest(context.GetRequest())
+ c.Response.SetResponse(context.GetResponse())
+ c.Request.controller = c
+ c.Params = new(Params)
+ c.Args = map[string]interface{}{}
+ c.ViewArgs = map[string]interface{}{
+ "RunMode": RunMode,
+ "DevMode": DevMode,
+ }
+
+}
+func (c *Controller) Destroy() {
+ // When the instantiated controller gets injected
+ // It inherits this method, so we need to
+ // check to see if the controller is nil before performing
+ // any actions
+ if c == nil {
+ return
+ }
+ if c.AppController != nil {
+ c.resetAppControllerFields()
+ // Return this instance to the pool
+ appController := c.AppController
+ c.AppController = nil
+ if RevelConfig.Controller.Reuse {
+ RevelConfig.Controller.CachedMap[c.Name].Push(appController)
+ }
+ c.AppController = nil
+ }
+
+ c.Request.Destroy()
+ c.Response.Destroy()
+ c.Params = nil
+ c.Args = nil
+ c.ViewArgs = nil
+ c.Name = ""
+ c.Type = nil
+ c.MethodName = ""
+ c.MethodType = nil
+ c.Action = ""
+ c.ClientIP = ""
+ c.Result = nil
+ c.Flash = Flash{}
+ c.Session = session.NewSession()
+ c.Params = nil
+ c.Validation = nil
+ c.Log = nil
+}
+
+// FlashParams serializes the contents of Controller.Params to the Flash
+// cookie.
+func (c *Controller) FlashParams() {
+ for key, vals := range c.Params.Values {
+ c.Flash.Out[key] = strings.Join(vals, ",")
+ }
+}
+
+func (c *Controller) SetCookie(cookie *http.Cookie) {
+ c.Response.Out.internalHeader.SetCookie(cookie.String())
+}
+
+type ErrorCoder interface {
+ HTTPCode() int
+}
+
+func (c *Controller) RenderError(err error) Result {
+ if coder, ok := err.(ErrorCoder); ok {
+ c.setStatusIfNil(coder.HTTPCode())
+ } else {
+ c.setStatusIfNil(http.StatusInternalServerError)
+ }
+
+ return ErrorResult{c.ViewArgs, err}
+}
+
+func (c *Controller) setStatusIfNil(status int) {
+ if c.Response.Status == 0 {
+ c.Response.Status = status
+ }
+}
+
+// Render a template corresponding to the calling Controller method.
+// Arguments will be added to c.ViewArgs prior to rendering the template.
+// They are keyed on their local identifier.
+//
+// For example:
+//
+// func (c Users) ShowUser(id int) revel.Result {
+// user := loadUser(id)
+// return c.Render(user)
+// }
+//
+// This action will render views/Users/ShowUser.html, passing in an extra
+// key-value "user": (User).
+//
+// This is the slower magical version which uses the runtime
+// to determine
+// 1) Set c.ViewArgs to the arguments passed into this function
+// 2) How to call the RenderTemplate by building the following line
+// c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
+//
+// If you want your code to run faster it is recommended you add the template values directly
+// to the c.ViewArgs and call c.RenderTemplate directly
+func (c *Controller) Render(extraViewArgs ...interface{}) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ // Get the calling function line number.
+ _, _, line, ok := runtime.Caller(1)
+ if !ok {
+ controllerLog.Error("Render: Failed to get Caller information")
+ }
+
+ // Get the extra ViewArgs passed in.
+ if renderArgNames, ok := c.MethodType.RenderArgNames[line]; ok {
+ if len(renderArgNames) == len(extraViewArgs) {
+ for i, extraRenderArg := range extraViewArgs {
+ c.ViewArgs[renderArgNames[i]] = extraRenderArg
+ }
+ } else {
+ controllerLog.Error(fmt.Sprint(len(renderArgNames), "RenderArg names found for",
+ len(extraViewArgs), "extra ViewArgs"))
+ }
+ } else {
+ controllerLog.Error(fmt.Sprint("No RenderArg names found for Render call on line", line,
+ "(Action", c.Action, ")"),"stack",logger.NewCallStack())
+ }
+
+ return c.RenderTemplate(c.Name + "/" + c.MethodType.Name + "." + c.Request.Format)
+}
+
+// RenderTemplate method does less magical way to render a template.
+// Renders the given template, using the current ViewArgs.
+func (c *Controller) RenderTemplate(templatePath string) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ // Get the Template.
+ lang, _ := c.ViewArgs[CurrentLocaleViewArg].(string)
+ template, err := MainTemplateLoader.TemplateLang(templatePath, lang)
+ if err != nil {
+ return c.RenderError(err)
+ }
+
+ return &RenderTemplateResult{
+ Template: template,
+ ViewArgs: c.ViewArgs,
+ }
+}
+
+// TemplateOutput returns the result of the template rendered using the controllers ViewArgs.
+func (c *Controller) TemplateOutput(templatePath string) (data []byte, err error) {
+ return TemplateOutputArgs(templatePath, c.ViewArgs)
+}
+
+// RenderJSON uses encoding/json.Marshal to return JSON to the client.
+func (c *Controller) RenderJSON(o interface{}) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ return RenderJSONResult{o, ""}
+}
+
+// RenderJSONP renders JSONP result using encoding/json.Marshal
+func (c *Controller) RenderJSONP(callback string, o interface{}) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ return RenderJSONResult{o, callback}
+}
+
+// RenderXML uses encoding/xml.Marshal to return XML to the client.
+func (c *Controller) RenderXML(o interface{}) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ return RenderXMLResult{o}
+}
+
+// RenderText renders plaintext in response, printf style.
+func (c *Controller) RenderText(text string, objs ...interface{}) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ finalText := text
+ if len(objs) > 0 {
+ finalText = fmt.Sprintf(text, objs...)
+ }
+ return &RenderTextResult{finalText}
+}
+
+// RenderHTML renders html in response
+func (c *Controller) RenderHTML(html string) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ return &RenderHTMLResult{html}
+}
+
+// Todo returns an HTTP 501 Not Implemented "todo" indicating that the
+// action isn't done yet.
+func (c *Controller) Todo() Result {
+ c.Response.Status = http.StatusNotImplemented
+ controllerLog.Debug("Todo: Not implemented function", "action", c.Action)
+ return c.RenderError(&Error{
+ Title: "TODO",
+ Description: "This action is not implemented",
+ })
+}
+
+// NotFound returns an HTTP 404 Not Found response whose body is the
+// formatted string of msg and objs.
+func (c *Controller) NotFound(msg string, objs ...interface{}) Result {
+ finalText := msg
+ if len(objs) > 0 {
+ finalText = fmt.Sprintf(msg, objs...)
+ }
+ c.Response.Status = http.StatusNotFound
+ return c.RenderError(&Error{
+ Title: "Not Found",
+ Description: finalText,
+ })
+}
+
+// Forbidden returns an HTTP 403 Forbidden response whose body is the
+// formatted string of msg and objs.
+func (c *Controller) Forbidden(msg string, objs ...interface{}) Result {
+ finalText := msg
+ if len(objs) > 0 {
+ finalText = fmt.Sprintf(msg, objs...)
+ }
+ c.Response.Status = http.StatusForbidden
+ return c.RenderError(&Error{
+ Title: "Forbidden",
+ Description: finalText,
+ })
+}
+
+// RenderFileName returns a file indicated by the path as provided via the filename.
+// It can be either displayed inline or downloaded as an attachment.
+// The name and size are taken from the file info.
+func (c *Controller) RenderFileName(filename string, delivery ContentDisposition) Result {
+ f, err := os.Open(filename)
+ if err != nil {
+ c.Log.Errorf("Cant open file: %v", err)
+ return c.RenderError(err)
+ }
+ return c.RenderFile(f, delivery)
+}
+
+// RenderFile returns a file, either displayed inline or downloaded
+// as an attachment. The name and size are taken from the file info.
+func (c *Controller) RenderFile(file *os.File, delivery ContentDisposition) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ var (
+ modtime = time.Now()
+ fileInfo, err = file.Stat()
+ )
+ if err != nil {
+ controllerLog.Error("RenderFile: error", "error", err)
+ }
+ if fileInfo != nil {
+ modtime = fileInfo.ModTime()
+ }
+ return c.RenderBinary(file, filepath.Base(file.Name()), delivery, modtime)
+}
+
+// RenderBinary is like RenderFile() except that it instead of a file on disk,
+// it renders data from memory (which could be a file that has not been written,
+// the output from some function, or bytes streamed from somewhere else, as long
+// it implements io.Reader). When called directly on something generated or
+// streamed, modtime should mostly likely be time.Now().
+func (c *Controller) RenderBinary(memfile io.Reader, filename string, delivery ContentDisposition, modtime time.Time) Result {
+ c.setStatusIfNil(http.StatusOK)
+
+ return &BinaryResult{
+ Reader: memfile,
+ Name: filename,
+ Delivery: delivery,
+ Length: -1, // http.ServeContent gets the length itself unless memfile is a stream.
+ ModTime: modtime,
+ }
+}
+
+// Redirect to an action or to a URL.
+// c.Redirect(Controller.Action)
+// c.Redirect("/controller/action")
+// c.Redirect("/controller/%d/action", id)
+func (c *Controller) Redirect(val interface{}, args ...interface{}) Result {
+ c.setStatusIfNil(http.StatusFound)
+
+ if url, ok := val.(string); ok {
+ if len(args) == 0 {
+ return &RedirectToURLResult{url}
+ }
+ return &RedirectToURLResult{fmt.Sprintf(url, args...)}
+ }
+ return &RedirectToActionResult{val, args}
+}
+
+// This stats returns some interesting stats based on what is cached in memory
+// and what is available directly
+func (c *Controller) Stats() map[string]interface{} {
+ result := CurrentEngine.Stats()
+ if RevelConfig.Controller.Reuse {
+ result["revel-controllers"] = RevelConfig.Controller.Stack.String()
+ for key, appStack := range RevelConfig.Controller.CachedMap {
+ result["app-" + key] = appStack.String()
+ }
+ }
+ return result
+}
+
+// Message performs a lookup for the given message name using the given
+// arguments using the current language defined for this controller.
+//
+// The current language is set by the i18n plugin.
+func (c *Controller) Message(message string, args ...interface{}) string {
+ return MessageFunc(c.Request.Locale, message, args...)
+}
+
+// SetAction sets the action that is being invoked in the current request.
+// It sets the following properties: Name, Action, Type, MethodType
+func (c *Controller) SetAction(controllerName, methodName string) error {
+
+ return c.SetTypeAction(controllerName, methodName, nil)
+}
+
+// SetAction sets the assigns the Controller type, sets the action and initializes the controller
+func (c *Controller) SetTypeAction(controllerName, methodName string, typeOfController *ControllerType) error {
+
+ // Look up the controller and method types.
+ if typeOfController == nil {
+ if c.Type = ControllerTypeByName(controllerName, anyModule); c.Type == nil {
+ return errors.New("revel/controller: failed to find controller " + controllerName)
+ }
+ } else {
+ c.Type = typeOfController
+ }
+
+ // Note method name is case insensitive search
+ if c.MethodType = c.Type.Method(methodName); c.MethodType == nil {
+ return errors.New("revel/controller: failed to find action " + controllerName + "." + methodName)
+ }
+
+ c.Name, c.MethodName = c.Type.Type.Name(), c.MethodType.Name
+ c.Action = c.Name + "." + c.MethodName
+
+ // Update Logger with controller and namespace
+ if c.Log != nil {
+ c.Log = c.Log.New("action", c.Action, "namespace", c.Type.Namespace)
+ }
+
+ if RevelConfig.Controller.Reuse {
+ if _, ok := RevelConfig.Controller.CachedMap[c.Name]; !ok {
+ // Create a new stack for this controller
+ localType := c.Type.Type
+ RevelConfig.Controller.CachedMap[c.Name] = utils.NewStackLock(
+ RevelConfig.Controller.CachedStackSize,
+ RevelConfig.Controller.CachedStackMaxSize,
+ func() interface{} {
+ return reflect.New(localType).Interface()
+ })
+ }
+ // Instantiate the controller.
+ c.AppController = RevelConfig.Controller.CachedMap[c.Name].Pop()
+ } else {
+ c.AppController = reflect.New(c.Type.Type).Interface()
+ }
+ c.setAppControllerFields()
+
+ return nil
+}
+
+func ControllerTypeByName(controllerName string, moduleSource *Module) (c *ControllerType) {
+ var found bool
+ if c, found = controllers[controllerName]; !found {
+ // Backup, passed in controllerName should be in lower case, but may not be
+ if c, found = controllers[strings.ToLower(controllerName)]; !found {
+ controllerLog.Debug("ControllerTypeByName: Cannot find controller in controllers map ", "controller", controllerName)
+ // Search for the controller by name
+ for _, cType := range controllers {
+ testControllerName := strings.ToLower(cType.Type.Name())
+ if testControllerName == strings.ToLower(controllerName) && (cType.ModuleSource == moduleSource || moduleSource == anyModule) {
+ controllerLog.Warn("ControllerTypeByName: Matched empty namespace controller ", "controller", controllerName, "namespace", cType.ModuleSource.Name)
+ c = cType
+ found = true
+ break
+ }
+ }
+ }
+ }
+ return
+}
+
+// Injects this instance (c) into the AppController instance
+func (c *Controller) setAppControllerFields() {
+ appController := reflect.ValueOf(c.AppController).Elem()
+ cValue := reflect.ValueOf(c)
+ for _, index := range c.Type.ControllerIndexes {
+ appController.FieldByIndex(index).Set(cValue)
+ }
+}
+
+// Removes this instance (c) from the AppController instance
+func (c *Controller) resetAppControllerFields() {
+ appController := reflect.ValueOf(c.AppController).Elem()
+ // Zero out controller
+ for _, index := range c.Type.ControllerIndexes {
+ appController.FieldByIndex(index).Set(reflect.Zero(reflect.TypeOf(c.AppController).Elem().FieldByIndex(index).Type))
+ }
+}
+
+func findControllers(appControllerType reflect.Type) (indexes [][]int) {
+ // It might be a multi-level embedding. To find the controllers, we follow
+ // every anonymous field, using breadth-first search.
+ type nodeType struct {
+ val reflect.Value
+ index []int
+ }
+ appControllerPtr := reflect.New(appControllerType)
+ queue := []nodeType{{appControllerPtr, []int{}}}
+ for len(queue) > 0 {
+ // Get the next value and de-reference it if necessary.
+ var (
+ node = queue[0]
+ elem = node.val
+ elemType = elem.Type()
+ )
+ if elemType.Kind() == reflect.Ptr {
+ elem = elem.Elem()
+ elemType = elem.Type()
+ }
+ queue = queue[1:]
+
+ // #944 if the type's Kind is not `Struct` move on,
+ // otherwise `elem.NumField()` will panic
+ if elemType.Kind() != reflect.Struct {
+ continue
+ }
+
+ // Look at all the struct fields.
+ for i := 0; i < elem.NumField(); i++ {
+ // If this is not an anonymous field, skip it.
+ structField := elemType.Field(i)
+ if !structField.Anonymous {
+ continue
+ }
+
+ fieldValue := elem.Field(i)
+ fieldType := structField.Type
+
+ // If it's a Controller, record the field indexes to get here.
+ if fieldType == controllerPtrType {
+ indexes = append(indexes, append(node.index, i))
+ continue
+ }
+
+ queue = append(queue,
+ nodeType{fieldValue, append(append([]int{}, node.index...), i)})
+ }
+ }
+ return
+}
+
+// RegisterController registers a Controller and its Methods with Revel.
+func RegisterController(c interface{}, methods []*MethodType) {
+ // De-star the controller type
+ // (e.g. given TypeOf((*Application)(nil)), want TypeOf(Application))
+ elem := reflect.TypeOf(c).Elem()
+
+ // De-star all of the method arg types too.
+ for _, m := range methods {
+ m.lowerName = strings.ToLower(m.Name)
+ for _, arg := range m.Args {
+ arg.Type = arg.Type.Elem()
+ }
+ }
+
+ // Fetch module for controller, if none found controller must be part of the app
+ controllerModule := ModuleFromPath(elem.PkgPath(), true)
+
+ controllerType := AddControllerType(controllerModule, elem, methods)
+
+ controllerLog.Debug("RegisterController:Registered controller", "controller", controllerType.Name())
+}