<commit_before>[[cil2]]
Component Implementation - Lesson 2 - Configuration
===================================================
<NAME> <<EMAIL>>
v2.0, June 2014: Reformatted for AsciiDoc.
:toc:
:numbered:
:website: http://tigase.net/
:Date: 2010-01-06 20:22

It might be hard to tell what is the first important thing to do with your new component implementation. Different developers may have a different view on this. It seems to me however that it is always a good idea to give to your component a way to configure it and provide some runtime settings.

This guide describes how to add configuration handling to your component.  There is detailed Configuration API description available so again I am not getting deep into all details just the necessary code.

To demonstrate how to maintain the component configuration let's say we want to make configurable types of packets which are being logged by the component. There are three possible packet types: \'*message*\', \'*presence*' and \'*iq*' and we want to be able to configure logging of any combination of them. Furthermore we also want to be able to configure the text which is prepended to the logged message and optionally switch the secure logging on. (Secure logging replaces all packet CData with text: \'_CData size: NN_' to protect user privacy.)

Let's create following private variables in our component:

[source,java]
private String[] packetTypes = {"message", "presence", "iq"};
private String prependText = "My packet: ";
private boolean secureLogging = false;

As the component configuration is maintained in a form of (_key, value_) Map we have to invent keys for each of our configuration entry:

[source,java]
private static final String PACKET_TYPES_KEY = "packet-types";
private static final String PREPEND_TEXT_KEY = "log-prepend";
private static final String SECURE_LOGGING_KEY = "secure-logging";

There are two methods used to maintain the component configuration: +getDefaults(...)+ where the component provides some configuration defaults and +setProperties(...)+ which sets working configuration for the component:

[source,java,numbered]
--------------------------------------------------------------------
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
  Map<String, Object> defs = super.getDefaults(params);
  defs.put(PACKET_TYPES_KEY, packetTypes);
  defs.put(PREPEND_TEXT_KEY, prependText);
  defs.put(SECURE_LOGGING_KEY, secureLogging);
  return defs;
}
 
@Override
public void setProperties(Map<String, Object> props) {
  super.setProperties(props);
  packetTypes = (String[])props.get(PACKET_TYPES_KEY);
  prependText = (String)props.get(PREPEND_TEXT_KEY);
  secureLogging = (Boolean)props.get(SECURE_LOGGING_KEY);
}
--------------------------------------------------------------------

You do not have to implement +getDefaults(...)+ method and provide default settings for your configuration but doing so gives you a few benefits.

The first, from the developer point of view, you don't have to check in the +setProperties(...)+ whether the value is of a correct type or convert it from String to the correct type as it always be either the default or user provided. It will be of a correct type as the configuration framework takes care of the types comparing between the user provided settings and default values. So this just makes your +setProperties(...)+ code much simpler and clearer.

image:images/test-comp-config-list-smaller.png[]

Secondly this also makes the administrator live easier. As you can see on the screenshot, configuration parameters provided with default values, can be changed via configuration ad-hoc commands. So the administrator can maintain your component at run-time from his XMPP client.

Regardless you implemented the +getDefaults(...)+ method or not you can always manually add parameters to the +init.properties+ file.

The syntax in +init.properties+ file is actually very simple and is described in details in the _Admin Guide_. As it shows on the screenshot the configuration parameter name consists of: '_component name' + /  + 'property key_'. To set configuration for your component in +init.properties+ file you have to append following lines to the file:

[source,bash]
test/log-prepend="My packet: "
test/packet-types[s]=message,presence,iq
test/secure-logging[B]=true

In square brackets you provide the property type, have a look at the _Admin Guide_ documentation for more details.

And this is the complete code of the new component with modified +processPacket(...)+ method taking advantage of configuration settings:

[source,java,numbered]
---------------------------------------------------------------------
import java.util.Map;
import java.util.logging.Logger;
import tigase.server.AbstractMessageReceiver;
import tigase.server.Packet;
 
public class TestComponent extends AbstractMessageReceiver {
 
