java.lang.Object
dk.cloudcreate.essentials.components.foundation.postgresql.ListenNotify

public final class ListenNotify extends Object
Helper class for setting up the NOTIFY part of the classical Postgresql LISTEN/NOTIFY concept.
See Listen Notify and
SQL Notify Security It is the responsibility of the user of this component to sanitize any table or column names provided to methods in this class to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the table/column names as a first line of defense.
The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.
It is highly recommended that the tableName value is only derived from a controlled and trusted source.
To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the table/column name values.
Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
See Also:
  • Field Details

  • Constructor Details

    • ListenNotify

      public ListenNotify()
  • Method Details

    • resolveTableChangeChannelName

      public static String resolveTableChangeChannelName(String tableName)
      Resolve the Postgresql LISTEN/NOTIFY channel name used to communicate each notification related to the changes that we NOTIFY, based on the addChangeNotificationTriggerToTable(Handle, String, List, String...) setup.
      Parameters:
      tableName - the name of the table Note:
      The tableName and thereby the resolved Table Change Channel Name will be directly used in constructing SQL statements through string concatenation, which exposes the component to SQL injection attacks.

      Security Note:
      It is the responsibility of the user of this component to sanitize the tableName to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the table name as a first line of defense.
      The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
      However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
      The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
      Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
      Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.
      It is highly recommended that the tableName value is only derived from a controlled and trusted source.
      To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the tableName value.
      Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
      Returns:
      the resolved channel name (the same as the tableName with the tableName checked against PostgresqlUtil.checkIsValidTableOrColumnName(String) as a first link of defense)
    • addChangeNotificationTriggerToTable

      public static void addChangeNotificationTriggerToTable(org.jdbi.v3.core.Handle handle, String tableName, List<ListenNotify.SqlOperation> triggerOnSqlOperations, String... includeAdditionalTableColumnsInNotificationPayload)
      Create (or replace an existing) Table change notification FUNCTION and an AFTER TRIGGER to the given Table in order to support the classical Postgresql LISTEN/NOTIFY concept.
      The NOTIFY/LISTEN channel is defined by the resolveTableChangeChannelName(String) and the payload is a JSON formatted string containing a "table_name" property as well as any additional columns provided in the includeAdditionalTableColumnsInNotificationPayload parameter.

      You can use listen(Jdbi, String, Duration) to setup asynchronous change notification

      Important information from SQL Notify:

      NOTIFY interacts with SQL transactions in some important ways. Firstly, if a NOTIFY is executed inside a transaction, the notify events are not delivered until and unless the transaction is committed. This is appropriate, since if the transaction is aborted, all the commands within it have had no effect, including NOTIFY. But it can be disconcerting if one is expecting the notification events to be delivered immediately. Secondly, if a listening session receives a notification signal while it is within a transaction, the notification event will not be delivered to its connected client until just after the transaction is completed (either committed or aborted). Again, the reasoning is that if a notification were delivered within a transaction that was later aborted, one would want the notification to be undone somehow — but the server cannot “take back” a notification once it has sent it to the client. So notification events are only delivered between transactions. The upshot of this is that applications using NOTIFY for real-time signaling should try to keep their transactions short.

      If the same channel name is signaled multiple times with identical payload strings within the same transaction, only one instance of the notification event is delivered to listeners. On the other hand, notifications with distinct payload strings will always be delivered as distinct notifications.
      Example:
      Given this table definition:
      CREATE TABLE test_table (id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, column1 TEXT, column2 TEXT)
      When adding notification trigger:
      
       jdbi.useTransaction(handle -> {
                   ListenNotify.addChangeNotificationTriggerToTable(handle, "test_table", List.of(ListenNotify.TriggerOnEvent.INSERT), "column1", "column2");
               });
       
      Then the listener subscription:
      
       subscription = ListenNotify.listen(jdbi,
                                          "test_table",
                                          Duration.ofMillis(200))
                                  .subscribe(notificationPayload -> {
                                      // Handle the String based notification payload received
                                  });
       
      will for an inserted row:
      
       jdbi.useTransaction(handle -> {
          handle.execute("INSERT INTO test_table (column1, column2) VALUES ('Column1Value', 'Column2Value')");
       });
       
      receive this payload:
      {"table_name":"test_table","column1":"Column1Value","column2":"Column2Value"}
      which you can parse into an Object using e.g. Jacksons ObjectMapper.
      If you extend TableChangeNotification you automatically inherit the mapping for the table-name and the ListenNotify.SqlOperation properties:
      
       class TestTableNotification extends TableChangeNotification {
           @JsonProperty("column1")
           private String column1;
           @JsonProperty("column2")
           private String column2;
       }
      
      
       var notification = objectMapper.readValue(notificationPayload, TestTableNotification.class);
       
      Parameters:
      handle - the jdbi handle
      tableName - the name of the table for which we want to setup a Table change notification FUNCTION and TRIGGER that can be used for LISTEN/NOTIFY

      Note:
      The tableName as well the result of resolveTableChangeChannelName(String) will be directly used in constructing SQL statements through string concatenation, which exposes the component to SQL injection attacks.

      Security Note:
      It is the responsibility of the user of this component to sanitize the tableName to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the table name as a first line of defense.
      The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
      However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
      The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
      Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
      Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.
      It is highly recommended that the tableName value is only derived from a controlled and trusted source.
      To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the tableName value.
      Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
      triggerOnSqlOperations - what type of ListenNotify.SqlOperation should result in a notification
      includeAdditionalTableColumnsInNotificationPayload - any additional column names that should be included in the NOTIFY JSON formatted String payload

      Note:
      The individual column names in includeAdditionalTableColumnsInNotificationPayload will be directly used in constructing SQL statements through string concatenation, which exposes the component to SQL injection attacks.

      Security Note:
      It is the responsibility of the user of this component to sanitize the includeAdditionalTableColumnsInNotificationPayload to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the column names as a first line of defense.
      The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
      However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
      The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
      Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
      Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.

      It is highly recommended that the includeAdditionalTableColumnsInNotificationPayload value is only derived from a controlled and trusted source.
      To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the includeAdditionalTableColumnsInNotificationPayload value.
      Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
    • removeChangeNotificationTriggerFromTable

      public static void removeChangeNotificationTriggerFromTable(org.jdbi.v3.core.Handle handle, String tableName)
      Remove Table change notification FUNCTION and an AFTER TRIGGER to the given Table in order to support the classical Postgresql LISTEN/NOTIFY concept.
      Parameters:
      handle - the jdbi handle
      tableName - the name of the table for which we want to remove the Table change notification FUNCTION and TRIGGER used for LISTEN/NOTIFY

      Note:
      The tableName as well the result of resolveTableChangeChannelName(String) will be directly used in constructing SQL statements through string concatenation, which exposes the component to SQL injection attacks.

      Security Note:
      It is the responsibility of the user of this component to sanitize the tableName to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the table name as a first line of defense.
      The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
      However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
      The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
      Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
      Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.
      It is highly recommended that the tableName value is only derived from a controlled and trusted source.
      To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the tableName value.
      Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
    • listen

      public static reactor.core.publisher.Flux<String> listen(org.jdbi.v3.core.Jdbi jdbi, String tableName, Duration pollingInterval)
      Listen to notifications related to the given table that was setup using addChangeNotificationTriggerToTable(Handle, String, List, String...)
      As the default Postgresql driver doesn't support asynchronous notification, we're using a polling mechanism.
      Note: If you need to listen to notification from multiple tables, then you MUST use MultiTableChangeListener instead.
      Example:
      
       subscription = ListenNotify.listen(jdbi,
                                          "test_table",
                                          Duration.ofMillis(200))
                                  .subscribe(notificationPayload -> {
                                      // Handle the String based notification payload received
                                  });
       
      Parameters:
      jdbi - the jdbi instance
      tableName - the name of the table for which we want to remove the Table change notification FUNCTION and TRIGGER used for LISTEN/NOTIFY

      Note:
      The tableName as well the result of resolveTableChangeChannelName(String) will be directly used in constructing SQL statements through string concatenation, which exposes the component to SQL injection attacks.

      Security Note:
      It is the responsibility of the user of this component to sanitize the tableName to ensure the security of all the SQL statements generated by this component. The ListenNotify component will call the PostgresqlUtil.checkIsValidTableOrColumnName(String) method to validate the table name as a first line of defense.
      The PostgresqlUtil.checkIsValidTableOrColumnName(String) provides an initial layer of defense against SQL injection by applying naming conventions intended to reduce the risk of malicious input.
      However, Essentials components as well as PostgresqlUtil.checkIsValidTableOrColumnName(String) does not offer exhaustive protection, nor does it assure the complete security of the resulting SQL against SQL injection threats.
      The responsibility for implementing protective measures against SQL Injection lies exclusively with the users/developers using the Essentials components and its supporting classes.
      Users must ensure thorough sanitization and validation of API input parameters, column, table, and index names.
      Insufficient attention to these practices may leave the application vulnerable to SQL injection, potentially endangering the security and integrity of the database.
      It is highly recommended that the tableName value is only derived from a controlled and trusted source.
      To mitigate the risk of SQL injection attacks, external or untrusted inputs should never directly provide the tableName value.
      Failure to adequately sanitize and validate this value could expose the application to SQL injection vulnerabilities, compromising the security and integrity of the database.
      pollingInterval - the interval between each polling attempt
      Returns:
      a Flux that contains the notification payload as a String.
      See Also: