func (s *service) ExtractConfig(configData BackendConfig, configType BaseConfig) (interface{}, error) {
	v := reflect.ValueOf(configType).Elem()  
	t := reflect.TypeOf(configType).Elem()
	typeOfT := v.Type()
	for i := 0; i < v.NumField(); i++ {
		f := v.Field(i)
		fieldName := t.Field(i).Tag.Get("json")
		omitempty := false
		if len(fieldName) > 0 {
			split := strings.Split(fieldName, ",")
			fieldName = split[0]
			if len(split) > 1 {
				if split[1] == "omitempty" {
					omitempty = true
				}
			}
		} else {
			fieldName = typeOfT.Field(i).Name
		}
		if f.Type().Name() == "int" {
			if intVal, converted := configData[fieldName].(float64); converted {
				v.Field(i).SetInt(int64(intVal))
			} else if intVal, converted := configData[fieldName].(int); converted {
				v.Field(i).SetInt(int64(intVal))
			} else if !omitempty {
				return configType, convertError("property missing/invalid: '" + fieldName + "' of expected type: " + f.Type().Name())
			}
		}
		if f.Type().Name() == "string" {
			if stringVal, converted := configData[fieldName].(string); converted {
				v.Field(i).SetString(stringVal)
			} else if !omitempty {
				return configType, convertError("missing/invalid: '" + fieldName + "' of type: " + f.Type().Name())
			}
		}
		if f.Type().Name() == "bool" {
			if boolVal, converted := configData[fieldName].(bool); converted {
				v.Field(i).SetBool(boolVal)
			} else if !omitempty {
				return configType, convertError("missing/invalid: '" + fieldName + "' of type: " + f.Type().Name())
			}
		}
	}
	return configType, nil
}
extractConfig loads the backend config. It has already been unmarshalled
configData contains data from the main config file's "backend_config" value
configType is a Processor's specific config value.
The reason why using reflection is because we'll get a nice error message if the field is missing
the alternative solution would be to json.Marshal() and json.Unmarshal() however that will not give us any
error messages
package backends
import (
	"bytes"
	"fmt"
	"github.com/flashmob/go-guerrilla/log"
	"github.com/flashmob/go-guerrilla/mail"
	"reflect"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
)
var (
	Svc *service
	processors map[string]ProcessorConstructor
	b Backend
)
func init() {
	Svc = &service{}
	processors = make(map[string]ProcessorConstructor)
}
type ProcessorConstructor func() Decorator
type Backend interface {
	Process(*mail.Envelope) Result
	ValidateRcpt(e *mail.Envelope) RcptError
	Initialize(BackendConfig) error
	Reinitialize() error
	Shutdown() error
	Start() error
}
type BackendConfig map[string]interface{}
type BaseConfig interface{}
type notifyMsg struct {
	err      error
	queuedID string
	result   Result
}
type Result interface {
	fmt.Stringer
	Code() int
}
type result struct {
	bytes.Buffer
}
func (r *result) String() string {
	return r.Buffer.String()
}
func (r *result) Code() int {
	trimmed := strings.TrimSpace(r.String())
	if len(trimmed) < 3 {
		return 554
	}
	code, err := strconv.Atoi(trimmed[:3])
	if err != nil {
		return 554
	}
	return code
}
func NewResult(r ...interface{}) Result {
	buf := new(result)
	for _, item := range r {
		switch v := item.(type) {
		case error:
			_, _ = buf.WriteString(v.Error())
		case fmt.Stringer:
			_, _ = buf.WriteString(v.String())
		case string:
			_, _ = buf.WriteString(v)
		}
	}
	return buf
}
type processorInitializer interface {
	Initialize(backendConfig BackendConfig) error
}
type processorShutdowner interface {
	Shutdown() error
}
type InitializeWith func(backendConfig BackendConfig) error
type ShutdownWith func() error
func (i InitializeWith) Initialize(backendConfig BackendConfig) error {
	return i(backendConfig)
}
func (s ShutdownWith) Shutdown() error {
	return s()
}
type Errors []error
func (e Errors) Error() string {
	if len(e) == 1 {
		return e[0].Error()
	}
	msg := ""
	for _, err := range e {
		msg += "\n" + err.Error()
	}
	return msg
}
func convertError(name string) error {
	return fmt.Errorf("failed to load backend config (%s)", name)
}
type service struct {
	initializers []processorInitializer
	shutdowners  []processorShutdowner
	sync.Mutex
	mainlog atomic.Value
}
func Log() log.Logger {
	if v, ok := Svc.mainlog.Load().(log.Logger); ok {
		return v
	}
	l, _ := log.GetLogger(log.OutputStderr.String(), log.InfoLevel.String())
	return l
}
func (s *service) SetMainlog(l log.Logger) {
	s.mainlog.Store(l)
}
func (s *service) AddInitializer(i processorInitializer) {
	s.Lock()
	defer s.Unlock()
	s.initializers = append(s.initializers, i)
}
func (s *service) AddShutdowner(sh processorShutdowner) {
	s.Lock()
	defer s.Unlock()
	s.shutdowners = append(s.shutdowners, sh)
}
func (s *service) reset() {
	s.shutdowners = make([]processorShutdowner, 0)
	s.initializers = make([]processorInitializer, 0)
}
func (s *service) initialize(backend BackendConfig) Errors {
	s.Lock()
	defer s.Unlock()
	var errors Errors
	failed := make([]processorInitializer, 0)
	for i := range s.initializers {
		if err := s.initializers[i].Initialize(backend); err != nil {
			errors = append(errors, err)
			failed = append(failed, s.initializers[i])
		}
	}
	s.initializers = failed
	return errors
}
func (s *service) shutdown() Errors {
	s.Lock()
	defer s.Unlock()
	var errors Errors
	failed := make([]processorShutdowner, 0)
	for i := range s.shutdowners {
		if err := s.shutdowners[i].Shutdown(); err != nil {
			errors = append(errors, err)
			failed = append(failed, s.shutdowners[i])
		}
	}
	s.shutdowners = failed
	return errors
}
func (s *service) AddProcessor(name string, p ProcessorConstructor) {
	var c ProcessorConstructor
	c = func() Decorator {
		return p()
	}
	processors[strings.ToLower(name)] = c
}