  private static final Logger log =
    Logger.getLogger(TestComponent.class.getName());
 
  private static final String PACKET_TYPES_KEY = "packet-types";
  private static final String PREPEND_TEXT_KEY = "log-prepend";
  private static final String SECURE_LOGGING_KEY = "secure-logging";
 
  private String[] packetTypes = {"message", "presence", "iq"};
  private String prependText = "My packet: ";
  private boolean secureLogging = false;
 
  @Override
  public void processPacket(Packet packet) {
    for (String pType : packetTypes) {
      if (pType == packet.getElemName()) {
        log.finest(prependText + packet.toString(secureLogging));
      }
    }
  }
 
  @Override
  public Map<String, Object> getDefaults(Map<String, Object> params) {
    Map<String, Object> defs = super.getDefaults(params);
    defs.put(PACKET_TYPES_KEY, packetTypes);
    defs.put(PREPEND_TEXT_KEY, prependText);
    defs.put(SECURE_LOGGING_KEY, secureLogging);
    return defs;
  }
 
  @Override
  public void setProperties(Map<String, Object> props) {
    super.setProperties(props);
    packetTypes = (String[])props.get(PACKET_TYPES_KEY);
    // Make sure we can compare element names by reference
    // instead of String content
    for (int i = 0; i < packetTypes.length; i++) {
      packetTypes[i] = packetTypes[i].intern();
    }
    prependText = (String)props.get(PREPEND_TEXT_KEY);
    secureLogging = (Boolean)props.get(SECURE_LOGGING_KEY);
  }<commit_msg>update component configuration development guide<commit_after>[[cil2]]
Component Implementation - Lesson 2 - Configuration
===================================================
<NAME> <<EMAIL>>
v2.0, June 2014: Reformatted for AsciiDoc.
:toc:
:numbered:
:website: http://tigase.net/
:Date: 2010-01-06 20:22

It might be hard to tell what is the first important thing to do with your new component implementation. Different developers may have a different view on this. It seems to me however that it is always a good idea to give to your component a way to configure it and provide some runtime settings.

This guide describes how to add configuration handling to your component.  There is detailed Configuration API description available so again I am not getting deep into all details just the necessary code.

To demonstrate how to maintain the component configuration let's say we want to make configurable types of packets which are being logged by the component. There are three possible packet types: \'*message*\', \'*presence*' and \'*iq*' and we want to be able to configure logging of any combination of them. Furthermore we also want to be able to configure the text which is prepended to the logged message and optionally switch the secure logging on. (Secure logging replaces all packet CData with text: \'_CData size: NN_' to protect user privacy.)

Let's create following private variables in our component:

[source,java]
private String[] packetTypes = {"message", "presence", "iq"};
private String prependText = "My packet: ";
private boolean secureLogging = false;

As the component configuration is maintained in a form of (_key, value_) Map we have to invent keys for each of our configuration entry:

[source,java]
private static final String PACKET_TYPES_KEY = "packet-types";
private static final String PREPEND_TEXT_KEY = "log-prepend";
private static final String SECURE_LOGGING_KEY = "secure-logging";

There are two methods used to maintain the component configuration: +getDefaults(...)+ where the component provides some configuration defaults and +setProperties(...)+ which sets working configuration for the component:

[source,java,numbered]
--------------------------------------------------------------------
@Override
public Map<String, Object> getDefaults(Map<String, Object> params) {
  Map<String, Object> defs = super.getDefaults(params);
  defs.put(PACKET_TYPES_KEY, packetTypes);
  defs.put(PREPEND_TEXT_KEY, prependText);
  defs.put(SECURE_LOGGING_KEY, secureLogging);
  return defs;
}
 
@Override
public void setProperties(Map<String, Object> props) {
  super.setProperties(props);
  if (props.get( PACKET_TYPES_KEY ) != null ) {
    packetTypes = (String[]) props.get( PACKET_TYPES_KEY );
  }
  if (props.get( PREPEND_TEXT_KEY ) != null ) {
    prependText = (String) props.get( PREPEND_TEXT_KEY );
  }
  if (props.get( SECURE_LOGGING_KEY ) != null ) {
    secureLogging = (Boolean) props.get( SECURE_LOGGING_KEY );
  }
}
--------------------------------------------------------------------

You do not have to implement +getDefaults(...)+ method and provide default settings for your configuration but doing so gives you a few benefits.

The first, from the developer point of view, you don't have to check in the +setProperties(...)+ whether the value is of a correct type or convert it from String to the correct type as it always be either the default or user provided. It will be of a correct type as the configuration framework takes care of the types comparing between the user provided settings and default values. So this just makes your +setProperties(...)+ code much simpler and clearer.

Please note that currently Tigase allows changing properties atomically hence you should check each time if given property was updated at the given call of +setProperties()+.

image:images/test-comp-config-list-smaller.png[]

Secondly this also makes the administrator live easier. As you can see on the screenshot, configuration parameters provided with default values, can be changed via configuration ad-hoc commands. So the administrator can maintain your component at run-time from his XMPP client.

Regardless you implemented the +getDefaults(...)+ method or not you can always manually add parameters to the +init.properties+ file.

The syntax in +init.properties+ file is actually very simple and is described in details in the _Admin Guide_. As it shows on the screenshot the configuration parameter name consists of: '_component name' + /  + 'property key_'. To set configuration for your component in +init.properties+ file you have to append following lines to the file:

[source,bash]
test/log-prepend="My packet: "
test/packet-types[s]=message,presence,iq
test/secure-logging[B]=true

In square brackets you provide the property type, have a look at the _Admin Guide_ documentation for more details.

And this is the complete code of the new component with modified +processPacket(...)+ method taking advantage of configuration settings:

[source,java,numbered]
---------------------------------------------------------------------
import java.util.Map;
import java.util.logging.Logger;
import tigase.server.AbstractMessageReceiver;
import tigase.server.Packet;
 
public class TestComponent extends AbstractMessageReceiver {
 
