Skip to content

Options

Options are low level configurations. In contrast to dropwizard configuration (file), which is user specific, options are set during development and represent developer decisions. Often, options allow to change opinionated default behaviours.

Options are declared with enums. Enums used to naturally group options (also cause pretty reporting). Enums must implement Option interface (this makes enum declaration more verbose (because it is impossible to use abstract class in enum), but provides required option info).

Guicey use options to share guice bundle configurations (configured packages to scan, search commands enabling etc) through GuiceyOptions enum (for simplicity, main guicey options usages are already implemented as shortcut methods in guice bundle). Another use is in web installers to change default behaviour though InstallersOptions enum.

Custom options may be defined for 3rd party bundle or even application. Options is a general mechanism providing configuration and access points with standard reporting (part of diagnostic reporting). It may be used as feature triggers (like guicey do), to enable debug behaviour or to specialize application state in tests (just to name a few).

Usage

Options may be set only in main GuiceBundle using .option method. This is important to let configuration parts to see the same values. For example, if guicey bundles would be allowed to change options then one bundles would see one value and other bundles - different value and, for sure, this will eventually lead to inconsistent behaviour.

Option could not be set to null. Option could be null only if it's default value is null and custom value not set. Custom option value is checked for compatibility with option type (from option definition) and error thrown if does not match. Of course, type checking is limited to top class and generics are ignored (so List<String> could not be specified and so can't be checked), but it's a compromise between complexity and easy of use (the same as Enum & Option pair).

Options could be accessed by:

Guicey tracks options definition and usage and report all used options as part of diagnostic reporting. Pay attention that defined (value set) but not used (not consumed) options are marked as NOT_USED to indicate possibly redundant options.

Actual application may use options in different time and so option may be defined as NOT_USE even if its actually "not yet" used. Try to consume options closer to actual usage to let user be aware if option not used with current configuration. For example, GuiceyOptions.BindConfigurationInterfaces will not appear in report at all if no custom configuration class used.

Custom options

Options must be enum and implement Option interface, like this:

enum MyOptions implements Option {

    DoExtraWork(Boolean, true),
    EnableDebug(Boolean, false),
    InternalConfig(String[], new String[]{"one", "two", "three"});

    private Class type
    private Object value

    // generic used only to check type - value correctness
    <T> SampleOptions(Class<T> type, T value) {
        this.type = type
        this.value = value
    }

    @Override
    public Class getType() {
        return type
    }

    @Override
    public Object getDefaultValue() {
        return value
    }
}

Each enum value declares option with exact type and default value. Option type is not limited, but implement proper toString for custom object used as option value. This will require for pretty reporting, as simple toString used for option value (except collections and arrays are rendered as []).

Now you can use option, for example, in bean:

import static MyOptions.DoExtraWork;

public class MyBean {
    @Inject Options options;

    pulic void someMethod() {
        ... 
        if (options.get(DoExtraWork)) {
            // extra work impl
        }
    }
}

To provide custom option value:

    GuiceBundle.builder()
        .option(DoExtraWork, false)
        ...

Options lookup

Guicey provides simple mapping utility to map properties to system properties, environment variables or simply bind from string (obtained manually somewhere).

GuiceBundle.builder()
    ...
    .options(new OptionsMapper()
                    .prop("myprop", Myoptions.SomeOption)
                    .env("STAGE", GuiceyOptions.InjectorStage)
                    .string(Myoptions.SomeOtherOption, "property value")
                    .map()) 
    .build()                

Here:

  • Myoptions.SomeOption could be changed with "myprop" system property (-Dmyprop=something)
  • GuiceyOptions.InjectorStage could be changed with environment variable "STAGE"
  • Myoptions.SomeOtherOption set from string (string could be obtained somewhere else manually)

Important

Missed mappings are ignored: e.g. if system property or environment variable is not defined - option will remain with default value (null will not be set!)

Supported conversions

Each option declares required option type

Mapper could automatically convert string to:

  • String
  • Boolean
  • Integer
  • Double
  • Short
  • Byte
  • Enum constant:
    • If option type is exact enum then value must be constant name
    • If option type is generic Enum then value must be 'fullEnumClass.constantName'
  • Array or any type (from above): values must be separated by comma ("one, two, three")
  • EnumSet: value must be comma separated list with fully qualified enum constants ('fullEnumClass.constantName')

Tip

You can use sting conversion directly somewhere else, if required: StringConverter.convert(TargetType, stringValue)

Exception is thrown when type is not supported for conversion. In this case use manual converter:

new OptionsMapper()
            .prop("myprop", Myoptions.SomeOption, val -> convertVal(val))
            .map()

Converter is actually any java.util.Function (here, lambda with method call (::convertVal)).

System properties

As shown before, you can bind single system property to option. But you can also allow to set any option with system property:

new OptionsMapper().props().map()

It will bind all properties in format: option.enumClasName.enumValue. For example, -Doption.ru.vyarus.dropwizard.guice.GuiceyOptions.UseHkBridge=true

Different prefix could be used: .props("myprefix")

Warning

All properties with matched prefix must be mappable to option (target enum exists), otherwise error will be thrown.

If any property requires custom value conversion then bind it before with converter and it will be ignored during mass mapping by prefix:

new OptionsMapper()
        .prop("option.ru.vyarus.dropwizard.guice.GuiceyOptions.UseHkBridge", 
                GuiceyOptions.UseHkBridge, val - > convert(val))
        .props()
        .map()

Debug

You can enable mapped options print with .printMappings():

new OptionsMapper()
            .prop("myprop", Myoptions.SomeOption, val -> convertVal(val))
            .printMappings()
            .map()

When enabled, all mapped options will be printed to console (logger is not used because it's not yet initialized).

Example output:

    env: VAR                   Opts.OptInt = 1
    prop: foo                  Opts.OptStr = bar
                               Opts.OptBool = true

for mapper:

new OptionsMapper()
        .printMappings()
        .env("VAR", Opts.OptInt)
        .env("VAR2", Opts.OptDbl)
        .prop("foo", Opts.OptStr)
        .prop("foo2", Opts.OptShort)
        .string(Opts.OptBool, "true")
        .map()

Here "VAR2" env. variable and "foo2" system property wasn't declared and so not mapped.

Custom lookup

You can directly specify map of options (.options(Map<Enum, Object>)) or write your own lookup mechanism:

    GuiceBundle.builder()
        .options(new MyOptionsLookup().getOptions())
        ...

.options() method contract simplified for just Enum, excluding Option for simpler usage, but still only option enums must be provided