You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2021/12/26 19:43:49 UTC

[juneau] branch master updated: Config API modernization.

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 94239f1  Config API modernization.
94239f1 is described below

commit 94239f137ba059d64cb8e72f0500905c4a8b3973
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Sun Dec 26 14:43:30 2021 -0500

    Config API modernization.
---
 .../main/java/org/apache/juneau/config/Config.java | 214 +------------
 .../java/org/apache/juneau/config/Section.java     | 353 +++++++++++++++++++++
 .../microservice/resources/ConfigResource.java     |  28 +-
 .../org/apache/juneau/rest/test/ConfigTest.java    |   2 +-
 .../apache/juneau/config/ConfigInterfaceTest.java  |   2 +-
 .../java/org/apache/juneau/config/ConfigTest.java  |  65 ++--
 6 files changed, 409 insertions(+), 255 deletions(-)

diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
index 33ce5f4..af9175e 100644
--- a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Config.java
@@ -19,7 +19,6 @@ import static org.apache.juneau.internal.SystemEnv.*;
 import static org.apache.juneau.internal.ThrowableUtils.*;
 import static org.apache.juneau.internal.IOUtils.*;
 
-import java.beans.*;
 import java.io.*;
 import java.lang.annotation.*;
 import java.lang.reflect.*;
