Configuring Java components

Any Java component might require some sort of configuration, be it simple strings or integers, or more complex structures. Because of all the boilerplate code that commonly goes into classes to hold such configuration, we often see this degenerate into a collection of key-value string pairs (e.g. javax.servlet.FilterConfig). To avoid this Vespa can deliver custom, type-safe configuration to all JDisc components. This configuration should be used for all state which is assumed to stay constant for the lifetime of the component instance. Read the document on developing applications to get started.

In a nutshell:

  • The developer provides a config schema text file - .def
  • Use the Vespa maven plugin to generate a config class from .def
  • Inject config objects in your code
The application code is interfacing with config through generated code - code and config is always in sync. (For details about how configuration works in Vespa, see Cloud Config System.)

Note: the bundle plugin simplifies these steps - refer to the basic-search sample application for an example.

Config definition - .def

Write a config definition file and place it in the application's src/main/resources/configdefinitions/ directory. E.g. src/main/resources/configdefinitions/my-component.def:

package=com.mydomain.mypackage

myCode     int     default=42
myMessage  string  default=""

Generate config class

Generate Java config classes:

$ mvn generate-resources
This generates the config classes in target/generated-sources/vespa-configgen-plugin/.

In the above example, the config definition file was named my-component.def, and its package declaration is com.mydomain.myypackage. Hence, the full name of the generated java class is com.mydomain.mypackage.MyComponentConfig

It is a good idea to generate the config classes first, then resolve dependencies and compile in the IDE.

Use config in code

The generated config class is now available for your component through constructor injection, which means that the component can declare the generated class as one of its constructor arguments:

package com.mydomain.mypackage;

public class MyComponent {

    private final int code;
    private final String message;

    @Inject
    public MyComponent(MyComponentConfig config) {
        code = config.myCode();
        message = config.myMessage();
    }
}
The JDisc Container will take care of creating and injecting the config instance. To override the default values of the config, specify values in src/main/application/services.xml. Following the above example:
<container version="1.0">
    <component id="com.mydomain.mypackage.MyComponent">
        <config name="com.mydomain.mypackage.my-component">
            <myCode>132</myCode>
            <myMessage>Hello, World!</myMessage>
        </config>
    </component>
</container>
and the deployed instance of MyComponent will be constructed using a corresponding instance of MyComponentConfig.

Unit Testing a Configurable Component

The generated config class provides a builder API that makes it easy to create config objects for unit testing. Below is an example that sets up a unit test for the MyComponent class from the previous example:

import static com.mydomain.mypackage.MyComponentConfig.*;

public class MyComponentTest {

    @Test
    public void requireThatMyComponentGetsConfig() {
        MyComponentConfig.Builder builder = new MyComponentConfig.Builder();
        builder.myCode(668)
               .myMessage("Neighbour of the beast");
        MyComponentConfig config = new MyComponentConfig(builder);
        MyComponent component = new MyComponent(config);
        …
   }
}
The config class used here is very simple - see a separate example of building a complex configuration object.

Adding Files to the Component Configuration

This section describes what to do if your component needs larger configuration objects that are stored in files, e.g. automata or large tables. Before you proceed, you should take a look at how to create provider components. Instead of integrating large objects into e.g. a searcher or processor, you might be better off by splitting the resource-demanding part of your component's configuration into a separate provider component. The procedure described below can be applied to any component type.

Files can be transferred using file distribution by adding the files to the application package, and adding corresponding path parameters to the .def file. See the config file reference for details about the path config type.

Assume this config definition, named my-component.def:

package=com.mydomain.mypackage

myFile path
The file must reside in the application package, and the path (relative to the application package root) must be given in the component's configuration in services.xml:
<container version="1.0">
    <component id="com.mydomain.mypackage.MyComponent">
        <config name="com.mydomain.mypackage.my-component">
            <myFile>my-files/my-file.txt</myFile>
        </config>
    </component>
</container>
An example component that uses the file:
package com.mydomain.mypackage;
import java.io.File;

public class MyComponent {
    private final File file;

    public MyComponent(MyComponentConfig config) {
        file = config.myFile().toFile();
    }
}
The myFile() getter returns a java.nio.Path object. The container framework guarantees that the file is fully transferred and present at the given location before the component constructor is invoked. Hence, the component can access the file contents right away.