Permalink
247 lines (217 sloc) 6.84 KB
// 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 (
"log"
"reflect"
"sort"
)
// An InterceptorFunc is functionality invoked by the framework BEFORE or AFTER
// an action.
//
// An interceptor may optionally return a Result (instead of nil). Depending on
// when the interceptor was invoked, the response is different:
// 1. BEFORE: No further interceptors are invoked, and neither is the action.
// 2. AFTER: Further interceptors are still run.
// In all cases, any returned Result will take the place of any existing Result.
//
// In the BEFORE case, that returned Result is guaranteed to be final, while
// in the AFTER case it is possible that a further interceptor could emit its
// own Result.
//
// Interceptors are called in the order that they are added.
//
// ***
//
// Two types of interceptors are provided: Funcs and Methods
//
// Func Interceptors may apply to any / all Controllers.
//
// func example(*revel.Controller) revel.Result
//
// Method Interceptors are provided so that properties can be set on application
// controllers.
//
// func (c AppController) example() revel.Result
// func (c *AppController) example() revel.Result
//
type InterceptorFunc func(*Controller) Result
type InterceptorMethod interface{}
type When int
const (
BEFORE When = iota
AFTER
PANIC
FINALLY
)
type InterceptTarget int
const (
AllControllers InterceptTarget = iota
)
type Interception struct {
When When
function InterceptorFunc
method InterceptorMethod
callable reflect.Value
target reflect.Type
interceptAll bool
}
// Invoke performs the given interception.
// val is a pointer to the App Controller.
func (i Interception) Invoke(val reflect.Value, target *reflect.Value) reflect.Value {
var arg reflect.Value
if i.function == nil {
// If it's an InterceptorMethod, then we have to pass in the target type.
arg = *target
} else {
// If it's an InterceptorFunc, then the type must be *Controller.
// We can find that by following the embedded types up the chain.
for val.Type() != controllerPtrType {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
val = val.Field(0)
}
arg = val
}
vals := i.callable.Call([]reflect.Value{arg})
return vals[0]
}
func InterceptorFilter(c *Controller, fc []Filter) {
defer invokeInterceptors(FINALLY, c)
defer func() {
if err := recover(); err != nil {
invokeInterceptors(PANIC, c)
panic(err)
}
}()
// Invoke the BEFORE interceptors and return early, if we get a result.
invokeInterceptors(BEFORE, c)
if c.Result != nil {
return
}
fc[0](c, fc[1:])
invokeInterceptors(AFTER, c)
}
func invokeInterceptors(when When, c *Controller) {
var (
app = reflect.ValueOf(c.AppController)
result Result
)
for _, intc := range getInterceptors(when, app) {
resultValue := intc.Interceptor.Invoke(app, &intc.Target)
if !resultValue.IsNil() {
result = resultValue.Interface().(Result)
}
if when == BEFORE && result != nil {
c.Result = result
return
}
}
if result != nil {
c.Result = result
}
}
var interceptors []*Interception
// InterceptFunc installs a general interceptor.
// This can be applied to any Controller.
// It must have the signature of:
// func example(c *revel.Controller) revel.Result
func InterceptFunc(intc InterceptorFunc, when When, target interface{}) {
interceptors = append(interceptors, &Interception{
When: when,
function: intc,
callable: reflect.ValueOf(intc),
target: reflect.TypeOf(target),
interceptAll: target == AllControllers,
})
}
// InterceptMethod installs an interceptor method that applies to its own Controller.
// func (c AppController) example() revel.Result
// func (c *AppController) example() revel.Result
func InterceptMethod(intc InterceptorMethod, when When) {
methodType := reflect.TypeOf(intc)
if methodType.Kind() != reflect.Func || methodType.NumOut() != 1 || methodType.NumIn() != 1 {
log.Fatalln("Interceptor method should have signature like",
"'func (c *AppController) example() revel.Result' but was", methodType)
}
interceptors = append(interceptors, &Interception{
When: when,
method: intc,
callable: reflect.ValueOf(intc),
target: methodType.In(0),
})
}
// This item is used to provide a sortable set to be returned to the caller. This ensures calls order is maintained
//
type interceptorItem struct {
Interceptor *Interception
Target reflect.Value
Level int
}
type interceptorItemList []*interceptorItem
func (a interceptorItemList) Len() int { return len(a) }
func (a interceptorItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a interceptorItemList) Less(i, j int) bool { return a[i].Level < a[j].Level }
type reverseInterceptorItemList []*interceptorItem
func (a reverseInterceptorItemList) Len() int { return len(a) }
func (a reverseInterceptorItemList) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a reverseInterceptorItemList) Less(i, j int) bool { return a[i].Level > a[j].Level }
func getInterceptors(when When, val reflect.Value) interceptorItemList {
result := interceptorItemList{}
for _, intc := range interceptors {
if intc.When != when {
continue
}
level,target := findTarget(val, intc.target)
if intc.interceptAll || target.IsValid() {
result = append(result, &interceptorItem{intc, target, level})
}
}
// Before is deepest to highest
if when ==BEFORE {
sort.Sort(result)
} else {
// Everything else is highest to deepest
sort.Sort(reverseInterceptorItemList(result))
}
return result
}
// Find the value of the target, starting from val and including embedded types.
// Also, convert between any difference in indirection.
// If the target couldn't be found, the returned Value will have IsValid() == false
func findTarget(val reflect.Value, target reflect.Type) (int, reflect.Value) {
// Look through the embedded types (until we reach the *revel.Controller at the top).
valueQueue := []reflect.Value{val}
level := 0
for len(valueQueue) > 0 {
val, valueQueue = valueQueue[0], valueQueue[1:]
// Check if val is of a similar type to the target type.
if val.Type() == target {
return level,val
}
if val.Kind() == reflect.Ptr && val.Elem().Type() == target {
return level,val.Elem()
}
if target.Kind() == reflect.Ptr && target.Elem() == val.Type() {
return level,val.Addr()
}
// If we reached the *revel.Controller and still didn't find what we were
// looking for, give up.
if val.Type() == controllerPtrType {
continue
}
// Else, add each anonymous field to the queue.
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
for i := 0; i < val.NumField(); i++ {
if val.Type().Field(i).Anonymous {
valueQueue = append(valueQueue, val.Field(i))
}
}
level --
}
return level, reflect.Value{}
}