Creating Custom Notifier

The notification service received a major overhaul in Payara Server Community 5.2020.5 (October 2020) which made it possible to implement new notifiers in a modular fashion. The overhaul also involved rewriting the existing notifiers as extensions and porting them over to a new community repository.

The modular design allows developers to create their custom Notifier and plug it in into the Payara Platform. This guide describes how to create your Notifier.

Requirements

  1. A class defining the configuration

    Since the data needs to be stored in the domain.xml file.

  2. Asadmin commands

    To read and write the configuration, including activating the plugin.

  3. The Notifier itself

    Which takes the Notification event and writes it to the channel you want to implement.

  4. Optionally, a plugin for the Admin Console

    So that the notifier can be managed through Payara Admin Console.

Project Setup

The Notifier must be an OSGI bundle before it can be integrated within the Payara Platform. One bundle contains the configuration class, the asadmin commands and the notifier itself. The other module is the optional plugin for the Admin Console.

Since each module must be an OSGI module, define the maven packaging

<packaging>glassfish-jar</packaging>

Several plugins are used to generate the correct OSGI module for Payara.

<plugins>
    <plugin>
        <groupId>org.glassfish.build</groupId>
        <artifactId>glassfishbuild-maven-plugin</artifactId>
        <version>3.2.20.payara-p2</version>
        <extensions>true</extensions>
        <configuration>
            <dir>${project.build.directory}/classes</dir>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.glassfish.hk2</groupId>
        <artifactId>hk2-inhabitant-generator</artifactId>
        <version>2.6.1.payara-p1</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate-inhabitants</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <supportedProjectTypes>jar,glassfish-jar</supportedProjectTypes>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.apache.felix</groupId>
        <artifactId>maven-bundle-plugin</artifactId>
        <version>4.1.0</version>
        <executions>
            <execution>
                <id>default-manifest</id>
                <phase>process-classes</phase>
                <goals>
                    <goal>manifest</goal>
                </goals>
                <configuration>
                    <Export-Package />
                    <instructions>
                        <_include>-osgi.bundle</_include>
                    </instructions>
                    <excludeDependencies>tools-jar</excludeDependencies>
                    <supportedProjectTypes>
                        <supportedProjectType>glassfish-jar</supportedProjectType>
                        <supportedProjectType>jar</supportedProjectType>
                    </supportedProjectTypes>
                </configuration>
            </execution>
        </executions>
        <configuration>
            <Export-Package />
            <instructions>
                <_include>-osgi.bundle</_include>
            </instructions>
            <excludeDependencies>tools-jar</excludeDependencies>
            <supportedProjectTypes>
                <supportedProjectType>glassfish-jar</supportedProjectType>
                <supportedProjectType>jar</supportedProjectType>
            </supportedProjectTypes>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.glassfish.build</groupId>
        <artifactId>command-security-maven-plugin</artifactId>
        <version>1.0.10.payara-p1</version>
        <configuration>
            <isFailureFatal>false</isFailureFatal>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.glassfish.hk2</groupId>
        <artifactId>config-generator</artifactId>
        <version>2.5.0-b53</version>
        <executions>
            <execution>
                <goals>
                    <goal>generate-injectors</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <supportedProjectTypes>jar,glassfish-jar</supportedProjectTypes>
        </configuration>
    </plugin>
    <plugin>
        <groupId>org.glassfish.hk2</groupId>
        <artifactId>osgiversion-maven-plugin</artifactId>
        <version>2.6.1.payara-p1</version>
        <executions>
            <execution>
                <id>default-compute-osgi-version</id>
                <phase>process-classes</phase>
                <goals>
                    <goal>compute-osgi-version</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <dropVersionComponent>qualifier</dropVersionComponent>
            <versionPropertyName>project.osgi.version</versionPropertyName>
        </configuration>
    </plugin>
</plugins>

The Payara specific versions of these plugins can be found in our Patched repository on GitHub.

  <pluginRepositories>
        <pluginRepository>
            <id>payara-nexus-artifacts</id>
            <url>https://nexus.payara.fish/repository/payara-artifacts</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

