001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * docs/licenses/cddl.txt 011 * or http://www.opensource.org/licenses/cddl1.php. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * docs/licenses/cddl.txt. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Portions Copyright 2014-2024 Ping Identity Corporation 026 */ 027package com.unboundid.directory.sdk.ds.api; 028 029import com.unboundid.asn1.ASN1OctetString; 030import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider; 031import com.unboundid.directory.sdk.common.internal.Reconfigurable; 032import com.unboundid.directory.sdk.common.internal.UnboundIDExtension; 033import com.unboundid.directory.sdk.common.operation.AddRequest; 034import com.unboundid.directory.sdk.common.operation.DeleteRequest; 035import com.unboundid.directory.sdk.common.operation.ModifyDNRequest; 036import com.unboundid.directory.sdk.common.operation.ModifyRequest; 037import com.unboundid.directory.sdk.common.types.Entry; 038import com.unboundid.directory.sdk.common.types.OperationContext; 039import com.unboundid.directory.sdk.ds.config.NotificationManagerConfig; 040import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension; 041import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 042import com.unboundid.directory.sdk.ds.types.Notification; 043import com.unboundid.directory.sdk.ds.types.NotificationDeliveryResult; 044import com.unboundid.directory.sdk.ds.types.NotificationProperties; 045import com.unboundid.ldap.sdk.LDAPException; 046import com.unboundid.ldap.sdk.ResultCode; 047import com.unboundid.ldap.sdk.unboundidds.extensions. 048 NotificationDestinationDetails; 049import com.unboundid.util.Extensible; 050import com.unboundid.util.ThreadSafety; 051import com.unboundid.util.ThreadSafetyLevel; 052import com.unboundid.util.args.ArgumentException; 053import com.unboundid.util.args.ArgumentParser; 054 055import java.util.Collections; 056import java.util.List; 057import java.util.Map; 058 059 060 061/** 062 * This class defines an API that must be implemented by extensions which 063 * wish to deliver notification of changes of interest processed within the 064 * server. 065 * <BR> 066 * <H2>Implementing a Notification Manager</H2> 067 * <H3>Subscription management</H3> 068 * A notification manager has one or more notification destinations which 069 * determine where notifications are to be delivered. Each notification 070 * destination has one or more notification subscriptions which determine 071 * which changes should result in a notification for that destination. 072 * Destinations and subscriptions each have associated details whose syntax 073 * is specific to the notification manager implementation. 074 * The notification manager implementation must implement the following 075 * methods to keep track of destinations, subscriptions and their associated 076 * details: 077 * <UL> 078 * <LI>initializeNotificationDestinations</LI> 079 * <LI>areDestinationDetailsAcceptable</LI> 080 * <LI>setNotificationDestination</LI> 081 * <LI>deleteNotificationDestination</LI> 082 * <LI>areSubscriptionDetailsAcceptable</LI> 083 * <LI>setNotificationSubscription</LI> 084 * <LI>deleteNotificationSubscription</LI> 085 * </UL> 086 * <H3>Matching changes with subscriptions</H3> 087 * A notification manager must implement the following methods (one for each 088 * type of LDAP operation) to determine whether a change matches any 089 * notification subscriptions. The notification manager may provide arbitrary 090 * properties that it wishes to be included with a notification: 091 * <UL> 092 * <LI>getAddNotificationProperties</LI> 093 * <LI>getDeleteNotificationProperties</LI> 094 * <LI>getModifyNotificationProperties</LI> 095 * <LI>getModifyDNNotificationProperties</LI> 096 * </UL> 097 * <H3>Delivering notifications</H3> 098 * A notification manager must implement the following method to attempt 099 * delivery of a notification to a destination: 100 * <UL> 101 * <LI>attemptDelivery</LI> 102 * </UL> 103 * <BR> 104 * <H2>Configuring a Notification Manager</H2> 105 * The LDAP changelog must first be enabled on each server that is to deliver 106 * notifications: 107 * <PRE> 108 * dsconfig set-backend-prop \ 109 * --backend-name changelog \ 110 * --set enabled:true 111 * </PRE> 112 * In order to configure a notification manager created using this API, 113 * use a command like: 114 * <PRE> 115 * dsconfig create-notification-manager \ 116 * --manager-name "<I>{manager-ID}</I>" \ 117 * --type third-party \ 118 * --set enabled:true \ 119 * --set "extension-class:<I>{class-name}</I>" \ 120 * --set "subscription-base-dn:<I>{subscription-base-dn}</I>" \ 121 * --set "extension-argument:<I>{name=value}</I>" 122 * </PRE> 123 * where "<I>{manager-ID}</I>" is the ID to use for the notification 124 * manager instance, "<I>{subscription-base-dn}</I>" is the base DN where 125 * subscription information for this notification manager is to be stored, 126 * "<I>{class-name}</I>" is the fully-qualified name of the Java class that 127 * extends 128 * {@code com.unboundid.directory.sdk.ds.api.NotificationManager}, and 129 * "<I>{name=value}</I>" represents name-value pairs for any arguments to 130 * provide to the notification manager. If multiple arguments should be 131 * provided to the notification manager, then the 132 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be 133 * provided multiple times. 134 * <BR> 135 * <BR> 136 * The notification manager must also be associated with a backend using a 137 * command like: 138 * <PRE> 139 * dsconfig set-backend-prop \ 140 * --backend-name "<I>userRoot</I>" \ 141 * --set "notification-manager:<I>{manager-ID}</I>" 142 * </PRE> 143 * The <I>{subscription-base-dn}</I> of the notification manager must be 144 * within the scope of the <I>base-dn</I> of the backend. 145 * <BR> 146 * <BR> 147 * The above configuration should be made on each server that holds a replica 148 * of the backend data to enable delivery of notifications for changes to that 149 * data. 150 */ 151@Extensible() 152@DirectoryServerExtension() 153@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_THREADSAFE) 154public abstract class NotificationManager 155 implements UnboundIDExtension, 156 Reconfigurable<NotificationManagerConfig>, 157 ExampleUsageProvider 158{ 159 /** 160 * {@inheritDoc} 161 */ 162 public abstract String getExtensionName(); 163 164 165 166 /** 167 * {@inheritDoc} 168 */ 169 public abstract String[] getExtensionDescription(); 170 171 172 173 /** 174 * {@inheritDoc} 175 */ 176 public void defineConfigArguments(final ArgumentParser parser) 177 throws ArgumentException 178 { 179 // No arguments will be allowed by default. 180 } 181 182 183 184 /** 185 * Initializes this notification manager. 186 * 187 * @param serverContext A handle to the server context for the server in 188 * which this extension is running. 189 * @param config The general configuration for this notification 190 * manager. 191 * @param parser The argument parser which has been initialized from 192 * the configuration for this notification manager. 193 * 194 * @throws LDAPException If a problem occurs while initializing this 195 * notification manager. 196 */ 197 public void initializeNotificationManager( 198 final DirectoryServerContext serverContext, 199 final NotificationManagerConfig config, 200 final ArgumentParser parser) 201 throws LDAPException 202 { 203 // No initialization will be performed by default. 204 } 205 206 207 208 /** 209 * Initializes or re-initializes the notification destinations for this 210 * notification manager. This will be called once after the notification 211 * manager has been initialized and any time subsequently. 212 * 213 * @param destinations The list of defined notification destinations and 214 * their associated subscriptions. 215 * 216 * @throws LDAPException If a problem occurs while initializing the 217 * notification destinations for this notification 218 * manager. 219 */ 220 public abstract void initializeNotificationDestinations( 221 List<NotificationDestinationDetails> destinations) 222 throws LDAPException; 223 224 225 226 /** 227 * Determine whether the provided destination details are acceptable. 228 * If this method returns true then it is expected that a call to 229 * {@code setNotificationDestination} with the same details will not 230 * fail due to invalid details. 231 * @param destinationID The notification destination ID. 232 * @param destinationDetails The notification destination details. 233 * @param unacceptableReasons A list that may be used to hold the 234 * reasons that the provided details are 235 * not acceptable. 236 * @return {@code true} if the provided details are acceptable. 237 */ 238 public abstract boolean areDestinationDetailsAcceptable( 239 String destinationID, 240 List<ASN1OctetString> destinationDetails, 241 List<String> unacceptableReasons); 242 243 244 /** 245 * Create or update a notification destination. 246 * @param destinationID The notification destination ID. 247 * @param destinationDetails The notification destination details. 248 * @throws LDAPException If a problem occurs while creating or updating the 249 * notification destination. 250 */ 251 public abstract void setNotificationDestination( 252 String destinationID, 253 List<ASN1OctetString> destinationDetails) 254 throws LDAPException; 255 256 257 258 /** 259 * Determine whether the provided subscription details are acceptable. 260 * If this method returns true then it is expected that a call to 261 * setNotificationSubscription with the same details will not fail 262 * due to invalid details. 263 * @param destinationID The notification destination ID. 264 * @param subscriptionID The notification subscription ID. 265 * @param subscriptionDetails The notification subscription details. 266 * @param unacceptableReasons A list that may be used to hold the 267 * reasons that the provided details are 268 * not acceptable. 269 * @return {@code true} if the provided details are acceptable. 270 */ 271 public abstract boolean areSubscriptionDetailsAcceptable( 272 String destinationID, 273 String subscriptionID, 274 List<ASN1OctetString> subscriptionDetails, 275 List<String> unacceptableReasons); 276 277 278 279 /** 280 * Create or update a notification subscription. 281 * @param destinationID The notification destination ID. 282 * @param subscriptionID The notification subscription ID. 283 * @param subscriptionDetails The notification destination details. 284 * @throws LDAPException If a problem occurs while creating or updating the 285 * notification subscription. 286 */ 287 public abstract void setNotificationSubscription( 288 String destinationID, 289 String subscriptionID, 290 List<ASN1OctetString> subscriptionDetails) 291 throws LDAPException; 292 293 294 295 /** 296 * Delete a notification destination including any associated 297 * subscriptions. 298 * @param destinationID The notification destination ID. 299 */ 300 public abstract void deleteNotificationDestination(String destinationID); 301 302 303 304 /** 305 * Delete a notification subscription. 306 * @param destinationID The notification destination ID. 307 * @param subscriptionID The notification subscription ID. 308 */ 309 public abstract void deleteNotificationSubscription(String destinationID, 310 String subscriptionID); 311 312 313 314 /** 315 * Determine whether the server running this extension is preferred for the 316 * given notification destination. For example, this method could return 317 * {@code true} if this server is local to the destination. 318 * 319 * @param destinationID The notification destination ID. 320 * 321 * @return {@code true} if this server is preferred for the given 322 * notification destination. 323 */ 324 public abstract boolean isPreferredForDestination(String destinationID); 325 326 327 328 /** 329 * This method is only provided for backwards compatibility. Subclasses 330 * should implement the alternate version of getAddNotificationProperties 331 * below. In which case, this method will not be called. 332 * 333 * Determine if any notifications are required for the provided add 334 * request and return notification properties for each notification 335 * destination that requires a notification. 336 * 337 * @param addRequest The add request that is being processed. 338 * 339 * @return A list of notification properties with an element for each 340 * notification destination that requires a notification. An 341 * empty or {@code null} list indicates that the operation does not 342 * require any notifications. 343 * 344 * @deprecated Override the other getAddNotificationProperties method 345 * instead. 346 */ 347 @Deprecated 348 public List<NotificationProperties> getAddNotificationProperties( 349 final AddRequest addRequest) 350 { 351 throw new UnsupportedOperationException( 352 "One of the getAddNotificationProperties() methods " + 353 "must be implemented"); 354 } 355 356 357 358 /** 359 * Determine if any notifications are required for the provided add 360 * request and return notification properties for each notification 361 * destination that requires a notification. 362 * 363 * @param addRequest The add request that is being processed. 364 * 365 * @param addedEntry The entry that was added. 366 * 367 * @param opContext The operation context for the current operation. 368 * 369 * @return A list of notification properties with an element for each 370 * notification destination that requires a notification. An 371 * empty or {@code null} list indicates that the operation does not 372 * require any notifications. 373 */ 374 @SuppressWarnings("deprecation") 375 public List<NotificationProperties> getAddNotificationProperties( 376 final AddRequest addRequest, 377 final Entry addedEntry, 378 final OperationContext opContext) 379 { 380 return getAddNotificationProperties(addRequest); 381 } 382 383 384 385 /** 386 * This method is only provided for backwards compatibility. Subclasses 387 * should implement the alternate version of getDeleteNotificationProperties 388 * below. In which case, this method will not be called. 389 * 390 * Determine if any notifications are required for the provided delete 391 * request and return notification properties for each notification 392 * destination that requires a notification. 393 * 394 * @param deleteRequest The delete request that is being processed. 395 * 396 * @return A list of notification properties with an element for each 397 * notification destination that requires a notification. An 398 * empty or {@code null} list indicates that the operation does not 399 * require any notifications. 400 * 401 * @deprecated Override the other getDeleteNotificationProperties method 402 * instead. 403 */ 404 @Deprecated 405 public List<NotificationProperties> getDeleteNotificationProperties( 406 final DeleteRequest deleteRequest) 407 { 408 throw new UnsupportedOperationException( 409 "One of the getDeleteNotificationProperties() methods " + 410 "must be implemented"); 411 } 412 413 414 415 /** 416 * Determine if any notifications are required for the provided delete 417 * request and return notification properties for each notification 418 * destination that requires a notification. 419 * 420 * @param deleteRequest The delete request that is being processed. 421 * 422 * @param deletedEntry The entry against which the delete operation was 423 * processed, if available, and {@code null} otherwise. 424 * 425 * @param opContext The operation context for the current operation. 426 * 427 * @return A list of notification properties with an element for each 428 * notification destination that requires a notification. An 429 * empty or {@code null} list indicates that the operation does not 430 * require any notifications. 431 */ 432 @SuppressWarnings("deprecation") 433 public List<NotificationProperties> getDeleteNotificationProperties( 434 final DeleteRequest deleteRequest, 435 final Entry deletedEntry, 436 final OperationContext opContext) 437 { 438 return getDeleteNotificationProperties(deleteRequest); 439 } 440 441 442 443 /** 444 * This method is only provided for backwards compatibility. Subclasses 445 * should implement the alternate version of getModifyNotificationProperties 446 * below. In which case, this method will not be called. 447 * 448 * Determine if any notifications are required for the provided modify 449 * request and return notification properties for each notification 450 * destination that requires a notification. 451 * 452 * @param modifyRequest The modify request that is being processed. 453 * 454 * @return A list of notification properties with an element for each 455 * notification destination that requires a notification. An 456 * empty or {@code null} list indicates that the operation does not 457 * require any notifications. 458 * 459 * @deprecated Override the other getModifyNotificationProperties method 460 * instead. 461 */ 462 @Deprecated 463 public List<NotificationProperties> getModifyNotificationProperties( 464 final ModifyRequest modifyRequest) 465 { 466 throw new UnsupportedOperationException( 467 "One of the getModifyNotificationProperties() methods " + 468 "must be implemented"); 469 } 470 471 472 473 /** 474 * Determine if any notifications are required for the provided modify 475 * request and return notification properties for each notification 476 * destination that requires a notification. 477 * 478 * @param modifyRequest The modify request that is being processed. 479 * 480 * @param oldEntry The entry as it appeared before the modify operation, 481 * or {@code null} if it is not available. 482 * 483 * @param newEntry The entry as it appeared after the modify operation, 484 * or {@code null} if it is not available. 485 * 486 * @param opContext The operation context for the current operation. 487 * 488 * @return A list of notification properties with an element for each 489 * notification destination that requires a notification. An 490 * empty or {@code null} list indicates that the operation does not 491 * require any notifications. 492 */ 493 @SuppressWarnings("deprecation") 494 public List<NotificationProperties> getModifyNotificationProperties( 495 final ModifyRequest modifyRequest, 496 final Entry oldEntry, 497 final Entry newEntry, 498 final OperationContext opContext) 499 { 500 return getModifyNotificationProperties(modifyRequest); 501 } 502 503 504 505 /** 506 * This method is only provided for backwards compatibility. Subclasses 507 * should implement the alternate version of getModifyDNNotificationProperties 508 * below. In which case, this method will not be called. 509 * 510 * Determine if any notifications are required for the provided modify DN 511 * request and return notification properties for each notification 512 * destination that requires a notification. 513 * 514 * @param modifyDNRequest The modify DN request that is being processed. 515 * 516 * @return A list of notification properties with an element for each 517 * notification destination that requires a notification. An 518 * empty or {@code null} list indicates that the operation does not 519 * require any notifications. 520 * 521 * @deprecated Override the other getModifyDNNotificationProperties method 522 * instead. 523 */ 524 @Deprecated 525 public List<NotificationProperties> 526 getModifyDNNotificationProperties( 527 final ModifyDNRequest modifyDNRequest) 528 { 529 throw new UnsupportedOperationException( 530 "One of the getModifyDNNotificationProperties() methods " + 531 "must be implemented"); 532 } 533 534 535 536 /** 537 * Determine if any notifications are required for the provided modify DN 538 * request and return notification properties for each notification 539 * destination that requires a notification. 540 * 541 * @param modifyDNRequest The modify DN request that is being processed. 542 * 543 * @param oldEntry The entry as it appeared before the modify DN 544 * operation, or {@code null} if it is not available. 545 * 546 * @param newEntry The entry as it appeared after the modify DN 547 * operation, or {@code null} if it is not available. 548 * 549 * @param opContext The operation context for the current operation. 550 * 551 * @return A list of notification properties with an element for each 552 * notification destination that requires a notification. An 553 * empty or {@code null} list indicates that the operation does not 554 * require any notifications. 555 */ 556 @SuppressWarnings("deprecation") 557 public List<NotificationProperties> 558 getModifyDNNotificationProperties( 559 final ModifyDNRequest modifyDNRequest, 560 final Entry oldEntry, 561 final Entry newEntry, 562 final OperationContext opContext) 563 { 564 return getModifyDNNotificationProperties(modifyDNRequest); 565 } 566 567 568 569 /** 570 * Attempt delivery of a notification and return a result indicating whether 571 * delivery was successful, and whether delivery should be retried if this 572 * attempt was unsuccessful. Notification manager implementations should be 573 * careful not to return {@code RETRY} when all future attempts of the 574 * notification delivery will fail, e.g. a remote change failing due to a 575 * schema violation. If the extension can determine that the remote service 576 * is completely unavailable, then it is fine to continue to retry, but if 577 * the service is available and only failing for some changes, then 578 * continuing to retry is dangerous. There are methods on the {@code 579 * Notification} interface to determine how many attempts have been made 580 * and for how long attempts have been made. Above some threshold, the 581 * extension should return {@code FAILURE} instead of {@code RETRY}. 582 * <br> 583 * <br> 584 * This method must be written to be thread-safe because notifications of 585 * changes that do not depend on each other are processed in parallel (e.g. 586 * when the changes affect unrelated entries). 587 * 588 * @param notification The notification to be delivered. 589 * 590 * @return A delivery result indicating whether delivery was successful, 591 * and whether delivery should be retried if this attempt was 592 * unsuccessful. 593 */ 594 public abstract NotificationDeliveryResult attemptDelivery( 595 Notification notification); 596 597 598 599 /** 600 * {@inheritDoc} 601 */ 602 public boolean isConfigurationAcceptable( 603 final NotificationManagerConfig config, 604 final ArgumentParser parser, 605 final List<String> unacceptableReasons) 606 { 607 // No extended validation will be performed by default. 608 return true; 609 } 610 611 612 613 /** 614 * {@inheritDoc} 615 */ 616 public ResultCode applyConfiguration( 617 final NotificationManagerConfig config, 618 final ArgumentParser parser, 619 final List<String> adminActionsRequired, 620 final List<String> messages) 621 { 622 // By default, no configuration changes will be applied. If there are any 623 // arguments, then add an admin action message indicating that the extension 624 // needs to be restarted for any changes to take effect. 625 if (! parser.getNamedArguments().isEmpty()) 626 { 627 adminActionsRequired.add( 628 "No configuration change has actually been applied. The new " + 629 "configuration will not take effect until this notification " + 630 "manager is disabled and re-enabled or until the server is " + 631 "restarted."); 632 } 633 634 return ResultCode.SUCCESS; 635 } 636 637 638 639 /** 640 * Performs any cleanup which may be necessary when this notification manager 641 * is to be taken out of service. 642 */ 643 public void finalizeNotificationManager() 644 { 645 // No implementation is required. 646 } 647 648 649 650 /** 651 * {@inheritDoc} 652 */ 653 public Map<List<String>,String> getExamplesArgumentSets() 654 { 655 return Collections.emptyMap(); 656 } 657}