@@ -671,7 +670,7 @@ public final class Config extends Context implements ConfigEventListener {
 	 * @return This object.
 	 */
 	public Config setSystemProperties() {
-		for (String section : getSections()) {
+		for (String section : getSectionNames()) {
 			for (String key : getKeys(section)) {
 				String k = (section.isEmpty() ? key : section + '/' + key);
 				System.setProperty(k, getRaw(k));
@@ -877,13 +876,26 @@ public final class Config extends Context implements ConfigEventListener {
 	 * If entry does not exist, returns an empty {@link Entry} object.
 	 *
 	 * @param key The key.
-	 * @return The value, never <jk>null</jk>.
+	 * @return The entry bean, never <jk>null</jk>.
 	 */
 	public Entry get(String key) {
 		return new Entry(this, configMap, sname(key), skey(key));
 	}
 
 	/**
+	 * Gets the section with the specified name.
+	 *
+	 * <p>
+	 * If section does not exist, returns an empty {@link Section} object.
+	 *
+	 * @param name The section name.  <jk>null</jk> and blank refer to the default section.
+	 * @return The section bean, never <jk>null</jk>.
+	 */
+	public Section getSection(String name) {
+		return new Section(this, configMap, emptyIfNull(name));
+	}
+
+	/**
 	 * Returns the keys of the entries in the specified section.
 	 *
 	 * @param section
@@ -936,209 +948,15 @@ public final class Config extends Context implements ConfigEventListener {
 	}
 
 	/**
-	 * Shortcut for calling <code>getSectionAsBean(sectionName, c, <jk>false</jk>)</code>.
-	 *
-	 * @param section
-	 * 	The section name to write from.
-	 * 	<br>If empty, refers to the default section.
-	 * 	<br>Must not be <jk>null</jk>.
-	 * @param c The bean class to create.
-	 * @return A new bean instance.
-	 * @throws ParseException Malformed input encountered.
-	 */
-	public <T> T getSectionAsBean(String section, Class<T> c) throws ParseException {
-		return getSectionAsBean(section, c, false);
-	}
-
-	/**
-	 * Converts this config file section to the specified bean instance.
-	 *
-	 * <p>
-	 * Key/value pairs in the config file section get copied as bean property values to the specified bean class.
-	 *
-	 * <h5 class='figure'>Example config file</h5>
-	 * <p class='bcode w800'>
-	 * 	<cs>[MyAddress]</cs>
-	 * 	<ck>name</ck> = <cv>John Smith</cv>
-	 * 	<ck>street</ck> = <cv>123 Main Street</cv>
-	 * 	<ck>city</ck> = <cv>Anywhere</cv>
-	 * 	<ck>state</ck> = <cv>NY</cv>
-	 * 	<ck>zip</ck> = <cv>12345</cv>
-	 * </p>
-	 *
-	 * <h5 class='figure'>Example bean</h5>
-	 * <p class='bcode w800'>
-	 * 	<jk>public class</jk> Address {
-	 * 		public String name, street, city;
-	 * 		public StateEnum state;
-	 * 		public int zip;
-	 * 	}
-	 * </p>
-	 *
-	 * <h5 class='figure'>Example usage</h5>
-	 * <p class='bcode w800'>
-	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
-	 * 	Address myAddress = cf.getSectionAsBean(<js>"MySection"</js>, Address.<jk>class</jk>);
-	 * </p>
-	 *
-	 * @param section
-	 * 	The section name to write from.
-	 * 	<br>If empty, refers to the default section.
-	 * 	<br>Must not be <jk>null</jk>.
-	 * @param c The bean class to create.
-	 * @param ignoreUnknownProperties
-	 * 	If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property
-	 * 	name.
-	 * @return A new bean instance, or <jk>null</jk> if the section doesn't exist.
-	 * @throws ParseException Unknown property was encountered in section.
-	 */
-	public <T> T getSectionAsBean(String section, Class<T> c, boolean ignoreUnknownProperties) throws ParseException {
-		assertArgNotNull("c", c);
-		section = section(section);
-
-		if (! configMap.hasSection(section))
-			return null;
-
-		Set<String> keys = configMap.getKeys(section);
-
-		BeanMap<T> bm = beanSession.newBeanMap(c);
-		for (String k : keys) {
-			BeanPropertyMeta bpm = bm.getPropertyMeta(k);
-			if (bpm == null) {
-				if (! ignoreUnknownProperties)
-					throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, section);
-			} else {
-				bm.put(k, get(section + '/' + k).as(bpm.getClassMeta().getInnerClass()).orElse(null));
-			}
-		}
-
-		return bm.getBean();
-	}
-
-	/**
-	 * Returns a section of this config copied into an {@link OMap}.
-	 *
-	 * @param section
-	 * 	The section name to write from.
-	 * 	<br>If empty, refers to the default section.
-	 * 	<br>Must not be <jk>null</jk>.
-	 * @return A new {@link OMap}, or <jk>null</jk> if the section doesn't exist.
-	 * @throws ParseException Malformed input encountered.
-	 */
-	public OMap getSectionAsMap(String section) throws ParseException {
-		section = section(section);
-
-		if (! configMap.hasSection(section))
-			return null;
-
-		Set<String> keys = configMap.getKeys(section);
-
-		OMap om = new OMap();
-		for (String k : keys)
-			om.put(k, get(section + '/' + k).as(Object.class).orElse(null));
-		return om;
-	}
-
-	/**
 	 * Returns the section names defined in this config.
 	 *
 	 * @return The section names defined in this config.
 	 */
-	public Set<String> getSections() {
+	public Set<String> getSectionNames() {
 		return Collections.unmodifiableSet(configMap.getSections());
 	}
 
 	/**
-	 * Wraps a config file section inside a Java interface so that values in the section can be read and
-	 * write using getters and setters.
-	 *
-	 * <h5 class='figure'>Example config file</h5>
-	 * <p class='bcode w800'>
-	 * 	<cs>[MySection]</cs>
-	 * 	<ck>string</ck> = <cv>foo</cv>
-	 * 	<ck>int</ck> = <cv>123</cv>
-	 * 	<ck>enum</ck> = <cv>ONE</cv>
-	 * 	<ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv>
-	 * 	<ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv>
-	 * 	<ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv>
-	 * </p>
-	 *
-	 * <h5 class='figure'>Example interface</h5>
-	 * <p class='bcode w800'>
-	 * 	<jk>public interface</jk> MyConfigInterface {
-	 *
-	 * 		String getString();
-	 * 		<jk>void</jk> setString(String x);
-	 *
-	 * 		<jk>int</jk> getInt();
-	 * 		<jk>void</jk> setInt(<jk>int</jk> x);
-	 *
-	 * 		MyEnum getEnum();
-	 * 		<jk>void</jk> setEnum(MyEnum x);
-	 *
-	 * 		MyBean getBean();
-	 * 		<jk>void</jk> setBean(MyBean x);
-	 *
-	 * 		<jk>int</jk>[][][] getInt3dArray();
-	 * 		<jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x);
-	 *
-	 * 		Map&lt;String,List&lt;MyBean[][][]&gt;&gt; getBean1d3dListMap();
-	 * 		<jk>void</jk> setBean1d3dListMap(Map&lt;String,List&lt;MyBean[][][]&gt;&gt; x);
-	 * 	}
-	 * </p>
-	 *
-	 * <h5 class='figure'>Example usage</h5>
-	 * <p class='bcode w800'>
-	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
-	 *
-	 * 	MyConfigInterface ci = cf.getSectionAsInterface(<js>"MySection"</js>, MyConfigInterface.<jk>class</jk>);
-	 *
-	 * 	<jk>int</jk> myInt = ci.getInt();
-	 *
-	 * 	ci.setBean(<jk>new</jk> MyBean());
-	 *
-	 * 	cf.save();
-	 * </p>
-	 *
-	 * <ul class='notes'>
-	 * 	<li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown.
-	 * </ul>
-	 *
-	 * @param section
-	 * 	The section name to write from.
-	 * 	<br>If empty, refers to the default section.
-	 * 	<br>Must not be <jk>null</jk>.
-	 * @param c The proxy interface class.
-	 * @return The proxy interface.
-	 */
-	@SuppressWarnings("unchecked")
-	public <T> T getSectionAsInterface(String section, final Class<T> c) {
-		assertArgNotNull("c", c);
-		final String section2 = section(section);
-
-		if (! c.isInterface())
-			throw illegalArgumentException("Class ''{0}'' passed to getSectionAsInterface() is not an interface.", c.getName());
-
-		InvocationHandler h = new InvocationHandler() {
-
-			@Override
-			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-				BeanInfo bi = Introspector.getBeanInfo(c, null);
-				for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
-					Method rm = pd.getReadMethod(), wm = pd.getWriteMethod();
-					if (method.equals(rm))
-						return Config.this.get(section2 + '/' + pd.getName()).to(rm.getGenericReturnType());
-					if (method.equals(wm))
-						return Config.this.set(section2 + '/' + pd.getName(), args[0]);
-				}
-				throw unsupportedOperationException("Unsupported interface method.  method=''{0}''", method);
-			}
-		};
-
-		return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h);
-	}
-
-	/**
 	 * Returns <jk>true</jk> if this section contains the specified key and the key has a non-blank value.
 	 *
 	 * @param key The key.
diff --git a/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Section.java b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Section.java
new file mode 100644
index 0000000..cfa98e0
--- /dev/null
+++ b/juneau-core/juneau-config/src/main/java/org/apache/juneau/config/Section.java
@@ -0,0 +1,353 @@
+// ***************************************************************************************************************************
+// * 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.juneau.config;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.apache.juneau.internal.ThrowableUtils.*;
+import static java.util.Optional.*;
+
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.config.internal.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * A single section in a config file.
+ */
+public class Section {
+
+	final Config config;
+	private final ConfigMap configMap;
+	final String name;
+
+	/**
+	 * Constructor.
+	 *
+	 * @param config The config that this entry belongs to.
+	 * @param configMap The map that this belongs to.
+	 * @param name The section name of this entry.
+	 */
+	protected Section(Config config, ConfigMap configMap, String name) {
+		this.config = config;
+		this.configMap = configMap;
+		this.name = name;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if this section exists.
+	 *
+	 * @return <jk>true</jk> if this section exists.
+	 */
+	public boolean isPresent() {
+		return configMap.hasSection(name);
+	}
+
+	/**
+	 * Shortcut for calling <code>toBean(sectionName, c, <jk>false</jk>)</code>.
+	 *
+	 * @param c The bean class to create.
+	 * @return A new bean instance, or <jk>null</jk> if this section does not exist.
+	 * @throws ParseException Malformed input encountered.
+	 */
+	public <T> T toBean(Class<T> c) throws ParseException {
+		return toBean(c, false);
+	}
+
+	/**
+	 * Shortcut for calling <code>asBean(sectionName, c, <jk>false</jk>)</code>.
+	 *
+	 * @param c The bean class to create.
+	 * @return A new bean instance, or {@link Optional#empty()} if this section does not exist.
+	 * @throws ParseException Malformed input encountered.
+	 */
+	public <T> Optional<T> asBean(Class<T> c) throws ParseException {
+		return ofNullable(toBean(c));
+	}
+
+	/**
+	 * Converts this config file section to the specified bean instance.
+	 *
+	 * <p>
+	 * Key/value pairs in the config file section get copied as bean property values to the specified bean class.
+	 *
+	 * <h5 class='figure'>Example config file</h5>
+	 * <p class='bcode w800'>
+	 * 	<cs>[MyAddress]</cs>
+	 * 	<ck>name</ck> = <cv>John Smith</cv>
+	 * 	<ck>street</ck> = <cv>123 Main Street</cv>
+	 * 	<ck>city</ck> = <cv>Anywhere</cv>
+	 * 	<ck>state</ck> = <cv>NY</cv>
+	 * 	<ck>zip</ck> = <cv>12345</cv>
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example bean</h5>
+	 * <p class='bcode w800'>
+	 * 	<jk>public class</jk> Address {
+	 * 		public String name, street, city;
+	 * 		public StateEnum state;
+	 * 		public int zip;
+	 * 	}
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example usage</h5>
+	 * <p class='bcode w800'>
+	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
+	 * 	Address myAddress = cf.getSection(<js>"MySection"</js>).toBean(Address.<jk>class</jk>);
+	 * </p>
+	 *
+	 * @param c The bean class to create.
+	 * @param ignoreUnknownProperties
+	 * 	If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property
+	 * 	name.
+	 * @return A new bean instance, or <jk>null</jk> if this section doesn't exist.
+	 * @throws ParseException Unknown property was encountered in section.
+	 */
+	public <T> T toBean(Class<T> c, boolean ignoreUnknownProperties) throws ParseException {
+		assertArgNotNull("c", c);
+		if (! isPresent()) return null;
+
+		Set<String> keys = configMap.getKeys(name);
+
+		BeanMap<T> bm = config.beanSession.newBeanMap(c);
+		for (String k : keys) {
+			BeanPropertyMeta bpm = bm.getPropertyMeta(k);
+			if (bpm == null) {
+				if (! ignoreUnknownProperties)
+					throw new ParseException("Unknown property ''{0}'' encountered in configuration section ''{1}''.", k, name);
+			} else {
+				bm.put(k, config.get(name + '/' + k).as(bpm.getClassMeta().getInnerClass()).orElse(null));
+			}
+		}
+
+		return bm.getBean();
+	}
+
+	/**
+	 * Converts this config file section to the specified bean instance.
+	 *
+	 * <p>
+	 * Key/value pairs in the config file section get copied as bean property values to the specified bean class.
+	 *
+	 * <h5 class='figure'>Example config file</h5>
+	 * <p class='bcode w800'>
+	 * 	<cs>[MyAddress]</cs>
+	 * 	<ck>name</ck> = <cv>John Smith</cv>
+	 * 	<ck>street</ck> = <cv>123 Main Street</cv>
+	 * 	<ck>city</ck> = <cv>Anywhere</cv>
+	 * 	<ck>state</ck> = <cv>NY</cv>
+	 * 	<ck>zip</ck> = <cv>12345</cv>
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example bean</h5>
+	 * <p class='bcode w800'>
+	 * 	<jk>public class</jk> Address {
+	 * 		public String name, street, city;
+	 * 		public StateEnum state;
+	 * 		public int zip;
+	 * 	}
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example usage</h5>
+	 * <p class='bcode w800'>
+	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
+	 * 	Address myAddress = cf.getSection(<js>"MySection"</js>).asBean(Address.<jk>class</jk>).orElse(<jk>null</jk>);
+	 * </p>
+	 *
+	 * @param c The bean class to create.
+	 * @param ignoreUnknownProperties
+	 * 	If <jk>false</jk>, throws a {@link ParseException} if the section contains an entry that isn't a bean property
+	 * 	name.
+	 * @return A new bean instance, or <jk>null</jk> if this section doesn't exist.
+	 * @throws ParseException Unknown property was encountered in section.
+	 */
+	public <T> Optional<T> asBean(Class<T> c, boolean ignoreUnknownProperties) throws ParseException {
+		return ofNullable(toBean(c, ignoreUnknownProperties));
+	}
+
+	/**
+	 * Returns this section as a map.
+	 *
+	 * @return A new {@link OMap}, or <jk>null</jk> if this section doesn't exist.
+	 */
+	public OMap toMap() {
+		if (! isPresent()) return null;
+
+		Set<String> keys = configMap.getKeys(name);
+
+		OMap om = new OMap();
+		for (String k : keys)
+			om.put(k, config.get(name + '/' + k).as(Object.class).orElse(null));
+		return om;
+	}
+
+	/**
+	 * Returns this section as a map.
+	 *
+	 * @return A new {@link OMap}, or {@link Optional#empty()} if this section doesn't exist.
+	 */
+	public Optional<OMap> asMap() {
+		return ofNullable(toMap());
+	}
+
+	/**
+	 * Wraps this section inside a Java interface so that values in the section can be read and
+	 * write using getters and setters.
+	 *
+	 * <h5 class='figure'>Example config file</h5>
+	 * <p class='bcode w800'>
+	 * 	<cs>[MySection]</cs>
+	 * 	<ck>string</ck> = <cv>foo</cv>
+	 * 	<ck>int</ck> = <cv>123</cv>
+	 * 	<ck>enum</ck> = <cv>ONE</cv>
+	 * 	<ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv>
+	 * 	<ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv>
+	 * 	<ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv>
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example interface</h5>
+	 * <p class='bcode w800'>
+	 * 	<jk>public interface</jk> MyConfigInterface {
+	 *
+	 * 		String getString();
+	 * 		<jk>void</jk> setString(String x);
+	 *
+	 * 		<jk>int</jk> getInt();
+	 * 		<jk>void</jk> setInt(<jk>int</jk> x);
+	 *
+	 * 		MyEnum getEnum();
+	 * 		<jk>void</jk> setEnum(MyEnum x);
+	 *
+	 * 		MyBean getBean();
+	 * 		<jk>void</jk> setBean(MyBean x);
+	 *
+	 * 		<jk>int</jk>[][][] getInt3dArray();
+	 * 		<jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x);
+	 *
+	 * 		Map&lt;String,List&lt;MyBean[][][]&gt;&gt; getBean1d3dListMap();
+	 * 		<jk>void</jk> setBean1d3dListMap(Map&lt;String,List&lt;MyBean[][][]&gt;&gt; x);
+	 * 	}
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example usage</h5>
+	 * <p class='bcode w800'>
+	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
+	 *
+	 * 	MyConfigInterface ci = cf.get(<js>"MySection"</js>).toInterface(MyConfigInterface.<jk>class</jk>);
+	 *
+	 * 	<jk>int</jk> myInt = ci.getInt();
+	 *
+	 * 	ci.setBean(<jk>new</jk> MyBean());
+	 *
+	 * 	cf.save();
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown.
+	 * </ul>
+	 *
+	 * @param c The proxy interface class.
+	 * @return The proxy interface.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T> T toInterface(final Class<T> c) {
+		assertArgNotNull("c", c);
+
+		if (! c.isInterface())
+			throw illegalArgumentException("Class ''{0}'' passed to toInterface() is not an interface.", c.getName());
+
+		InvocationHandler h = new InvocationHandler() {
+
+			@Override
+			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+				BeanInfo bi = Introspector.getBeanInfo(c, null);
+				for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
+					Method rm = pd.getReadMethod(), wm = pd.getWriteMethod();
+					if (method.equals(rm))
+						return config.get(name + '/' + pd.getName()).to(rm.getGenericReturnType());
+					if (method.equals(wm))
+						return config.set(name + '/' + pd.getName(), args[0]);
+				}
+				throw unsupportedOperationException("Unsupported interface method.  method=''{0}''", method);
+			}
+		};
+
+		return (T)Proxy.newProxyInstance(c.getClassLoader(), new Class[] { c }, h);
+	}
+
+	/**
+	 * Wraps this section inside a Java interface so that values in the section can be read and
+	 * write using getters and setters.
+	 *
+	 * <h5 class='figure'>Example config file</h5>
+	 * <p class='bcode w800'>
+	 * 	<cs>[MySection]</cs>
+	 * 	<ck>string</ck> = <cv>foo</cv>
+	 * 	<ck>int</ck> = <cv>123</cv>
+	 * 	<ck>enum</ck> = <cv>ONE</cv>
+	 * 	<ck>bean</ck> = <cv>{foo:'bar',baz:123}</cv>
+	 * 	<ck>int3dArray</ck> = <cv>[[[123,null],null],null]</cv>
+	 * 	<ck>bean1d3dListMap</ck> = <cv>{key:[[[[{foo:'bar',baz:123}]]]]}</cv>
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example interface</h5>
+	 * <p class='bcode w800'>
+	 * 	<jk>public interface</jk> MyConfigInterface {
+	 *
+	 * 		String getString();
+	 * 		<jk>void</jk> setString(String x);
+	 *
+	 * 		<jk>int</jk> getInt();
+	 * 		<jk>void</jk> setInt(<jk>int</jk> x);
+	 *
+	 * 		MyEnum getEnum();
+	 * 		<jk>void</jk> setEnum(MyEnum x);
+	 *
+	 * 		MyBean getBean();
+	 * 		<jk>void</jk> setBean(MyBean x);
+	 *
+	 * 		<jk>int</jk>[][][] getInt3dArray();
+	 * 		<jk>void</jk> setInt3dArray(<jk>int</jk>[][][] x);
+	 *
+	 * 		Map&lt;String,List&lt;MyBean[][][]&gt;&gt; getBean1d3dListMap();
+	 * 		<jk>void</jk> setBean1d3dListMap(Map&lt;String,List&lt;MyBean[][][]&gt;&gt; x);
+	 * 	}
+	 * </p>
+	 *
+	 * <h5 class='figure'>Example usage</h5>
+	 * <p class='bcode w800'>
+	 * 	Config cf = Config.<jsm>create</jsm>().name(<js>"MyConfig.cfg"</js>).build();
+	 *
+	 * 	MyConfigInterface ci = cf.get(<js>"MySection"</js>).asInterface(MyConfigInterface.<jk>class</jk>).orElse(<jk>null</jk>);
+	 *
+	 * 	<jk>int</jk> myInt = ci.getInt();
+	 *
+	 * 	ci.setBean(<jk>new</jk> MyBean());
+	 *
+	 * 	cf.save();
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>Calls to setters when the configuration is read-only will cause {@link UnsupportedOperationException} to be thrown.
+	 * </ul>
+	 *
+	 * @param c The proxy interface class.
+	 * @return The proxy interface.
+	 */
+	public <T> Optional<T> asInterface(final Class<T> c) {
+		return ofNullable(toInterface(c));
+	}
+}
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
index 1cab30c..54da3c4 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
+++ b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
@@ -25,7 +25,6 @@ import org.apache.juneau.http.annotation.FormData;
 import org.apache.juneau.http.annotation.Path;
 import org.apache.juneau.http.annotation.Response;
 import org.apache.juneau.http.annotation.Schema;
-import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.http.response.*;
@@ -103,7 +102,7 @@ public class ConfigResource extends BasicRestServlet {
 	)
 	public OMap getConfigSection(
 			@Path("section") @Schema(d="Section name in config file.") String section
-		) throws SectionNotFound, BadConfig {
+		) throws SectionNotFound {
 
 		return getSection(section);
 	}
@@ -121,7 +120,7 @@ public class ConfigResource extends BasicRestServlet {
 	public String getConfigEntry(
 			@Path("section") @Schema(d="Section name in config file.") String section,
 			@Path("key") @Schema(d="Key name in section.") String key
-		) throws SectionNotFound, BadConfig {
+		) throws SectionNotFound {
 
 		return getSection(section).getString(key);
 	}
@@ -194,7 +193,7 @@ public class ConfigResource extends BasicRestServlet {
 			@Path("section") @Schema(d="Section name in config file.") String section,
 			@Path("key") @Schema(d="Key name in section.") String key,
 			@Body @Schema(d="New value for entry.") String value
-		) throws SectionNotFound, BadConfig {
+		) throws SectionNotFound {
 
 		getContext().getConfig().set(section + '/' + key, value);
 		return getSection(section).getString(key);
@@ -213,28 +212,11 @@ public class ConfigResource extends BasicRestServlet {
 		}
 	}
 
-	@Response @Schema(description="The configuration file contained syntax errors and could not be parsed.")
-	private class BadConfig extends InternalServerError {
-		private static final long serialVersionUID = 1L;
-
-		BadConfig(Exception e) {
-			super(e, "The configuration file contained syntax errors and could not be parsed.");
-		}
-	}
-
 	//-----------------------------------------------------------------------------------------------------------------
 	// Helper methods
 	//-----------------------------------------------------------------------------------------------------------------
 
-	private OMap getSection(String name) throws SectionNotFound, BadConfig {
-		OMap m;
-		try {
-			m = getContext().getConfig().getSectionAsMap(name);
-		} catch (ParseException e) {
-			throw new BadConfig(e);
-		}
-		if (m == null)
-			throw new SectionNotFound();
-		return m;
+	private OMap getSection(String name) throws SectionNotFound {
+		return getContext().getConfig().getSection(name).asMap().orElseThrow(SectionNotFound::new);
 	}
 }
diff --git a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/ConfigTest.java b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/ConfigTest.java
index 2eaf940..691235d 100644
--- a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/ConfigTest.java
+++ b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/ConfigTest.java
@@ -40,7 +40,7 @@ public class ConfigTest extends RestTestcase {
 
 		Config cf = Config.create().memStore().build().load(m);
 
-		assertObject(cf.getSectionAsMap("Test")).asJson().is("{int1:'1',int2:'[1,2,3]',int3:'1',int4:'1',int5:'-1',boolean1:'true',boolean2:'[true,true]',testManifestEntry:'test-value'}");
+		assertObject(cf.getSection("Test").toMap()).asJson().is("{int1:'1',int2:'[1,2,3]',int3:'1',int4:'1',int5:'-1',boolean1:'true',boolean2:'[true,true]',testManifestEntry:'test-value'}");
 
 		assertEquals("'1'", c.get(URL + "/Test%2Fint1/" + getName(String.class)).run().getBody().asString());
 		assertEquals("'[1,2,3]'", c.get(URL + "/Test%2Fint2/" + getName(String.class)).run().getBody().asString());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigInterfaceTest.java b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigInterfaceTest.java
index f7ad992..c691c69 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigInterfaceTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigInterfaceTest.java
@@ -31,7 +31,7 @@ public class ConfigInterfaceTest {
 
 	public ConfigInterfaceTest() throws Exception {
 		cf = Config.create().serializer(SimpleJsonSerializer.DEFAULT.copy().addBeanTypes().addRootType().build()).build();
-		proxy = cf.getSectionAsInterface("A", ConfigInterface.class);
+		proxy = cf.getSection("A").toInterface(ConfigInterface.class);
 	}
 
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigTest.java b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigTest.java
index f96bc98..24e5c8f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/config/ConfigTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/config/ConfigTest.java
@@ -839,24 +839,24 @@ public class ConfigTest {
 		ABean a = null;
 		BBean b = null;
 
-		a = c.getSectionAsBean("", ABean.class);
+		a = c.getSection("").toBean(ABean.class);
 		assertObject(a).asJson().is("{foo:'qux'}");
-		a = c.getSectionAsBean("", ABean.class);
+		a = c.getSection("").toBean(ABean.class);
 		assertObject(a).asJson().is("{foo:'qux'}");
-		a = c.getSectionAsBean("S", ABean.class);
+		a = c.getSection(null).toBean(ABean.class);
+		assertObject(a).asJson().is("{foo:'qux'}");
+		a = c.getSection("S").toBean(ABean.class);
 		assertObject(a).asJson().is("{foo:'baz'}");
 
-		b = c.getSectionAsBean("", BBean.class);
+		b = c.getSection("").toBean(BBean.class);
 		assertObject(b).asJson().is("{foo:'qux'}");
-		b = c.getSectionAsBean("", BBean.class);
+		b = c.getSection("").toBean(BBean.class);
 		assertObject(b).asJson().is("{foo:'qux'}");
-		b = c.getSectionAsBean("S", BBean.class);
+		b = c.getSection("S").toBean(BBean.class);
 		assertObject(b).asJson().is("{foo:'baz'}");
 
-		assertThrown(()->c.getSectionAsBean("T", ABean.class)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
-		assertThrown(()->c.getSectionAsBean("T", BBean.class)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
-		assertThrown(()->c.getSectionAsBean(null, ABean.class)).message().is("Argument 'section' cannot be null.");
-		assertThrown(()->c.getSectionAsBean(null, BBean.class)).message().is("Argument 'section' cannot be null.");
+		assertThrown(()->c.getSection("T").toBean(ABean.class)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
+		assertThrown(()->c.getSection("T").toBean(BBean.class)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
 	}
 
 	//====================================================================================================
@@ -869,13 +869,13 @@ public class ConfigTest {
 		ABean a = null;
 		BBean b = null;
 
-		a = c.getSectionAsBean("T", ABean.class, true);
+		a = c.getSection("T").toBean(ABean.class, true);
 		assertObject(a).asJson().is("{foo:'qux'}");
-		b = c.getSectionAsBean("T", BBean.class, true);
+		b = c.getSection("T").toBean(BBean.class, true);
 		assertObject(b).asJson().is("{foo:'qux'}");
 
-		assertThrown(()->c.getSectionAsBean("T", ABean.class, false)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
-		assertThrown(()->c.getSectionAsBean("T", BBean.class, false)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
+		assertThrown(()->c.getSection("T").toBean(ABean.class, false)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
+		assertThrown(()->c.getSection("T").toBean(BBean.class, false)).message().is("Unknown property 'bar' encountered in configuration section 'T'.");
 	}
 
 	//====================================================================================================
@@ -885,13 +885,12 @@ public class ConfigTest {
 	public void getSectionAsMap() throws Exception {
 		Config c = init("a=1", "[S]", "b=2", "[T]");
 
-		assertObject(c.getSectionAsMap("")).asJson().is("{a:'1'}");
-		assertObject(c.getSectionAsMap("")).asJson().is("{a:'1'}");
-		assertObject(c.getSectionAsMap("S")).asJson().is("{b:'2'}");
-		assertObject(c.getSectionAsMap("T")).asJson().is("{}");
-		assertNull(c.getSectionAsMap("U"));
-
-		assertThrown(()->c.getSectionAsMap(null)).message().is("Argument 'section' cannot be null.");
+		assertObject(c.getSection("").toMap()).asJson().is("{a:'1'}");
+		assertObject(c.getSection("").toMap()).asJson().is("{a:'1'}");
+		assertObject(c.getSection(null).toMap()).asJson().is("{a:'1'}");
+		assertObject(c.getSection("S").toMap()).asJson().is("{b:'2'}");
+		assertObject(c.getSection("T").toMap()).asJson().is("{}");
+		assertNull(c.getSection("U").toMap());
 	}
 
 	//====================================================================================================
@@ -902,20 +901,22 @@ public class ConfigTest {
 		Config c = init("foo=qux", "[S]", "foo=baz", "[T]", "foo=qux", "bar=qux");
 		AInterface a = null;
 
-		a = c.getSectionAsInterface("", AInterface.class);
+		a = c.getSection("").toInterface(AInterface.class);
+		assertEquals("qux", a.getFoo());
+
+		a = c.getSection("").toInterface(AInterface.class);
 		assertEquals("qux", a.getFoo());
 
-		a = c.getSectionAsInterface("", AInterface.class);
+		a = c.getSection(null).toInterface(AInterface.class);
 		assertEquals("qux", a.getFoo());
 
-		a = c.getSectionAsInterface("S", AInterface.class);
+		a = c.getSection("S").toInterface(AInterface.class);
 		assertEquals("baz", a.getFoo());
 
-		a = c.getSectionAsInterface("T", AInterface.class);
+		a = c.getSection("T").toInterface(AInterface.class);
 		assertEquals("qux", a.getFoo());
 
-		assertThrown(()->c.getSectionAsInterface("T", ABean.class)).message().is("Class 'org.apache.juneau.config.ConfigTest$ABean' passed to getSectionAsInterface() is not an interface.");
-		assertThrown(()->c.getSectionAsInterface(null, AInterface.class)).message().is("Argument 'section' cannot be null.");
+		assertThrown(()->c.getSection("T").toInterface(ABean.class)).message().is("Class 'org.apache.juneau.config.ConfigTest$ABean' passed to toInterface() is not an interface.");
 	}
 
 	public static interface AInterface {
@@ -1424,15 +1425,15 @@ public class ConfigTest {
 	public void testGetSectionMap() throws Exception {
 		Config cf = init("[A]", "a1=1", "", "[D]", "d1=$C{A/a1}","d2=$S{X}");
 
-		assertObject(cf.getSectionAsMap("A")).asJson().is("{a1:'1'}");
-		assertNull(cf.getSectionAsMap("B"));
-		assertObject(cf.getSectionAsMap("C")).asJson().is("null");
+		assertObject(cf.getSection("A").toMap()).asJson().is("{a1:'1'}");
+		assertNull(cf.getSection("B").toMap());
+		assertObject(cf.getSection("C").toMap()).asJson().is("null");
 
-		OMap m = cf.getSectionAsMap("A");
+		OMap m = cf.getSection("A").toMap();
 		assertObject(m).asJson().is("{a1:'1'}");
 
 		System.setProperty("X", "x");
-		m = cf.getSectionAsMap("D");
+		m = cf.getSection("D").toMap();
 		assertObject(m).asJson().is("{d1:'1',d2:'x'}");
 		System.clearProperty("X");
 	}