Pronto ConfigurationPronto contains classes for automatically satisfying interfaces
with configuration information. These classes reside in the
com.tomgibara.pronto.config package and
sub-packages. The highlights are:
Access to the configuration functionality of Pronto is available
from a singleton factory. This creates Config objects
from a ConfigSource (that provides the configuration
properties) and an optional class loader (that is used to create
object instances). The Config objects created by this
factory can be further customized by changing their operational policy
before use. Thus an example of creating a Config object is:
import com.tomgibara.pronto.config.ConfigFactory;
import com.tomgibara.pronto.config.Config;
import com.tomgibara.pronto.config.source.FileConfigSource;
Config config = ConfigFactory.getInstance()
.newConfig(new FileConfigSource(propsFile), null);
This will create a Config object that draws properties
from the file specified by propsFile. By default, the the
FileConfigSource will attempt to parse the properties in
the format specified by the documentation for
java.util.Properties. As with all configuration sources,
the FileConfigSource observes when the properties have
been changed and provide the config object with an
opportunity to make the newest properties available to client
code.
Now suppose that we have an interface (defined below) which serves as configuration for our application. We want to read this information in from some configuration source (a file in our case) without needing to write a pojo implementation and code to populate it.
public interface UserSettings {
enum Role {admin, manager, developer, user};
/** The user's name in the system. */
String getName();
/** A hash code of the user's password */
long getPasswordHash();
/** Whether the user credentials are currently valid.*/
boolean isValid();
/** The roles to which the user belongs. */
Role[] getRoles();
/** Settings for web access. */
Map getSettings();
/** A URL to the user's home page. */
URL getURL();
/** The user's account number */
String getAccountCode();
}
Then we can very simply satisfy this interface with properties
from propsFile with the single line of code below.
UserSettings userSettings = config.adaptSettings(null, false, UserSettings.class)
An entirely imaginary user properties file for this interface would be:
name=tom
password-hash=290563099
roles=admin,developer
valid=true
url=http://www.tomgibara.com
settings=theme: green; login=auto
This sample file demonstrates some of the details of how properties are mapped to Java methods. Note the following:
String are
supported.URLs and Files, are
automatically constructed (eg. url above).Because the configuration file is polled for updates, changes to
the file will be reflected in method calls to the interface at some
time after the file has been changed. For example, if we changed the
valid property to false,
userSettings.isValid() would, after a delay, return
false. The delay in the liveness is controlled by the
ConfigPolicy associated with the Config
object used to create the interface implementation. The default is
currently 3 seconds – the value is a trade-off between the speed
of property retrieval (which depends on how often the file is checked
for changes) and the speed at which changes are reflected in the
values returned from interfaces. A number of other operational
parameters can be changed via the configuration policy object.
Properties for multiple interfaces can be combined into a single property source by identifying arranging them into domains. Domains are dot-delimited 'namespaces' in the standard Java style.
Extending the example above with a second interface…
public interface ServerSettings {
/** The public domain from which the server is accessible. */
String getPublicDomain();
/** The public port from which the server is accessible. */
Integer getPublicPort();
}
…we can populate an instance of each interface from the following properties file…
#server settings
server.public-domain=demo.tomgibara.com
server.public-port=80
#admin user settings
admin.name=tom
admin.password-hash=290563099
admin.roles=admin,developer
admin.valid=true
admin.url=http://www.tomgibara.com
admin.settings=theme: green; login=auto
…by simply specifying the domain from which each implementation will draw its properties:
UserSettings userSettings = config.adaptSettings("admin", false, UserSettings.class);
ServerSettings serverSettings =
config.adaptSettings("server", false, ServerSettings.class
Any number of different sets of configuration information can be supported in this way and different interfaces can share the same (or overlapping) sets of properties.
It is frequently the case that, where the same (or similar) interfaces are implemented by properties from different domains, it is sensible that they share some properties. This can be achieved by inheriting properties between domains. As the following example demonstrates.
#server settings
server.public-domain=demo.tomgibara.com
server.public-port=80
#default user settings
users.settings=theme: green; login=auto
users.roles=user
#administrative user settings
users.admin.name=tom
users.admin.password-hash=290563099
users.admin.roles=admin,developer
users.admin.valid=true
users.admin.url=http://www.tomgibara.com
#user settings for anonymous user
users.anon.name=anonymous
UserSettings adminUserSettings =
config.adaptSettings("users.admin", true, UserSettings.class);
UserSettings anonUserSettings =
config.adaptSettings("users.anon", true, UserSettings.class);
anonUserSettings.getSettings(); //returns the map {green = theme, auto = login}.
adminUserSettings.getSettings(); //returns the same map as above - both inherit it.
anonUserSettings.getRoles(); //returns array {user} - inheritted.
adminUserSettings.getRoles(); //returns array {admin,developer} - not inheritted.
anonUserSettings.getName(); returns string "anonymous".
adminUserSettings.getName(); returns string "tom", neither inherit.
Properties can be drawn from a number of different
sources. Standard sources may be found in the
com.tomgibara.pronto.config.source package. New sources
can be created by implementing the simple
com.tomgibara.pronto.config.ConfigSource
interface. Sources which need to parse properties from resources (such
as files and urls) are recommended to use a
pluggable implementation of
com.tomgibara.pronto.config.source.PropertiesReader for
greater flexibility. Implementations of that interface can be supplied
to several existing source to control the parsing of properties.