SolarNode Settings API - SolarNetwork/solarnetwork GitHub Wiki
The SolarNode platform includes a declarative API for exposing configurable settings
on services provided by plug-ins. The SettingSpecifierProvider
interface is the entry point to this API, which allows a service to expose configurable
properties to the SolarNode runtime, which can then generate a web UI that allows a user
to edit that property.
For example, here's a screen shot of SolarNode rendering the settings for a weather data source service:
When writing a SolarNode plugin that you want to be configurable in the SolarNode UI,
implement the SettingSpecifierProvider
interface in your
service and expose that service to the OSGi runtime. This API provides a method to return
any number of SettingSpecifier
objects, each of which represents a
property to expose to the UI. Typically these are simple, mutable properties such as the
TextFieldSettingSpecifier
used for mutable string
properties. The SolarNode web UI renders HTML form elements on behalf of the plug-in, and
manages persisting changes to them via a SettingsService
provided by
the platform.
For each property exposed as a setting, your service must provide a standard JavaBean
setter method that will accept values changed by a user. For example, a
username setting would expect a setUsername()
method on the service like this:
// the SettingSpecifierProvider API provides the list of settings to expose
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
// expose a "username" setting with a default value of "admin"
results.add(new BasicTextFieldSettingSpecifier("username", "admin"));
return results;
}
// settings are updated at runtime via JavaBean setter methods
public void setUsername(String username) {
this.username = username;
}
The Settings Playpen SolarNode plugin is a demonstration of most of the available setting types. If you deploy this plugin into a SolarNode runtime you'll see a Settings playpen component that you can configure and see how the settings are implemented.
The SettingSpecifier
interface is extended by other interfaces to
provide more specific functionality.
Interface | Description |
---|---|
GroupSettingSpecifier |
Group a set of settings together. |
KeyedSettingSpecifier |
Mutable setting marker for a specific property. |
LocationLookupSettingSpecifier |
Specialized setting for SolarNet location IDs. |
MultiValueSettingSpecifier |
Setting to choose from a list of values. |
RadioGroupSettingSpecifier |
Setting to choose from a list of values, using radio buttons. |
SetupResourceSettingSpecifier |
Provides custom UI elements to SolarNode. |
SliderSettingSpecifier |
A number setting with a fixed minimum/maximum range. |
TextFieldSettingSpecifier |
A simple editable text field based property. |
TextFieldSettingSpecifier secure |
A "secure entry" style text field property. |
TitleSettingSpecifier |
A simple read-only text based property. |
ToggleSettingSpecifier |
An on/off style toggle setting. |
The purpose of this specifier is to group other specifiers into a visually distinct way
in the settings UI. The BasicGroupSettingSpecifier
class
provides a default implementation of this interface.
In the following screen shot, the SolarNode web UI is rendering a group of setting form fields with horizontal rules before/after the group:
This setting is also used for dynamic setting lists.
This is an abstract interface used to mark a setting as being mutable based on a specific
property, or key. This interface isn't typically used directly, as it is extended by
other interfaces for specific setting types. The BaseKeyedSettingSpecifier
class provides a default (abstract) implementation of this interface.
In SolarNode, setting keys generally equate to JavaBean property names, which thus
corespond to setter methods following the JavaBean standard. For example, a setting key
username
would expect a method on the service like this:
public void setUsername(String username) {
this.username = username;
}
The purpose of this specifier is to provide a way to easily find a SolarNetwork location
ID to associate with a service. For example, weather data sources need to associate a
location ID with the data they collect. The
BasicLocationLookupSettingSpecifier
class
provides a default implementation of this interface.
In the following screen shot, the SolarNode web UI is rendering a location lookup setting, which includes a Change button:
Clicking that Change button brings up a specialized search form where locations can be searched for by their names and other details:
This specifier is used for making a choice from a list of items. The
BasicMultiValueSettingSpecifier
class
provides a default implementation of this interface. The SolarNode web UI
renders this type as a drop-down menu:
The code for that settings looks like this:
private static final String[] DEFAULT_MENU = new String[] { "Option 1", "Option 2", "Option 3" };
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
BasicMultiValueSettingSpecifier menuSpec = new BasicMultiValueSettingSpecifier("menu",
DEFAULT_MENU[0]);
Map<String, String> menuValues = new LinkedHashMap<>(DEFAULT_MENU.length);
for ( String s : DEFAULT_MENU ) {
menuValues.put(s, s);
}
menuSpec.setValueTitles(menuValues);
results.add(menuSpec);
return results;
}
public void setMenu(String menu) {
this.menu = menu;
}
This specifier is used for making a choice from a list of items. The
BasicRadioGroupSettingSpecifier
class
provides a default implementation of this interface. The SolarNode web UI
renders this type as a group of radio buttons:
The code for that settings looks like this:
private static final String[] DEFAULT_RADIO = new String[] { "One", "Two", "Three" };
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
BasicRadioGroupSettingSpecifier radioSpec = new BasicRadioGroupSettingSpecifier("radio",
DEFAULT_RADIO[0]);
Map<String, String> radioValues = new LinkedHashMap<String, String>(DEFAULT_RADIO.length);
for ( String s : DEFAULT_RADIO ) {
radioValues.put(s, s);
}
radioSpec.setValueTitles(radioValues);
results.add(radioSpec);
return results;
}
public void setRadio(String radio) {
this.radio = radio;
}
This interface defines a way for a setting to provide custom UI elements to SolarNode. The
BasicSetupResourceSettingSpecifier
class provides a
default implementation of this interface. See the Custom SolarNode Settings
UI guide for more information.
This specifier defines a mutable numeric property that is constrained between minimum and
maximum values and a step size. The
BasicSliderSettingSpecifier
class provides a default
implementation of this interface. The SolarNode web UI renders this setting as a draggable
slider:
It is configured in code like this:
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
// expose a number slider between 0 and 10, default value of 5, step size 0.5
results.add(new BasicSliderSettingSpecifier("slide", 5.0, 0.0, 10.0, 0.5));
return results;
}
// runtime setter method to accept changes
public void setSlide(Double slide) {
this.slide = slide;
}
This specifier defines a mutable property that is rendered as an editable text field
in the SolarNode UI. The BasicTextFieldSettingSpecifier
class provides a default implementation of this interface.
In the following screen shot, all the text fields shown are exposed via
BasicTextFieldSettingSpecifier
settings:
Non-string properties can be configured via the text field specifier as well, as long
as the SolarNode runtime can convert the text field string value into the required type.
For example, the last field in the example above allows configuring an Integer
property.
In cases where SolarNode cannot automatically convert from a string into the required
type, then service can expose an additional property that accepts a string value
and the setting would expose that property so that the setter method for that property
can perform the conversion itself.
For example, a text field setting customerNumber
can work with a JavaBean setter
method
public void setCustomerNumber(Integer num) {
this.customerNumber = num;
}
by converting the text field string value into an Integer
.
This is not actually a specific interface, but rather a configurable property of the
TextFieldSettingSpecifier
. The
BasicTextFieldSettingSpecifier
has a constructor
that accepts a boolean secureTextEntry
argument. When set to true
the SolarNode web
UI renders the text field as a password text field:
The code for that setting looks like this:
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
results.add(new BasicTextFieldSettingSpecifier("password", null, true));
return results;
}
public void setPassword(String password) {
this.password = password;
}
This specifier defines an immutable text property that is rendered as non-editable text.
The BasicTitleSettingSpecifier
class provides a default
implementation of this interface. This can be used to display status information to the
SolarNode web UI. For example, here is a meter data source that exposes two title
settings with information about the meter device and the latest reading from the device:
The code for those settings looks like this:
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(2);
results.add(new BasicTitleSettingSpecifier("info", getInfoMessage(), true));
results.add(new BasicTitleSettingSpecifier("sample", getSampleMessage(sample), true));
return results;
}
This specifier defines a basic on/off type switch setting. The
BasicToggleSettingSpecifier
class provides a default
implementation of this interface.
The code for a toggle setting looks like this:
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
results.add(new BasicToggleSettingSpecifier("toggle", false));
return results;
}
public void setToggle(Boolean toggle) {
this.toggle = toggle;
}
The SolarNode settings framework supports configuring dynamic lists of settings, either
as a list of basic properties like a list of strings, or even complex objects like a list
of contact details. The GroupSettingSpecifier
defines a
dynamic
property to trigger this behavior. The
BasicGroupSettingSpecifier
class provides a constructor
that can be used to enable this mode.
For dynamic setting lists to work, the SettingSpecifierProvider
service must provide
a List
based JavaBean property to manage the dynamic list. Then, some additional methods
to dynamically adjust the number of items in the list are required. All these methods
follow a naming convention based on the name of the setting. In the following table, X
stands for a setting name:
Method | Example | Description |
---|---|---|
getX() |
getAllowedNames() |
Getter method for the list instance. Must return a List or array. |
getXCount() |
getAllowedNamesCount() |
Returns an int value of the size of the list. |
setXCount(int) |
setAllowedNamesCount(int size) |
Set the size of the list to size . Note it must be able to adjust the list size smaller or larger, and must populate default objects when expanding the list. |
Let's look at an example of a simple string list setting. SolarNode will render the list like this:
The name of this setting is listString. The SettingsUtil
class has a useful method to help construct the dynamic list group setting:
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<>(1);
// basic dynamic list of strings
Collection<String> listStrings = getListString();
BasicGroupSettingSpecifier listStringGroup = SettingsUtil.dynamicListSettingSpecifier(
"listString", listStrings, new SettingsUtil.KeyedListCallback<String>() {
@Override
public Collection<SettingSpecifier> mapListSettingKey(String value, int index,
String key) {
return Collections.singletonList(
new BasicTextFieldSettingSpecifier(key, ""));
}
});
results.add(listStringGroup);
return results;
}
// getter for the dynamic list
public List<String> getListString() {
return listString;
}
// setter for the dynamic list
public void setListString(List<String> listString) {
this.listString = listString;
}
// getter for the dynamic list size
public int getListStringCount() {
List<String> l = getListString();
return (l == null ? 0 : l.size());
}
// setter for the dynamic list size
public void setListStringCount(int count) {
if ( count < 0 ) {
count = 0;
}
List<String> l = getListString();
int lCount = (l == null ? 0 : l.size());
while ( lCount > count ) {
l.remove(l.size() - 1);
lCount--;
}
while ( lCount < count ) {
l.add("");
lCount++;
}
}