/**
* Copyright The Apache Software Foundation
*
* 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 org.apache.hadoop.hbase;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.util.StringUtils;
/**
* Do a shallow merge of multiple KV configuration pools. This is a very useful
* utility class to easily add per-object configurations in addition to wider
* scope settings. This is different from Configuration.addResource()
* functionality, which performs a deep merge and mutates the common data
* structure.
* <p>
* For clarity: the shallow merge allows the user to mutate either of the
* configuration objects and have changes reflected everywhere. In contrast to a
* deep merge, that requires you to explicitly know all applicable copies to
* propagate changes.
*/
@InterfaceAudience.Private
public class CompoundConfiguration extends Configuration {
/**
* Default Constructor. Initializes empty configuration
*/
public CompoundConfiguration() {
}
// Devs: these APIs are the same contract as their counterparts in
// Configuration.java
private static interface ImmutableConfigMap {
String get(String key);
String getRaw(String key);
Class<?> getClassByName(String name) throws ClassNotFoundException;
int size();
}
protected List<ImmutableConfigMap> configs
= new ArrayList<ImmutableConfigMap>();
/**
* Add Hadoop Configuration object to config list.
* The added configuration overrides the previous ones if there are name collisions.
* @param conf configuration object
* @return this, for builder pattern
*/
public CompoundConfiguration add(final Configuration conf) {
if (conf instanceof CompoundConfiguration) {
this.configs.addAll(0, ((CompoundConfiguration) conf).configs);
return this;
}
// put new config at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() {
Configuration c = conf;
@Override
public String get(String key) {
return c.get(key);
}
@Override
public String getRaw(String key) {
return c.getRaw(key);
}
@Override
public Class<?> getClassByName(String name)
throws ClassNotFoundException {
return c.getClassByName(name);
}
@Override
public int size() {
return c.size();
}
@Override
public String toString() {
return c.toString();
}
});
return this;
}
/**
* Add ImmutableBytesWritable map to config list. This map is generally
* created by HTableDescriptor or HColumnDescriptor, but can be abstractly
* used. The added configuration overrides the previous ones if there are
* name collisions.
*
* @param map
* ImmutableBytesWritable map
* @return this, for builder pattern
*/
public CompoundConfiguration addWritableMap(
final Map<ImmutableBytesWritable, ImmutableBytesWritable> map) {
// put new map at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() {
Map<ImmutableBytesWritable, ImmutableBytesWritable> m = map;
@Override
public String get(String key) {
ImmutableBytesWritable ibw = new ImmutableBytesWritable(Bytes
.toBytes(key));
if (!m.containsKey(ibw))
return null;
ImmutableBytesWritable value = m.get(ibw);
if (value == null || value.get() == null)
return null;
return Bytes.toString(value.get());
}
@Override
public String getRaw(String key) {
return get(key);
}
@Override
public Class<?> getClassByName(String name)
throws ClassNotFoundException {
return null;
}
@Override
public int size() {
return m.size();
}
@Override
public String toString() {
return m.toString();
}
});
return this;
}
/**
* Add String map to config list. This map is generally created by HTableDescriptor
* or HColumnDescriptor, but can be abstractly used. The added configuration
* overrides the previous ones if there are name collisions.
*
* @return this, for builder pattern
*/
public CompoundConfiguration addStringMap(final Map<String, String> map) {
// put new map at the front of the list (top priority)
this.configs.add(0, new ImmutableConfigMap() {
Map<String, String> m = map;
@Override
public String get(String key) {
return m.get(key);
}
@Override
public String getRaw(String key) {
return get(key);
}
@Override
public Class<?> getClassByName(String name)
throws ClassNotFoundException {
return null;
}
@Override
public int size() {
return m.size();
}
@Override
public String toString() {
return m.toString();
}
});
return this;
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("CompoundConfiguration: " + this.configs.size() + " configs");
for (ImmutableConfigMap m : this.configs) {
sb.append(this.configs);
}
return sb.toString();
}
@Override
public String get(String key) {
for (ImmutableConfigMap m : this.configs) {
String value = m.get(key);
if (value != null) {
return value;
}
}
return null;
}
@Override
public String getRaw(String key) {
for (ImmutableConfigMap m : this.configs) {
String value = m.getRaw(key);
if (value != null) {
return value;
}
}
return null;
}
@Override
public Class<?> getClassByName(String name) throws ClassNotFoundException {
for (ImmutableConfigMap m : this.configs) {
Class<?> value = m.getClassByName(name);
if (value != null) {
return value;
}
}
throw new ClassNotFoundException();
}
@Override
public int size() {
int ret = 0;
for (ImmutableConfigMap m : this.configs) {
ret += m.size();
}
return ret;
}
/***************************************************************************
* You should just ignore everything below this line unless there's a bug in
* Configuration.java...
*
* Below get APIs are directly copied from Configuration.java Oh, how I wish
* this wasn't so! A tragically-sad example of why you use interfaces instead
* of inheritance.
*
* Why the duplication? We basically need to override Configuration.getProps
* or we'd need protected access to Configuration.properties so we can modify
* that pointer. There are a bunch of functions in the base Configuration that
* call getProps() and we need to use our derived version instead of the base
* version. We need to make a generic implementation that works across all
* HDFS versions. We should modify Configuration.properties in HDFS 1.0 to be
* protected, but we still need to have this code until that patch makes it to
* all the HDFS versions we support.
***************************************************************************/
@Override
public String get(String name, String defaultValue) {
String ret = get(name);
return ret == null ? defaultValue : ret;
}
@Override
public int getInt(String name, int defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Integer.parseInt(hexString, 16);
}
return Integer.parseInt(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public long getLong(String name, long defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
String hexString = getHexDigits(valueString);
if (hexString != null) {
return Long.parseLong(hexString, 16);
}
return Long.parseLong(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
protected String getHexDigits(String value) {
boolean negative = false;
String str = value;
String hexString = null;
if (value.startsWith("-")) {
negative = true;
str = value.substring(1);
}
if (str.startsWith("0x") || str.startsWith("0X")) {
hexString = str.substring(2);
if (negative) {
hexString = "-" + hexString;
}
return hexString;
}
return null;
}
@Override
public float getFloat(String name, float defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
return Float.parseFloat(valueString);
} catch (NumberFormatException e) {
return defaultValue;
}
}
@Override
public boolean getBoolean(String name, boolean defaultValue) {
String valueString = get(name);
if ("true".equals(valueString))
return true;
else if ("false".equals(valueString))
return false;
else return defaultValue;
}
@Override
public IntegerRanges getRange(String name, String defaultValue) {
return new IntegerRanges(get(name, defaultValue));
}
@Override
public Collection<String> getStringCollection(String name) {
String valueString = get(name);
return StringUtils.getStringCollection(valueString);
}
@Override
public String[] getStrings(String name) {
String valueString = get(name);
return StringUtils.getStrings(valueString);
}
@Override
public String[] getStrings(String name, String... defaultValue) {
String valueString = get(name);
if (valueString == null) {
return defaultValue;
} else {
return StringUtils.getStrings(valueString);
}
}
@Override
public Class<?>[] getClasses(String name, Class<?>... defaultValue) {
String[] classnames = getStrings(name);
if (classnames == null)
return defaultValue;
try {
Class<?>[] classes = new Class<?>[classnames.length];
for (int i = 0; i < classnames.length; i++) {
classes[i] = getClassByName(classnames[i]);
}
return classes;
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public Class<?> getClass(String name, Class<?> defaultValue) {
String valueString = get(name);
if (valueString == null)
return defaultValue;
try {
return getClassByName(valueString);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
@Override
public <U> Class<? extends U> getClass(String name,
Class<? extends U> defaultValue, Class<U> xface) {
try {
Class<?> theClass = getClass(name, defaultValue);
if (theClass != null && !xface.isAssignableFrom(theClass))
throw new RuntimeException(theClass + " not " + xface.getName());
else if (theClass != null)
return theClass.asSubclass(xface);
else
return null;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/*******************************************************************
* This class is immutable. Quickly abort any attempts to alter it *
*******************************************************************/
@Override
public void clear() {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public Iterator<Map.Entry<String, String>> iterator() {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void set(String name, String value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setIfUnset(String name, String value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setInt(String name, int value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setLong(String name, long value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setFloat(String name, float value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setBoolean(String name, boolean value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setBooleanIfUnset(String name, boolean value) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setStrings(String name, String... values) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setClass(String name, Class<?> theClass, Class<?> xface) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void setClassLoader(ClassLoader classLoader) {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void readFields(DataInput in) throws IOException {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void write(DataOutput out) throws IOException {
throw new UnsupportedOperationException("Immutable Configuration");
}
@Override
public void writeXml(OutputStream out) throws IOException {
throw new UnsupportedOperationException("Immutable Configuration");
}
};