The Notifier API itself is available in the internal-api artifact that is located in our Public Nexus repository.

   <dependency>
       <groupId>fish.payara.server.internal.common</groupId>
       <artifactId>internal-api</artifactId>
       <version>5.27.0</version>
       <optional>true</optional>
   </dependency>
  <repository>
      <id>payara-nexus-enterprise-artifacts</id>
      <name>Payara Enterprise Artifacts</name>
      <url>https://nexus.payara.fish/repository/payara-enterprise-artifacts-private</url>
      <releases>
          <enabled>true</enabled>
      </releases>
      <snapshots>
          <enabled>false</enabled>
      </snapshots>
  </repository>

The knowledge base article Using Payara Enterprise Builds in a Maven project describes how you can define the authentication for the repository that is defined above.

Any additional dependencies for your notifier also need to be specified and included when you install it within Payara.

Have a look at Notifiers GitHub repository for the setup of the current Notifiers.

Installation

When the OSGI module is generated by Maven, drop the jar file in the <PAYARA-HOME>/glassfish/modules directory and start the domain.

Notifier Configuration

By default, each Notifier has 3 configuration parameters

  • enabled

    Is the Notifier active?

  • noisy

    When set, the notifier includes verbose information in the output.

  • dynamic

    Whether to apply the changes immediately (true) or after server restart.

Additional configuration can be created by defining properties in a special annotated interface.

import org.jvnet.hk2.config.Attribute;
import org.jvnet.hk2.config.Configured;

import fish.payara.internal.notification.PayaraNotifierConfiguration;

@Configured
public interface CustomNotifierConfiguration extends PayaraNotifierConfiguration {

    @Attribute(defaultValue = "*", dataType = String.class)
    String getTestValue();
    void setTestValue(String value) throws PropertyVetoException;

    @Attribute(required = false, dataType = Integer.class)
    Integer getThresholdValue();
    void setThresholdValue(Integer value) throws PropertyVetoException;

    @Attribute(dataType = Boolean.class, defaultValue = "true")
    Boolean getDuplicateValue();
    void setDuplicateValue(Boolean value) throws PropertyVetoException;

}

