File: filterconfig.go

package info (click to toggle)
golang-github-revel-revel 0.12.0+dfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid
  • size: 828 kB
  • sloc: xml: 7; makefile: 4
file content (220 lines) | stat: -rw-r--r-- 6,838 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
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:
//   FilterAction(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:]...)...)
			} else {
				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
}