X-Git-Url: https://gerrit.akraino.org/r/gitweb?a=blobdiff_plain;f=src%2Ffoundation%2Fapi%2Frevel%2Fcontroller.go;fp=src%2Ffoundation%2Fapi%2Frevel%2Fcontroller.go;h=29dbf7c706d53b4c1b7381e39420c022fe829d9e;hb=1d1ee6961c93781e1187d8c7faa868da6b2f01f4;hp=0000000000000000000000000000000000000000;hpb=56dd5e0f2164b37b40ac1daa188ccc618b4cbd19;p=iec.git diff --git a/src/foundation/api/revel/controller.go b/src/foundation/api/revel/controller.go new file mode 100644 index 0000000..29dbf7c --- /dev/null +++ b/src/foundation/api/revel/controller.go @@ -0,0 +1,546 @@ +// 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()) +}