--- /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 (
+ "reflect"
+ "strings"
+)
+
+// Map from "Controller" or "Controller.Method" to the Filter chain
+var filterOverrides = make(map[string][]Filter)
+
+// FilterConfigurator allows the developer configure the filter chain on a
+// per-controller or per-action basis. The filter configuration is applied by
+// the FilterConfiguringFilter, which is itself a filter stage. For example,
+//
+// Assuming:
+// Filters = []Filter{
+// RouterFilter,
+// FilterConfiguringFilter,
+// SessionFilter,
+// ActionInvoker,
+// }
+//
+// Add:
+// FilterAction(App.Action).
+// Add(OtherFilter)
+//
+// => RouterFilter, FilterConfiguringFilter, SessionFilter, OtherFilter, ActionInvoker
+//
+// Remove:
+// FilterAction(App.Action).
+// Remove(SessionFilter)
+//
+// => RouterFilter, FilterConfiguringFilter, OtherFilter, ActionInvoker
+//
+// Insert:
+// FilterAction(App.Action).
+// Insert(OtherFilter, revel.BEFORE, SessionFilter)
+//
+// => RouterFilter, FilterConfiguringFilter, OtherFilter, SessionFilter, ActionInvoker
+//
+// Filter modifications may be combined between Controller and Action. For example:
+// FilterController(App{}).
+// Add(Filter1)
+// FilterAction(App.Action).
+// Add(Filter2)
+//
+// .. would result in App.Action being filtered by both Filter1 and Filter2.
+//
+// Note: the last filter stage is not subject to the configurator. In
+// particular, Add() adds a filter to the second-to-last place.
+type FilterConfigurator struct {
+ key string // e.g. "App", "App.Action"
+ controllerName string // e.g. "App"
+}
+
+func newFilterConfigurator(controllerName, methodName string) FilterConfigurator {
+ if methodName == "" {
+ return FilterConfigurator{controllerName, controllerName}
+ }
+ return FilterConfigurator{controllerName + "." + methodName, controllerName}
+}
+
+// FilterController returns a configurator for the filters applied to all
+// actions on the given controller instance. For example:
+// FilterController(MyController{})
+func FilterController(controllerInstance interface{}) FilterConfigurator {
+ t := reflect.TypeOf(controllerInstance)
+ for t.Kind() == reflect.Ptr {
+ t = t.Elem()
+ }
+ return newFilterConfigurator(t.Name(), "")
+}
+
+// FilterAction returns a configurator for the filters applied to the given
+// controller method. For example:
+// FilterAction(MyController.MyAction)
+func FilterAction(methodRef interface{}) FilterConfigurator {
+ var (
+ methodValue = reflect.ValueOf(methodRef)
+ methodType = methodValue.Type()
+ )
+ if methodType.Kind() != reflect.Func || methodType.NumIn() == 0 {
+ panic("Expecting a controller method reference (e.g. Controller.Action), got a " +
+ methodType.String())
+ }
+
+ controllerType := methodType.In(0)
+ method := FindMethod(controllerType, methodValue)
+ if method == nil {
+ panic("Action not found on controller " + controllerType.Name())
+ }
+
+ for controllerType.Kind() == reflect.Ptr {
+ controllerType = controllerType.Elem()
+ }
+
+ return newFilterConfigurator(controllerType.Name(), method.Name)
+}
+
+// Add the given filter in the second-to-last position in the filter chain.
+// (Second-to-last so that it is before ActionInvoker)
+func (conf FilterConfigurator) Add(f Filter) FilterConfigurator {
+ conf.apply(func(fc []Filter) []Filter {
+ return conf.addFilter(f, fc)
+ })
+ return conf
+}
+
+func (conf FilterConfigurator) addFilter(f Filter, fc []Filter) []Filter {
+ return append(fc[:len(fc)-1], f, fc[len(fc)-1])
+}
+
+// Remove a filter from the filter chain.
+func (conf FilterConfigurator) Remove(target Filter) FilterConfigurator {
+ conf.apply(func(fc []Filter) []Filter {
+ return conf.rmFilter(target, fc)
+ })
+ return conf
+}
+
+func (conf FilterConfigurator) rmFilter(target Filter, fc []Filter) []Filter {
+ for i, f := range fc {
+ if FilterEq(f, target) {
+ return append(fc[:i], fc[i+1:]...)
+ }
+ }
+ return fc
+}
+
+// Insert a filter into the filter chain before or after another.
+// This may be called with the BEFORE or AFTER constants, for example:
+// revel.FilterAction(App.Index).
+// Insert(MyFilter, revel.BEFORE, revel.ActionInvoker).
+// Insert(MyFilter2, revel.AFTER, revel.PanicFilter)
+func (conf FilterConfigurator) Insert(insert Filter, where When, target Filter) FilterConfigurator {
+ if where != BEFORE && where != AFTER {
+ panic("where must be BEFORE or AFTER")
+ }
+ conf.apply(func(fc []Filter) []Filter {
+ return conf.insertFilter(insert, where, target, fc)
+ })
+ return conf
+}
+
+func (conf FilterConfigurator) insertFilter(insert Filter, where When, target Filter, fc []Filter) []Filter {
+ for i, f := range fc {
+ if FilterEq(f, target) {
+ if where == BEFORE {
+ return append(fc[:i], append([]Filter{insert}, fc[i:]...)...)
+ }
+ return append(fc[:i+1], append([]Filter{insert}, fc[i+1:]...)...)
+ }
+ }
+ return fc
+}
+
+// getChain returns the filter chain that applies to the given controller or
+// action. If no overrides are configured, then a copy of the default filter
+// chain is returned.
+func (conf FilterConfigurator) getChain() []Filter {
+ var filters []Filter
+ if filters = getOverrideChain(conf.controllerName, conf.key); filters == nil {
+ // The override starts with all filters after FilterConfiguringFilter
+ for i, f := range Filters {
+ if FilterEq(f, FilterConfiguringFilter) {
+ filters = make([]Filter, len(Filters)-i-1)
+ copy(filters, Filters[i+1:])
+ break
+ }
+ }
+ if filters == nil {
+ panic("FilterConfiguringFilter not found in revel.Filters.")
+ }
+ }
+ return filters
+}
+
+// apply applies the given functional change to the filter overrides.
+// No other function modifies the filterOverrides map.
+func (conf FilterConfigurator) apply(f func([]Filter) []Filter) {
+ // Updates any actions that have had their filters overridden, if this is a
+ // Controller configurator.
+ if conf.controllerName == conf.key {
+ for k, v := range filterOverrides {
+ if strings.HasPrefix(k, conf.controllerName+".") {
+ filterOverrides[k] = f(v)
+ }
+ }
+ }
+
+ // Update the Controller or Action overrides.
+ filterOverrides[conf.key] = f(conf.getChain())
+}
+
+// FilterEq returns true if the two filters reference the same filter.
+func FilterEq(a, b Filter) bool {
+ return reflect.ValueOf(a).Pointer() == reflect.ValueOf(b).Pointer()
+}
+
+// FilterConfiguringFilter is a filter stage that customizes the remaining
+// filter chain for the action being invoked.
+func FilterConfiguringFilter(c *Controller, fc []Filter) {
+ if newChain := getOverrideChain(c.Name, c.Action); newChain != nil {
+ newChain[0](c, newChain[1:])
+ return
+ }
+ fc[0](c, fc[1:])
+}
+
+// getOverrideChain retrieves the overrides for the action that is set
+func getOverrideChain(controllerName, action string) []Filter {
+ if newChain, ok := filterOverrides[action]; ok {
+ return newChain
+ }
+ if newChain, ok := filterOverrides[controllerName]; ok {
+ return newChain
+ }
+ return nil
+}