blob: d905322ecd05d9c46efc2fd8057cecd84279ac83 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package conf
import (
"bufio"
"fmt"
"io"
"log"
"os"
"strconv"
"strings"
"syscall"
)
//
// The configuration code for HTraced.
//
// HTraced can be configured via Hadoop-style XML configuration files, or by passing -Dkey=value
// command line arguments. Command-line arguments without an equals sign, such as "-Dkey", will be
// treated as setting the key to "true".
//
// Configuration key constants should be defined in config_keys.go. Each key should have a default,
// which will be used if the user supplies no value, or supplies an invalid value.
// For that reason, it is not necessary for the Get, GetInt, etc. functions to take a default value
// argument.
//
// Configuration objects are immutable. However, you can make a copy of a configuration which adds
// some changes using Configuration#Clone().
//
type Config struct {
settings map[string]string
defaults map[string]string
}
type Builder struct {
// If non-nil, the XML configuration file to read.
Reader io.Reader
// If non-nil, the configuration values to use.
Values map[string]string
// If non-nil, the default configuration values to use.
Defaults map[string]string
// If non-nil, the command-line arguments to use.
Argv []string
}
// Load a configuration from the application's argv, configuration file, and the standard
// defaults.
func LoadApplicationConfig(values map[string]string) *Config {
reader, err := openFile(CONFIG_FILE_NAME, []string{"."})
if err != nil {
log.Fatal("Error opening config file: " + err.Error())
}
bld := Builder{}
if reader != nil {
defer reader.Close()
bld.Reader = bufio.NewReader(reader)
}
bld.Argv = os.Args[1:]
bld.Defaults = DEFAULTS
if values != nil {
bld.Values = values
}
var cnf *Config
cnf, err = bld.Build()
if err != nil {
log.Fatal("Error building configuration: " + err.Error())
}
os.Args = append(os.Args[0:1], bld.Argv...)
return cnf
}
// Attempt to open a configuration file somewhere on the provided list of paths.
func openFile(cnfName string, paths []string) (io.ReadCloser, error) {
for p := range paths {
path := fmt.Sprintf("%s%c%s", paths[p], os.PathSeparator, cnfName)
file, err := os.Open(path)
if err == nil {
log.Println("Reading configuration from " + path)
return file, nil
}
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENOENT {
continue
}
log.Println("Error opening " + path + " for read: " + err.Error())
}
return nil, nil
}
// Build a new configuration object from the provided conf.Builder.
func (bld *Builder) Build() (*Config, error) {
// Load values and defaults
cnf := Config{}
cnf.settings = make(map[string]string)
if bld.Values != nil {
for k, v := range bld.Values {
cnf.settings[k] = v
}
}
cnf.defaults = make(map[string]string)
if bld.Defaults != nil {
for k, v := range bld.Defaults {
cnf.defaults[k] = v
}
}
// Process the configuration file, if we have one
if bld.Reader != nil {
parseXml(bld.Reader, cnf.settings)
}
// Process command line arguments
var i int
for i < len(bld.Argv) {
str := bld.Argv[i]
if strings.HasPrefix(str, "-D") {
idx := strings.Index(str, "=")
if idx == -1 {
key := str[2:]
cnf.settings[key] = "true"
} else {
key := str[2:idx]
val := str[idx+1:]
cnf.settings[key] = val
}
bld.Argv = append(bld.Argv[:i], bld.Argv[i+1:]...)
} else {
i++
}
}
return &cnf, nil
}
// Returns true if the configuration has a non-default value for the given key.
func (cnf *Config) Contains(key string) bool {
_, ok := cnf.settings[key]
return ok
}
// Get a string configuration key.
func (cnf *Config) Get(key string) string {
ret := cnf.settings[key]
if ret != "" {
return ret
}
return cnf.defaults[key]
}
// Get a boolean configuration key.
func (cnf *Config) GetBool(key string) bool {
str := cnf.settings[key]
ret, err := strconv.ParseBool(str)
if err == nil {
return ret
}
str = cnf.defaults[key]
ret, err = strconv.ParseBool(str)
if err == nil {
return ret
}
return false
}
// Get an integer configuration key.
func (cnf *Config) GetInt(key string) int {
str := cnf.settings[key]
ret, err := strconv.Atoi(str)
if err == nil {
return ret
}
str = cnf.defaults[key]
ret, err = strconv.Atoi(str)
if err == nil {
return ret
}
return 0
}
// Get an int64 configuration key.
func (cnf *Config) GetInt64(key string) int64 {
str := cnf.settings[key]
ret, err := strconv.ParseInt(str, 10, 64)
if err == nil {
return ret
}
str = cnf.defaults[key]
ret, err = strconv.ParseInt(str, 10, 64)
if err == nil {
return ret
}
return 0
}
// Make a deep copy of the given configuration.
// Optionally, you can specify particular key/value pairs to change.
// Example:
// cnf2 := cnf.Copy("my.changed.key", "my.new.value")
func (cnf *Config) Clone(args ...string) *Config {
if len(args)%2 != 0 {
panic("The arguments to Config#copy are key1, value1, " +
"key2, value2, and so on. You must specify an even number of arguments.")
}
ncnf := &Config{defaults: cnf.defaults}
ncnf.settings = make(map[string]string)
for k, v := range cnf.settings {
ncnf.settings[k] = v
}
for i := 0; i < len(args); i += 2 {
ncnf.settings[args[i]] = args[i+1]
}
return ncnf
}