A pluggable interface is required for the OpenEdge HealthScanner to output data to other monitoring tools. You can write a custom HealthScanner view to output HealthScanner details in the format of your choice.

The plugin architecture in PAS for OpenEdge is based on the Java Service Provider Interface (SPI). PAS for OpenEdge requires implementing the correct interfaces for formatting the HealthScanner data and packaging the implementation in a JAR file.

The OpenEdge HealthScanner has four built-in views that can be accessed by URLs in the following form:
  • http://localhost:port/health?view=details
  • http://localhost:port/health?view=summary
  • http://localhost:port/health?view=config
  • http://localhost:port/health?view=dataset

This topic describes how to create additional views when a specific format is needed for the HealthScanner data. For example, a new view called Acme might be called using the view=acme parameter in the URL.

To write a custom HealthScanner view that can output data to other monitoring tools, perform the following steps:
  1. Implement the com.progress.appserv.services.health.spi.HealthViewVisitor interface. This interface follows a hierarchical visitor pattern and is available in oecollector-version.jar:
    public interface HealthViewVisitor 
    {
    	// Called for each probe that is not a composite.
    	void visit ( HealthProbe probe );
    
    	// Called for each composite on entering.
    	void enter ( CompositeHealthProbe probe );
    
    	// Called for each composite on leaving.
    	void leave ( CompositeHealthProbe probe );     
    
    	// Method to get result     
    	String getDetails (); 
    }
  2. Implement the com.progress.appserv.services.health.spi.HealthViewFactory interface. This is a factory that creates instances of the view visitor interface that you implemented in Step 1. The HealthViewFactory interface is also included in oecollector-version.jar:
    public interface HealthViewFactory 
    { 
    	// Provide the name of the view visitor.
    	String getViewName ();    
    
    	// Get an instance of the view visitor.
    	HealthViewVisitor getVisitor ();
    }
  3. Register the service provider by creating a file named com.progress.appserv.services.health.spi.HealthViewFactory. The content of the file is the name of your factory class.

    For example, if your view factory implementation from Step 2 contains the line com.acme.AcmeViewFactory, then your factory class is com.acme.AcmeViewFactory.

  4. Package the service as a JAR file. Include compiled versions of the classes that you created in Step 1 and Step 2. As well as the file you created in Step 3.
    For example, if your visitor interface is com.acme.AcmeViewVisitor and your factory is com.acme.AcmeViewFactory then the contents of the JAR file looks like this:
    META-INF/MANIFEST.MF
    META-INF/services/com.progress.appserv.services.health.spi.HealthViewFactory
    com/acme/AcmeViewFactory.class
    com/acme/AcmeViewVisitor.class
  5. Copy the JAR file into $DLC/servers/pasoe/lib/ext.
  6. Restart the PAS for OpenEdge instance, and test the view in a browser:
    http://localhost:port/health?view=acme

Example interface implementation

The following code is an example of a HealthScanner visitor interface implementation called AcmeViewVisitor.java:

package com.acme;

import java.util.ArrayDeque;
import java.util.Deque;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.progress.appserv.services.health.probe.CompositeHealthProbe;
import com.progress.appserv.services.health.probe.HealthProbe;
import com.progress.appserv.services.health.spi.HealthViewVisitor;
import com.progress.appserv.services.health.probe.ProbeResult;

/**
 * A probe visitor that creates a JSON document from the polling results
 * of a tree of probes.
 */
public class AcmeViewVisitor 
    implements HealthViewVisitor 
{
    private ObjectNode json = null;
    private Deque<ArrayNode> ancestors;
    private ArrayNode current = null;
    
    // Constants that are specific to the JSON created by this visitor
    private static final String NAME = "name";
    private static final String HEALTH = "health";
    private static final String CHILDREN = "children";
    
    // JSON mapper.
    private ObjectMapper mapper;
    
    /**
     * Constructor.
     */
    public AcmeViewVisitor() 
    {
        // Create a mapper for creating JSON nodes.
        this.mapper = new ObjectMapper();
        
        // Create a stack for keeping track of ancestor layers.
        this.ancestors = new ArrayDeque<>();
    }
        
    /**
     * Get a JSON description of the detailed results of polling the probe tree
     * 
     * @return  A JSON document.
     */
    @Override
    public String getDetails() 
    {
        // Guard for visitor not yet accepted.
        if (this.json == null)
            return null;
        
        String retval = null;
        
        try
        {
            retval = this.mapper.writeValueAsString( this.json );
        }
        catch (JsonProcessingException e)
        {
            retval = "{\"message\": \"Details unavailable.\"}";
        }

        return retval;
    }
    
    /**
     * Called for each probe that is not a composite.
     * 
     * @param  The probe to visit.
     */
    @Override
    public void visit( HealthProbe probe )
    {
        createJsonNode( probe );
    }

    /**
     * Called for each composite on entering
     * 
     * @param probe  The composite probe.
     */
    @Override
    public void enter( CompositeHealthProbe probe )
    {
        ObjectNode probeNode = createJsonNode( probe );
        ArrayNode children = mapper.createArrayNode();
        probeNode.set( CHILDREN, children );

        if (this.current != null)
            this.ancestors.push( this.current );
        this.current = children;
    }

    /**
     * Called for each composite on leaving, after traversing the child probes.
     * 
     * @param  the composite probe
     */
    @Override
    public void leave( CompositeHealthProbe probe )
    {
        this.current = this.ancestors.isEmpty() ? null : this.ancestors.pop();
    }

    /**
     * Create a json object node from a probe polling result.
     * 
     * @param probe  The probe to get result from
     * @return  A JSON object node.
     */
    private ObjectNode createJsonNode( HealthProbe probe ) 
    {
        // Create the main node with name
        ObjectNode probeNode = mapper.createObjectNode();
        probeNode.put( NAME, probe.getName() );

        ProbeResult result = probe.getPollResult();
        if (result != null)
            probeNode.put( HEALTH, result.health() );

        // Add to the parent
        addJsonNodeToParent( probeNode );
        
        return probeNode;
    }
        
    /**
     * Add the given node to the current list of children.
     * 
     * @param probeNode  The node to add.
     */
    private void addJsonNodeToParent( ObjectNode probeNode ) 
    {
        if (current == null)
            // Must be the root. Save it as the result of this visitor
            this.json = probeNode;
        else
            // Not the root.  Add to array of children of current composite.
            this.current.add( probeNode );
    }
}

The following code is an example of a HealthScanner factory interface implementation called AcmeViewFactory.java:

package com.acme;

import com.progress.appserv.services.health.spi.HealthViewFactory;
import com.progress.appserv.services.health.spi.HealthViewVisitor;

public class AcmeViewFactory
    implements HealthViewFactory
{
    private final static String NAME = "acme";

    public String getViewName()
    {
        return NAME;
    }

    public HealthViewVisitor getVisitor()
    {
        return new AcmeViewVisitor();
    }
}
Note: The HealthScanner service provider interfaces are available in the oecollector-version.jar that is included with your PAS for OpenEdge distribution in the $DLC/servers/pasoe/lib/ext directory.