diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java index 6d8f58764fb27..5188f361253b9 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/Configuration.java @@ -902,6 +902,20 @@ public static void setRestrictSystemPropertiesDefault(boolean val) { restrictSystemPropsDefault = val; } + /** + * Creates an Unmodifiable view of Configuration. Any attempted changes to + * this object directly will throw an UnsupportedOperationException. The + * object returned does not prevent changes to the the configuration through + * use of static methods and means external to the object returned. + * + * @param other The configuration to wrap + * @return an Unmodifiable view of Configuration + */ + public static Configuration unmodifiableConfiguration(Configuration other) { + Configuration unmodifiable = new UnmodifiableConfiguration(other); + return unmodifiable; + } + public void setRestrictSystemProperties(boolean val) { this.restrictSystemProps = val; } diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/UnmodifiableConfiguration.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/UnmodifiableConfiguration.java new file mode 100644 index 0000000000000..9bb1d84efec55 --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/conf/UnmodifiableConfiguration.java @@ -0,0 +1,556 @@ +/* + * 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.conf; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.InetSocketAddress; +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +import com.google.common.collect.Iterators; + +import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.fs.Path; + +/** + * An unmodifiable view of a Configuration. + */ +@Unstable +class UnmodifiableConfiguration extends Configuration { + + private final Configuration other; + + UnmodifiableConfiguration(Configuration other) { + super(false); + this.other = other; + } + + @Override + public Iterator> iterator() { + return Iterators.unmodifiableIterator(other.iterator()); + } + + @Override + public String get(String name) { + return this.other.get(name); + } + + @Override + public boolean onlyKeyExists(String name) { + return this.other.onlyKeyExists(name); + } + + @Override + public String getTrimmed(String name) { + return this.other.getTrimmed(name); + } + + @Override + public String getTrimmed(String name, String defaultValue) { + return this.other.getTrimmed(name, defaultValue); + } + + @Override + public String getRaw(String name) { + return this.other.getRaw(name); + } + + @Override + public String get(String name, String defaultValue) { + return this.other.get(name, defaultValue); + } + + @Override + public int getInt(String name, int defaultValue) { + return this.other.getInt(name, defaultValue); + } + + @Override + public int[] getInts(String name) { + return this.other.getInts(name); + } + + @Override + public long getLong(String name, long defaultValue) { + return this.other.getLong(name, defaultValue); + } + + @Override + public long getLongBytes(String name, long defaultValue) { + return this.other.getLongBytes(name, defaultValue); + } + + @Override + public float getFloat(String name, float defaultValue) { + return this.other.getFloat(name, defaultValue); + } + + @Override + public double getDouble(String name, double defaultValue) { + return this.other.getDouble(name, defaultValue); + } + + @Override + public boolean getBoolean(String name, boolean defaultValue) { + return this.other.getBoolean(name, defaultValue); + } + + @Override + public > T getEnum(String name, T defaultValue) { + return this.other.getEnum(name, defaultValue); + } + + @Override + public long getTimeDuration(String name, long defaultValue, TimeUnit unit) { + return this.other.getTimeDuration(name, defaultValue, unit); + } + + @Override + public long getTimeDuration(String name, String defaultValue, TimeUnit unit) { + return this.other.getTimeDuration(name, defaultValue, unit); + } + + @Override + public long getTimeDurationHelper(String name, String vStr, TimeUnit unit) { + return this.other.getTimeDurationHelper(name, vStr, unit); + } + + @Override + public long[] getTimeDurations(String name, TimeUnit unit) { + return this.other.getTimeDurations(name, unit); + } + + @Override + public double getStorageSize(String name, String defaultValue, + StorageUnit targetUnit) { + return this.other.getStorageSize(name, defaultValue, targetUnit); + } + + @Override + public double getStorageSize(String name, double defaultValue, + StorageUnit targetUnit) { + return this.other.getStorageSize(name, defaultValue, targetUnit); + } + + @Override + public Pattern getPattern(String name, Pattern defaultValue) { + return this.other.getPattern(name, defaultValue); + } + + @Override + public synchronized String[] getPropertySources(String name) { + return this.other.getPropertySources(name); + } + + @Override + public IntegerRanges getRange(String name, String defaultValue) { + return this.other.getRange(name, defaultValue); + } + + @Override + public Collection getStringCollection(String name) { + return this.other.getStringCollection(name); + } + + @Override + public String[] getStrings(String name) { + return this.other.getStrings(name); + } + + @Override + public String[] getStrings(String name, String... defaultValue) { + return this.other.getStrings(name, defaultValue); + } + + @Override + public Collection getTrimmedStringCollection(String name) { + return this.other.getTrimmedStringCollection(name); + } + + @Override + public String[] getTrimmedStrings(String name) { + return this.other.getTrimmedStrings(name); + } + + @Override + public String[] getTrimmedStrings(String name, String... defaultValue) { + return this.other.getTrimmedStrings(name, defaultValue); + } + + @Override + public char[] getPassword(String name) throws IOException { + return this.other.getPassword(name); + } + + @Override + public char[] getPasswordFromCredentialProviders(String name) + throws IOException { + return this.other.getPasswordFromCredentialProviders(name); + } + + @Override + public InetSocketAddress getSocketAddr(String hostProperty, + String addressProperty, String defaultAddressValue, int defaultPort) { + return this.other.getSocketAddr(hostProperty, addressProperty, + defaultAddressValue, defaultPort); + } + + @Override + public InetSocketAddress getSocketAddr(String name, String defaultAddress, + int defaultPort) { + return this.other.getSocketAddr(name, defaultAddress, defaultPort); + } + + @Override + public Class getClassByName(String name) throws ClassNotFoundException { + return this.other.getClassByName(name); + } + + @Override + public Class getClassByNameOrNull(String name) { + return this.other.getClassByNameOrNull(name); + } + + @Override + public Class[] getClasses(String name, Class... defaultValue) { + return this.other.getClasses(name, defaultValue); + } + + @Override + public Class getClass(String name, Class defaultValue) { + return this.other.getClass(name, defaultValue); + } + + @Override + public Class getClass(String name, + Class defaultValue, + Class xface) { + return this.other.getClass(name, defaultValue, xface); + } + + @Override + public List getInstances(String name, Class xface) { + return this.other.getInstances(name, xface); + } + + @Override + public Path getLocalPath(String dirsProp, String path) throws IOException { + return this.other.getLocalPath(dirsProp, path); + } + + @Override + public File getFile(String dirsProp, String path) throws IOException { + return this.other.getFile(dirsProp, path); + } + + @Override + public URL getResource(String name) { + return this.other.getResource(name); + } + + @Override + public InputStream getConfResourceAsInputStream(String name) { + return this.other.getConfResourceAsInputStream(name); + } + + @Override + public Reader getConfResourceAsReader(String name) { + return this.other.getConfResourceAsReader(name); + } + + @Override + public Set getFinalParameters() { + return this.other.getFinalParameters(); + } + + @Override + public int size() { + return this.other.size(); + } + + @Override + public Map getPropsWithPrefix(String confPrefix) { + return this.other.getPropsWithPrefix(confPrefix); + } + + @Override + public ClassLoader getClassLoader() { + return this.other.getClassLoader(); + } + + @Override + public String toString() { + return this.other.toString(); + } + + @Override + public Map getValByRegex(String regex) { + return this.other.getValByRegex(regex); + } + + @Override + public Properties getAllPropertiesByTag(String tag) { + return this.other.getAllPropertiesByTag(tag); + } + + @Override + public Properties getAllPropertiesByTags(List tagList) { + return this.other.getAllPropertiesByTags(tagList); + } + + @Override + public boolean isPropertyTag(String tagStr) { + return this.other.isPropertyTag(tagStr); + } + + // all the mutable methods below should throw an error + @Override + public void readFields(DataInput in) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void setDeprecatedProperties() { + throw new UnsupportedOperationException(); + } + + @Override + public void setRestrictSystemProperties(boolean val) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(String name, boolean restrictedParser) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(URL url) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(URL url, boolean restrictedParser) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(Path file) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(Path file, boolean restrictedParser) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(InputStream in) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(InputStream in, boolean restrictedParser) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(InputStream in, String name) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(InputStream in, String name, + boolean restrictedParser) { + throw new UnsupportedOperationException(); + } + + @Override + public void addResource(Configuration conf) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void reloadConfiguration() { + throw new UnsupportedOperationException(); + } + + @Override + public void setAllowNullValueProperties(boolean val) { + throw new UnsupportedOperationException(); + } + + @Override + public void setRestrictSystemProps(boolean val) { + throw new UnsupportedOperationException(); + } + + @Override + public void set(String name, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public void set(String name, String value, String source) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void unset(String name) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void setIfUnset(String name, String value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setInt(String name, int value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setLong(String name, long value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setFloat(String name, float value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setDouble(String name, double value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setBoolean(String name, boolean value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setBooleanIfUnset(String name, boolean value) { + throw new UnsupportedOperationException(); + } + + @Override + public > void setEnum(String name, T value) { + throw new UnsupportedOperationException(); + } + + @Override + public void setTimeDuration(String name, long value, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void setStorageSize(String name, double value, StorageUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void setPattern(String name, Pattern pattern) { + throw new UnsupportedOperationException(); + } + + @Override + public void setStrings(String name, String... values) { + throw new UnsupportedOperationException(); + } + + @Override + public void setSocketAddr(String name, InetSocketAddress addr) { + throw new UnsupportedOperationException(); + } + + @Override + public InetSocketAddress updateConnectAddr( + String hostProperty, + String addressProperty, + String defaultAddressValue, + InetSocketAddress addr) { + throw new UnsupportedOperationException(); + } + + @Override + public InetSocketAddress updateConnectAddr(String name, + InetSocketAddress addr) { + throw new UnsupportedOperationException(); + } + + @Override + public void setClass(String name, Class theClass, Class xface) { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() { + throw new UnsupportedOperationException(); + } + + @Override + public void writeXml(OutputStream out) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeXml(Writer out) { + throw new UnsupportedOperationException(); + } + + @Override + public void writeXml(String propertyName, Writer out) + throws IllegalArgumentException { + throw new UnsupportedOperationException(); + } + + @Override + public void setClassLoader(ClassLoader classLoader) { + throw new UnsupportedOperationException(); + } + + @Override + public synchronized void setQuietMode(boolean quietmode) { + throw new UnsupportedOperationException(); + } + + @Override + public void write(DataOutput out) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestUnmodifiableConfiguration.java b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestUnmodifiableConfiguration.java new file mode 100644 index 0000000000000..9ff8a422ff96b --- /dev/null +++ b/hadoop-common-project/hadoop-common/src/test/java/org/apache/hadoop/conf/TestUnmodifiableConfiguration.java @@ -0,0 +1,187 @@ +/* + * 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.conf; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.apache.hadoop.test.LambdaTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Test the UnmodifiableConfiguration. + */ +public class TestUnmodifiableConfiguration { + + private static Logger log = LoggerFactory + .getLogger(TestUnmodifiableConfiguration.class); + private static Configuration hadoopConf = new Configuration(); + + private enum TEST { + BLAH + } + + @Test + public void testGet() { + Configuration conf = Configuration.unmodifiableConfiguration(hadoopConf); + String prop = conf.get("blah", "blah"); + assertEquals("blah", prop); + } + + @Test + public void testIter() throws Exception{ + Configuration conf = Configuration.unmodifiableConfiguration(hadoopConf); + LambdaTestUtils.intercept(UnsupportedOperationException.class, + () -> conf.iterator().remove()); + } + + @Test + /** + * Test that all mutable methods throw exceptions. Only acceptable methods: + * + *
+   * get*
+   * onlyKeyExists()
+   * is*
+   * iterator()
+   * size()
+   * toString()
+   * 
+ */ + public void testAllMethods() throws Exception { + UnmodifiableConfiguration conf = new UnmodifiableConfiguration(hadoopConf); + Method[] allMethods = UnmodifiableConfiguration.class.getDeclaredMethods(); + ByteArrayOutputStream baos = new ByteArrayOutputStream(0); + ByteArrayInputStream bais = new ByteArrayInputStream("".getBytes()); + + for (Method method : allMethods) { + String name = method.getName(); + if (!name.startsWith("get") && !name.startsWith("i") && + !name.equals("onlyKeyExists") && !name.equals("toString") && + !name.equals("size")) { + log.info("Testing method {}", method.getName()); + + // get the proper params for the method + List params = new ArrayList<>(); + for (Class param : method.getParameterTypes()) { + log.debug("Method has paramType {}", param.getName()); + if (param.isPrimitive()) { + if (param.getName().equals("boolean")) { + params.add(Boolean.TRUE); + } else { + params.add(0); + } + continue; + } + if (param.isArray()) { + params.add(new String[] {}); + continue; + } + switch (param.getName()) { + case "java.lang.String": + case "org.apache.hadoop.fs.Path": + case "java.net.URL": + params.add(param.getDeclaredConstructor(String.class) + .newInstance("http://")); + break; + case "java.io.DataOutput": + case "java.io.OutputStream": + params.add(new DataOutputStream(baos)); + break; + case "java.io.DataInput": + case "java.io.InputStream": + params.add(new DataInputStream(bais)); + break; + case "java.util.regex.Pattern": + params.add(java.util.regex.Pattern.compile("b")); + break; + case "java.lang.Enum": + params.add(TEST.BLAH); + break; + case "java.util.concurrent.TimeUnit": + params.add(TimeUnit.DAYS); + break; + case "org.apache.hadoop.conf.StorageUnit": + params.add(StorageUnit.GB); + break; + case "java.net.InetSocketAddress": + params.add(new java.net.InetSocketAddress(1)); + break; + case "java.lang.Class": + params.add(this.getClass()); + break; + case "java.io.Writer": + params.add(new StringWriter(0)); + break; + case "java.lang.ClassLoader": + params.add(ClassLoader.getSystemClassLoader()); + break; + default: + params.add(param.newInstance()); + break; + } + } + try { + log.info("invoke {} with {} params: {}", name, params.size(), + params); + callMethod(conf, method, params); + fail("Method did not throw UnsupportedOperationException: " + + method.getName()); + } catch (InvocationTargetException e) { + if (!(e.getCause() instanceof UnsupportedOperationException)) { + throw e; + } + } + } + } + } + + private void callMethod(Configuration conf, Method method, List params) + throws Exception { + switch (params.size()) { + case 1: + method.invoke(conf, params.get(0)); + break; + case 2: + method.invoke(conf, params.get(0), params.get(1)); + break; + case 3: + method.invoke(conf, params.get(0), params.get(1), params.get(2)); + break; + case 4: + method.invoke(conf, params.get(0), params.get(1), params.get(2), + params.get(3)); + break; + default: + method.invoke(conf); + } + } +}