// 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 }