Custom Publishers

Develop custom opportunity publishers and integrate them with POB.

Last Updated: 27 May 2022 • Page Author: Jillur Quddus

Overview

This page is intended for Java software engineers who wish to develop a custom publisher to publish procurement opportunities to any downstream application or system (in addition to the native publishers provided by POB).

Development

POB can be easily extended to publish procurement opportunities to any downstream application or system. To create a custom publisher, simply extend the OpportunityPublisher abstract class found in the $POB_BASE/pob-data/pob-publishers Maven module and implement the publish(Opportunity opportunity) abstract method as demonstrated in the following code snippet:

import ai.hyperlearning.pob.data.publishers.OpportunityPublisher

public class TestPublisher extends OpportunityPublisher {

    public TestPublisher(Map<String, Object> properties) {
        super(properties);
    }
    
    @Override
    public void publish(Opportunity opportunity) {
        ... custom publisher code goes here ...
    }

}

To review POB's Opportunity entity model class, please open the Opportunity class found in the $POB_BASE/pob-model Maven module.

Examples

POB's native opportunity publishers may be found in the $POB_BASE/pob-data/pob-publishers Maven module. For example, POB's native Slack publisher SlackPublisher is provided below - it works by simply creating a HTTP client and executing a HTTP POST request to the configured Slack Webhook URL where the HTTP request body contains the Opportunity object as a JSON object conforming to the required Slack webhook message format.

package ai.hyperlearning.pob.data.publishers.slack;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ai.hyperlearning.pob.data.publishers.CommonPublisherProperties;
import ai.hyperlearning.pob.data.publishers.OpportunityPublisher;
import ai.hyperlearning.pob.data.publishers.exceptions.OpportunityPublishingException;
import ai.hyperlearning.pob.model.Opportunity;

import okhttp3.Call;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Slack Publisher
 *
 * @author jillurquddus
 * @since 0.0.1
 */

public class SlackPublisher extends OpportunityPublisher {
    
    private static final Logger LOGGER = 
            LoggerFactory.getLogger(SlackPublisher.class);
    
    // Webhook properties
    private static final String CHANNEL_PROPERTY_KEY = "channel";
    private static final String WEBHOOK_PROPERTY_KEY = "webhook";
    
    // Request client
    private OkHttpClient client;
    
    // Request properties
    private static final String MEDIA_TYPE = 
            org.springframework.http.MediaType.APPLICATION_JSON_VALUE;
    
    // Message content and formatting
    private static final String JSON_PLACEHOLDER_SLACK_CHANNEL = 
            "[SLACK_CHANNEL]";
    private static final String REQUEST_BODY_JSON_TEMPLATE = 
            "{" + 
                    "\"channel\": \"" + JSON_PLACEHOLDER_SLACK_CHANNEL + "\", " +
                    "\"text\": \"" + 
                        CommonPublisherProperties.MESSAGE_CONTENT_TEMPLATE + 
                    "\", " + 
                    "\"unfurl_links\": true" + 
            "}";
    
    public SlackPublisher(Map<String, Object> properties) {
        super(properties);
        client = new OkHttpClient();
    }
    
    @Override
    public void publish(Opportunity opportunity) {
        
        LOGGER.info("Started the Slack publisher.");
        Response response = null;
        try {
            
            // Get the Slack publisher properties
            String channel = (String) getProperties().get(CHANNEL_PROPERTY_KEY);
            String webhook = (String) getProperties().get(WEBHOOK_PROPERTY_KEY);
            
            // Create the Slack message as a JSON object
            String json = buildMessage(REQUEST_BODY_JSON_TEMPLATE, opportunity)
                    .replace(JSON_PLACEHOLDER_SLACK_CHANNEL, channel);
            
            // Create the HTTP POST request
            if (client == null)
                client = new OkHttpClient();
            RequestBody body = RequestBody.create(
                    json, MediaType.parse(MEDIA_TYPE));
            Request request = new Request.Builder()
                      .url(webhook)
                      .post(body)
                      .build();
            
            // Submit the POST request and get the Slack webhook response
            LOGGER.debug("POSTing opportunity to Slack channel: \n{}", json);
            Call call = client.newCall(request);
            response = call.execute();
            
        } catch (Exception e) {
            
            String message = "An error was encountered whilst attempting to "
                    + "publish the opportunity to Slack.";
            throw new OpportunityPublishingException(message, e.getMessage());
        
        } finally {
            
            // Close the response object to avoid connection leaks
            if (response != null) {
                try {
                    response.close();
                } catch (Exception e) {
                    LOGGER.warn("An error was encountered whilst attempting to "
                            + "close the OkHttpClient response object.", e);
                }
            }
            
        }
        
    }

}

Integration

To integrate your custom publisher into the main POB data pipeline, simply append your custom publisher to the publishers list in the POB's Application Configuration found in the application.yml file in the $POB_BASE/pob-configuration Maven module, as follows:

publishers:
    - id: csv
      enabled: true
      publisherClass: ai.hyperlearning.pob.data.publishers.csv.CsvPublisher
      properties:
        path: ${java.io.tmpdir}/pob.csv
    - id: slack
      enabled: true
      publisherClass: ai.hyperlearning.pob.data.publishers.slack.SlackPublisher
      properties:
        channel: ${slack-channel}
        webhook: ${slack-webhook}
    - id: google-chat
      enabled: false
      publisherClass: ai.hyperlearning.pob.data.publishers.google.GoogleChatPublisher
      properties:
        webhook: ${google-chat-webhook}
    - id: elasticsearch
      enabled: false
      publisherClass: ai.hyperlearning.pob.data.publishers.elastic.ElasticsearchPublisher
      properties:
        url: ${elasticsearch-url}
        username: ${elasticsearch-username}
        password: ${elasticsearch-password}
        index: pob
        ssl: true
    - id: my-custom-publisher
      enabled: true
      publisherClass: com.example.pob.publishers.MyCustomPublisher
      properties:
        customProp: ${custom-value}

Once configured, the main POB data pipeline will instantiate and execute all the enabled publisher classes that have been registered in application.yml, including any newly added custom publishers. Note that all custom publisher properties defined in application.yml will be injected and made available to your custom publisher class by the main POB data pipeline via the properties hash map in the Publisher object.

Last updated