  private static final Logger log =
    Logger.getLogger(TestComponent.class.getName());
 
  private static final String PACKET_TYPES_KEY = "packet-types";
  private static final String PREPEND_TEXT_KEY = "log-prepend";
  private static final String SECURE_LOGGING_KEY = "secure-logging";
 
  private String[] packetTypes = {"message", "presence", "iq"};
  private String prependText = "My packet: ";
  private boolean secureLogging = false;
 
  @Override
  public void processPacket(Packet packet) {
    for (String pType : packetTypes) {
      if (pType == packet.getElemName()) {
        log.finest(prependText + packet.toString(secureLogging));
      }
    }
  }
 
  @Override
  public Map<String, Object> getDefaults(Map<String, Object> params) {
    Map<String, Object> defs = super.getDefaults(params);
    defs.put(PACKET_TYPES_KEY, packetTypes);
    defs.put(PREPEND_TEXT_KEY, prependText);
    defs.put(SECURE_LOGGING_KEY, secureLogging);
    return defs;
  }
 
  @Override
  public void setProperties(Map<String, Object> props) {
    super.setProperties(props);
    if (props.get( PACKET_TYPES_KEY ) != null ) {
      packetTypes = (String[]) props.get( PACKET_TYPES_KEY );
    }
    // Make sure we can compare element names by reference
    // instead of String content
    for (int i = 0; i < packetTypes.length; i++) {
      packetTypes[i] = packetTypes[i].intern();
    }
    if (props.get( PREPEND_TEXT_KEY ) != null ) {
      prependText = (String) props.get( PREPEND_TEXT_KEY );
    }
    if (props.get( SECURE_LOGGING_KEY ) != null ) {
      secureLogging = (Boolean) props.get( SECURE_LOGGING_KEY );
    }
  }
{'fraction_non_alphanumeric': 0.11773135228901124, 'fraction_numerical': 0.003167659702843352, 'mean_word_length': 3.9570093457943925, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5457991', 'n_tokens_mistral': 3536, 'n_tokens_neox': 3433, 'n_words': 1470}