The highlights of the above code are

  • @Configured`

    Defines the interface as part of the configuration, and a suitable proxy holding the actual configuration values will be created at runtime.

  • PayaraNotifierConfiguration

    Holds the common attributes like enabled and noisy, and is required for storing it in the Payara Configuration file.

  • @Attribute

    Defines an additional configuration property. You can specify the type of the value (dataType), if the value is required (required), and a default value if the user doesn’t specify it explicitly (defaultValue).

Notifier Asadmin Commands

The Asadmin Commands are required so your custom notifier can be configured and activated once it is installed on the Payara Platform. You can choose the name of those commands, but the Payara naming scheme is

get-<notifierName>-notifier-configuration
set-<notifierName>-notifier-configuration

For each of the Asadmin commands, you need to create a Java Class.

import org.glassfish.api.admin.CommandLock;
import org.glassfish.api.admin.ExecuteOn;
import org.glassfish.api.admin.RestEndpoint;
import org.glassfish.api.admin.RestEndpoints;
import org.glassfish.api.admin.RuntimeType;
import org.glassfish.config.support.CommandTarget;
import org.glassfish.config.support.TargetType;
import org.glassfish.hk2.api.PerLookup;
import org.jvnet.hk2.annotations.Service;

import fish.payara.internal.notification.admin.BaseGetNotifierConfigurationCommand;
import fish.payara.internal.notification.admin.NotificationServiceConfiguration;

@Service(name = "get-custom-notifier-configuration")
@PerLookup
@CommandLock(CommandLock.LockType.NONE)
@ExecuteOn({RuntimeType.DAS, RuntimeType.INSTANCE})
@TargetType(value = {CommandTarget.DAS, CommandTarget.STANDALONE_INSTANCE, CommandTarget.CLUSTER, CommandTarget.CLUSTERED_INSTANCE, CommandTarget.CONFIG})
@RestEndpoints({
   @RestEndpoint(configBean = NotificationServiceConfiguration.class,
       opType = RestEndpoint.OpType.GET,
       path = "get-custom-notifier-configuration",
       description = "Lists Custom Notifier Configuration")
})
public class GetCustomNotifierConfigurationCommand extends BaseGetNotifierConfigurationCommand<CustomNotifierConfiguration> {

    @Override
    protected Map<String, Object> getNotifierConfiguration(CustomNotifierConfiguration configuration) {
        Map<String, Object> map = super.getNotifierConfiguration(configuration);

        if (configuration != null) {
            map.put("Test Value", configuration.getTestValue());
            //...
        }

        return map;
    }

}

The highlights of the code are:

  • @Service(name = "get-custom-notifier-configuration")

    Defines the name of the asadmin command.

  • @ExecuteOn and @TargetType

    Determines on which environments the command can be used. Make sure it can be run on the Domain and the instances itself.

  • @RestEndpoint

    All Asadmin commands are sent as REST calls to the server. This annotation defines the endpoint (name, type, etc) and is required to make the command work.

  • BaseGetNotifierConfigurationCommand<CustomNotifierConfiguration>

    Base implementation of the asadmin command to retrieve the configuration for a notifier.

  • protected Map<String, Object> getNotifierConfiguration()

    Method that needs to be implemented to retrieve the specific values of the notifier. The result is a Map (having key - value pairs) describing the configuration that will be printed as the result of the Asadmin command.

The class for setting the notifier configuration can look like this

import java.beans.PropertyVetoException;

import org.glassfish.api.Param;
import org.glassfish.api.admin.CommandLock;
import org.glassfish.api.admin.ExecuteOn;
import org.glassfish.api.admin.RestEndpoint;
import org.glassfish.api.admin.RestEndpoints;
import org.glassfish.api.admin.RuntimeType;
import org.glassfish.config.support.CommandTarget;
import org.glassfish.config.support.TargetType;
import org.glassfish.hk2.api.PerLookup;
import org.jvnet.hk2.annotations.Service;

import fish.payara.internal.notification.admin.BaseSetNotifierConfigurationCommand;
import fish.payara.internal.notification.admin.NotificationServiceConfiguration;


@Service(name = "set-custom-notifier-configuration")
@PerLookup
@CommandLock(CommandLock.LockType.NONE)
@ExecuteOn({RuntimeType.DAS, RuntimeType.INSTANCE})
@TargetType(value = {CommandTarget.DAS, CommandTarget.STANDALONE_INSTANCE, CommandTarget.CLUSTER, CommandTarget.CLUSTERED_INSTANCE, CommandTarget.CONFIG})
@RestEndpoints({
        @RestEndpoint(configBean = NotificationServiceConfiguration.class,
                opType = RestEndpoint.OpType.POST,
                path = "set-custom-notifier-configuration",
                description = "Configures Custom Notification Service")
})
public class SetCustomNotifierConfigurationCommand extends BaseSetNotifierConfigurationCommand<CustomNotifierConfiguration> {

    @Param(name = "testValue")
    private String testValue;

    @Param(name = "thresholdValue", optional = true)
    private Integer thresholdValue;

    @Param(name = "duplicateValue")
    private Boolean duplicateValue;

    @Override
    protected void applyValues(CustomNotifierConfiguration configuration) throws PropertyVetoException {
        super.applyValues(configuration);
        if (this.testValue != null) {
            configuration.setTestValue(this.testValue);
        }
        // ...
    }

}

Some additional aspects of the example not already discussed for the get variant of the Asadmin command.

  • @Param

    Parameters in the REST call holding the new configuration values. The name is the name of the variable defined in the Notifier Configuration class.

Notifier code

Now that we have the configuration data, and the possibility to configure the custom notifier, we can implement the Notifier itself.

import org.jvnet.hk2.annotations.Service;

import fish.payara.internal.notification.PayaraConfiguredNotifier;
import fish.payara.internal.notification.PayaraNotification;

@Service(name = "custom-notifier")
public class CustomNotifier extends PayaraConfiguredNotifier<CustomNotifierConfiguration> {

    @Override
    public void handleNotification(PayaraNotification event) {
        // Handle the event.
    }

    @Override
    public void bootstrap() {
        System.out.println("Bootstrapping custom notifier");
    }

    @Override
    public void destroy() {
        System.out.println("Destroying custom notifier");
    }

}

The highlights of the Notifier are

  • @Service(name = "custom-notifier")

    Name of the notifier within the Payara platform. The value custom-notifier will appear in the list of all notifiers on the appropriate screens of the Admin Console.

  • PayaraConfiguredNotifier

    The base class implementing the glue code for the Payara Platform. You only need to implement the method handleNotification to handle the event.

  • bootstrap()

    Override this method if you want to perform some actions when the Notifier is created.

  • destroy()

    Override this method if you want to perform some actions when the Notfier is destroyed.

Payara Notification

This is the main class of the Notification event. Events can be raised for

  • JMX Monitoring

  • Health Check Monitoring

  • Asadmin command auditing feature

  • Request Tracing traces

The class holds the following information

  • eventType

    Log.Level value of the event, like INFO, WARN, …​

  • hostName

    Hostname on which the Notification was generated.

  • domainName

    Domain name on which the Notification was generated like domain1.

  • instanceName

    Instance name on which the Notification was generated.

  • serverName

    Name of the server the instance is running on.

  • subject

    The short 'subject' of the notification. This depends on the subsystem that generated the notification.

  • message

    The full message of the notification, depending on the subsystem that generated the notification.

  • data

    Some more detailed data on the Notification event, depending on subsystem that generated the notification.

    • HealthCheckNotificationData

      Data for the Health Check notification event like Status (GOOD, CRITICAL, …​ )

    • RequestTracingNotificationData

      Data for the Request Tracing notification event like the Tracing Span information details.

Notifier Plugin

With the Notifier Admin Console Plugin, you can have a dedicated screen for the configuration of the custom notifier. This is optional and doesn’t need to be created.

The configuration class, Asadmin commands and Notifier itself are enough to have a working custom Notifier.

In this section, the basic requirements and conventions are described in order to create such a plugin. Have a look at the plugins of the existing notifiers to see several examples how such a custom plugin could be created.

The Notifier plugin is based on the Console Provider of Payara to extend the Admin Console functionality.

Plugin

Define the plugin by defining the file META-INF/admingui/console-plugin.xml and specify the following content:

<console-config id="customNotifier">
   <integration-point
      id="customNotifier"
      type="fish.payara.admingui:notifierTab"
      priority="40"
      parentId="notificationConfigTabs"
      content="custom/customNotifierTabs.jsf"
   />
</console-config>
  • id

    Define the unique id for the integration and is also used as part of the identifier.

  • priority

    Location of the Notifier on the screen. A higher priority (lower value) means the tab for the custom notifier is more to the left.

  • content

    Location of the snippet that defines the Tab (using the Payara JSF templating framework)

Tab

The snippet that defines the Tab on the Notifiers page.

<sun:tab id="customNotifierTab" immediate="true" text="$resource{i18nexn.notifier.custom.tabs.tabText}"
         toolTip="$resource{i18nexn.notifier.custom.tabs.tabToolTip}">
    <!beforeCreate
        setResourceBundle(key="i18nexn" bundle="fish.payara.admingui.notifier.custom.Strings");
    />
    <!command
        setSessionAttribute(key="notificationConfigTab" value="customNotifierTab");
        gf.redirect(page="#{request.contextPath}/customNotifier/custom/customNotifierConfiguration.jsf?configName=#{pageSession.configName}");
    />
</sun:tab>

Some important notices about this snippet

  • id

    The id of the component. It should be the id defined in the console-plugin.xml file followed by Tab.

  • Resource bundle

    Make sure the Resource bundle is defined containing all the labels that needs to be shown on the screen.

  • Command

    Link here to the location of the snippet defining the body of the Notifier configuration page.

Configuration Page

This page has more requirements in order to operate correctly within the Payara Admin Console. The easiest way to define is to start from an existing example and modify the configuration fields.

each configuration property requires the following snippet

<sun:property id="testValueProp" labelAlign="left" noWrap="#{true}" overlapLabel="#{false}"
              label="$resource{i18nexn.notifier.jfr.configuration.categoryLabel}"
              helpText="$resource{i18nexn.notifier.jfr.configuration.categoryLabelHelpText}">
    <sun:textField id="namesField"  maxLength="255"
                   text="#{pageSession.valueMap['testvalue']}" styleClass="string"
                   required="#{true}"/>
</sun:property>

Some important aspects about the snippet

  • sun:textField

    because we want to have an input field where the user can enter some text. Use sun:checkbox if you want a checkbox for a true/false value.

  • text="#{pageSession.valueMap['testvalue']}"

    Define the property that needs to be displayed and the value is assigned to this property when the save button is clicked. This must be an all lowercase value of the property you have defined in the configuration and Rest parameter.