/* Copyright 2013 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#include "mongo/util/options_parser/environment.h"
#include
#include "mongo/bson/util/builder.h"
#include "mongo/db/jsobj.h"
#include "mongo/util/options_parser/constraints.h"
namespace mongo {
namespace optionenvironment {
using std::shared_ptr;
using std::string;
using std::type_info;
// Environment implementation
Status Environment::addKeyConstraint(KeyConstraint* keyConstraint) {
keyConstraints.push_back(keyConstraint);
return Status::OK();
}
Status Environment::addConstraint(Constraint* constraint) {
constraints.push_back(constraint);
return Status::OK();
}
/** Get the value at Key. Note that we should not be able to add empty values to the
* environment, so we don't check for that here */
Status Environment::get(const Key& get_key, Value* get_value) const {
typedef std::map::const_iterator it_type;
it_type value = values.find(get_key);
if (value == values.end()) {
value = default_values.find(get_key);
if (value == default_values.end()) {
StringBuilder sb;
sb << "Value not found for key: " << get_key;
return Status(ErrorCodes::NoSuchKey, sb.str());
}
}
*get_value = value->second;
return Status::OK();
}
/** Set the Value in our Environment. Always disallow empty values */
Status Environment::set(const Key& add_key, const Value& add_value) {
// 1. Make sure value is not empty
if (add_value.isEmpty()) {
return Status(ErrorCodes::InternalError, "Attempted to add an empty value");
}
// 2. Save old values
std::map old_values = values;
// 3. Add value to be added
values[add_key] = add_value;
// 4. Validate only if our environment is already valid
if (valid) {
Status ret = validate();
if (!ret.isOK()) {
// 5. Revert to old values if this was invalid
values = old_values;
return ret;
}
}
return Status::OK();
}
/** Removes a Value from our Environment */
Status Environment::remove(const Key& remove_key) {
// 1. Save old values
std::map old_values = values;
// 2. Remove value to be removed
values.erase(remove_key);
// 3. Validate only if our environment is already valid
if (valid) {
Status ret = validate();
if (!ret.isOK()) {
// 4. Revert to old values if this was invalid
values = old_values;
return ret;
}
}
return Status::OK();
}
/** Set the default Value for the given Key in our Environment. Always disallow empty values */
Status Environment::setDefault(const Key& add_key, const Value& add_value) {
// 1. Make sure value is not empty
if (add_value.isEmpty()) {
return Status(ErrorCodes::InternalError, "Attempted to set an empty default value");
}
// 2. Disallow modifying defaults after calling validate on this Environment
if (valid) {
return Status(ErrorCodes::InternalError,
"Attempted to set a default value after calling validate");
}
// 3. Add this value to our defaults
default_values[add_key] = add_value;
return Status::OK();
}
/** Set all the Values from the source Environment in our Environment. Does not check for empty
* values as the source Environment should not have been allowed to have any */
Status Environment::setAll(const Environment& add_environment) {
// 1. Save old values
std::map old_values = values;
// 2. Add values to be added
std::map add_values = add_environment.values;
for (std::map::const_iterator iterator = add_values.begin();
iterator != add_values.end();
iterator++) {
values[iterator->first] = iterator->second;
}
// 3. Validate only if our environment is already valid
if (valid) {
Status ret = validate();
if (!ret.isOK()) {
// 4. Revert to old values if this was invalid
values = old_values;
return ret;
}
}
return Status::OK();
}
/** Validate the Environment by iterating over all our constraints and calling them on our
* Environment
*/
Status Environment::validate(bool setValid) {
// 1. Iterate and check all KeyConstraints
typedef std::vector::iterator it_keyConstraint;
for (it_keyConstraint iterator = keyConstraints.begin(); iterator != keyConstraints.end();
iterator++) {
Status ret = (**iterator)(*this);
if (!ret.isOK()) {
return ret;
}
}
// 2. Iterate and check all Constraints
typedef std::vector::iterator it_constraint;
for (it_constraint iterator = constraints.begin(); iterator != constraints.end(); iterator++) {
Status ret = (**iterator)(*this);
if (!ret.isOK()) {
return ret;
}
}
// 3. Our Environment is now valid. Record this if we should and return success
if (setValid) {
valid = true;
}
return Status::OK();
}
/** Implementation of legacy interface to be consistent with
* boost::program_options::variables_map during the transition period
*
* boost::program_options::variables_map inherits the count function from std::map, which
* returns 1 if the value is set, and 0 if it is not set
*/
bool Environment::count(const Key& key) const {
Value value;
Status ret = get(key, &value);
if (ret.isOK()) {
return true;
} else {
return false;
}
}
Value Environment::operator[](const Key& key) const {
Value value;
Status ret = get(key, &value);
return value;
}
/* Debugging */
void Environment::dump() const {
std::map::const_iterator iter;
for (iter = values.begin(); iter != values.end(); ++iter) {
std::cout << "Key: '" << iter->first << "', Value: '" << iter->second.toString() << "'"
<< std::endl;
}
}
namespace {
// Converts a map of values with dotted key names to a BSONObj with sub objects.
// 1. Check for dotted field names and call valueMapToBSON recursively.
// 2. Append the actual value to our builder if we did not find a dot in our key name.
Status valueMapToBSON(const std::map& params,
BSONObjBuilder* builder,
const std::string& prefix = std::string()) {
for (std::map::const_iterator it(params.begin()); it != params.end(); it++) {
Key key = it->first;
Value value = it->second;
// 1. Check for dotted field names and call valueMapToBSON recursively.
// NOTE: this code depends on the fact that std::map is sorted
//
// EXAMPLE:
// The map:
// {
// "var1.dotted1" : false,
// "var2" : true,
// "var1.dotted2" : 6
// }
//
// Gets sorted by keys as:
// {
// "var1.dotted1" : false,
// "var1.dotted2" : 6,
// "var2" : true
// }
//
// Which means when we see the "var1" prefix, we can iterate until we see either a name
// without a dot or without "var1" as a prefix, aggregating its fields in a new map as
// we go. Because the map is sorted, once we see a name without a dot or a "var1"
// prefix we know that we've seen everything with "var1" as a prefix and can recursively
// build the entire sub object at once using our new map (which is the only way to make
// a single coherent BSON sub object using this append only builder).
//
// The result of this function for this example should be a BSON object of the form:
// {
// "var1" : {
// "dotted1" : false,
// "dotted2" : 6
// },
// "var2" : true
// }
// Check to see if this key name is dotted
std::string::size_type dotOffset = key.find('.');
if (dotOffset != string::npos) {
// Get the name of the "section" that we are currently iterating. This will be
// the name of our sub object.
std::string sectionName = key.substr(0, dotOffset);
// Build a map of the "section" that we are iterating to be passed in a
// recursive call.
std::map sectionMap;
std::string beforeDot = key.substr(0, dotOffset);
std::string afterDot = key.substr(dotOffset + 1, key.size() - dotOffset - 1);
std::map::const_iterator it_next = it;
do {
// Here we know that the key at it_next has a dot and has the prefix we are
// currently creating a sub object for. Since that means we will definitely
// process that element in this loop, advance the outer for loop iterator here.
it = it_next;
// Add the value to our section map with a key of whatever is after the dot
// since the section name itself will be part of our sub object builder.
sectionMap[afterDot] = value;
// Peek at the next value for our iterator and check to see if we've finished.
if (++it_next == params.end()) {
break;
}
key = it_next->first;
value = it_next->second;
// Look for a dot for our next iteration.
dotOffset = key.find('.');
beforeDot = key.substr(0, dotOffset);
afterDot = key.substr(dotOffset + 1, key.size() - dotOffset - 1);
} while (dotOffset != string::npos && beforeDot == sectionName);
// Use the section name in our object builder, and recursively call
// valueMapToBSON with our sub map with keys that have the section name removed.
BSONObjBuilder sectionObjBuilder(builder->subobjStart(sectionName));
valueMapToBSON(sectionMap, §ionObjBuilder, sectionName).transitional_ignore();
sectionObjBuilder.done();
// Our iterator is currently on the last field that matched our dot and prefix, so
// continue to the next loop iteration.
continue;
}
// 2. Append the actual value to our builder if we did not find a dot in our key name.
const type_info& type = value.type();
if (type == typeid(string)) {
if (value.as().empty()) {
// boost po uses empty string for flags like --quiet
// TODO: Remove this when we remove boost::program_options
builder->appendBool(key, true);
} else {
builder->append(key, value.as());
}
} else if (type == typeid(int))
builder->append(key, value.as());
else if (type == typeid(double))
builder->append(key, value.as());
else if (type == typeid(bool))
builder->appendBool(key, value.as());
else if (type == typeid(long))
builder->appendNumber(key, (long long)value.as());
else if (type == typeid(unsigned))
builder->appendNumber(key, (long long)value.as());
else if (type == typeid(unsigned long long))
builder->appendNumber(key, (long long)value.as());
else if (type == typeid(StringVector_t))
builder->append(key, value.as());
else if (type == typeid(StringMap_t)) {
BSONObjBuilder subBuilder(builder->subobjStart(key));
StringMap_t stringMap = value.as();
for (StringMap_t::iterator stringMapIt = stringMap.begin();
stringMapIt != stringMap.end();
stringMapIt++) {
subBuilder.append(stringMapIt->first, stringMapIt->second);
}
subBuilder.done();
} else
builder->append(key, "UNKNOWN TYPE: " + demangleName(type));
}
return Status::OK();
}
} // namespace
BSONObj Environment::toBSON() const {
BSONObjBuilder builder;
Status ret = valueMapToBSON(values, &builder);
if (!ret.isOK()) {
return BSONObj();
}
return builder.obj();
}
} // namespace optionenvironment
} // namespace mongo