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 * Copyright 2014-2017 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.ds.config.NotificationManagerConfig; 038import com.unboundid.directory.sdk.ds.internal.DirectoryServerExtension; 039import com.unboundid.directory.sdk.ds.types.DirectoryServerContext; 040import com.unboundid.directory.sdk.ds.types.Notification; 041import com.unboundid.directory.sdk.ds.types.NotificationDeliveryResult; 042import com.unboundid.directory.sdk.ds.types.NotificationProperties; 043import com.unboundid.ldap.sdk.LDAPException; 044import com.unboundid.ldap.sdk.ResultCode; 045import com.unboundid.ldap.sdk.unboundidds.extensions. 046 NotificationDestinationDetails; 047import com.unboundid.util.Extensible; 048import com.unboundid.util.ThreadSafety; 049import com.unboundid.util.ThreadSafetyLevel; 050import com.unboundid.util.args.ArgumentException; 051import com.unboundid.util.args.ArgumentParser; 052 053import java.util.Collections; 054import java.util.List; 055import java.util.Map; 056 057 058 059/** 060 * This class defines an API that must be implemented by extensions which 061 * wish to deliver notification of changes of interest processed within the 062 * server. 063 * <BR> 064 * <H2>Implementing a Notification Manager</H2> 065 * <H3>Subscription management</H3> 066 * A notification manager has one or more notification destinations which 067 * determine where notifications are to be delivered. Each notification 068 * destination has one or more notification subscriptions which determine 069 * which changes should result in a notification for that destination. 070 * Destinations and subscriptions each have associated details whose syntax 071 * is specific to the notification manager implementation. 072 * The notification manager implementation must implement the following 073 * methods to keep track of destinations, subscriptions and their associated 074 * details: 075 * <UL> 076 * <LI>initializeNotificationDestinations</LI> 077 * <LI>areDestinationDetailsAcceptable</LI> 078 * <LI>setNotificationDestination</LI> 079 * <LI>deleteNotificationDestination</LI> 080 * <LI>areSubscriptionDetailsAcceptable</LI> 081 * <LI>setNotificationSubscription</LI> 082 * <LI>deleteNotificationSubscription</LI> 083 * </UL> 084 * <H3>Matching changes with subscriptions</H3> 085 * A notification manager must implement the following methods (one for each 086 * type of LDAP operation) to determine whether a change matches any 087 * notification subscriptions. The notification manager may provide arbitrary 088 * properties that it wishes to be included with a notification: 089 * <UL> 090 * <LI>getAddNotificationProperties</LI> 091 * <LI>getDeleteNotificationProperties</LI> 092 * <LI>getModifyNotificationProperties</LI> 093 * <LI>getModifyDNNotificationProperties</LI> 094 * </UL> 095 * <H3>Delivering notifications</H3> 096 * A notification manager must implement the following method to attempt 097 * delivery of a notification to a destination: 098 * <UL> 099 * <LI>attemptDelivery</LI> 100 * </UL> 101 * <BR> 102 * <H2>Configuring a Notification Manager</H2> 103 * The LDAP changelog must first be enabled on each server that is to deliver 104 * notifications: 105 * <PRE> 106 * dsconfig set-backend-prop \ 107 * --backend-name changelog \ 108 * --set enabled:true 109 * </PRE> 110 * In order to configure a notification manager created using this API, 111 * use a command like: 112 * <PRE> 113 * dsconfig create-notification-manager \ 114 * --manager-name "<I>{manager-ID}</I>" \ 115 * --type third-party \ 116 * --set enabled:true \ 117 * --set "extension-class:<I>{class-name}</I>" \ 118 * --set "subscription-base-dn:<I>{subscription-base-dn}</I>" \ 119 * --set "extension-argument:<I>{name=value}</I>" 120 * </PRE> 121 * where "<I>{manager-ID}</I>" is the ID to use for the notification 122 * manager instance, "<I>{subscription-base-dn}</I>" is the base DN where 123 * subscription information for this notification manager is to be stored, 124 * "<I>{class-name}</I>" is the fully-qualified name of the Java class that 125 * extends 126 * {@code com.unboundid.directory.sdk.ds.api.NotificationManager}, and 127 * "<I>{name=value}</I>" represents name-value pairs for any arguments to 128 * provide to the notification manager. If multiple arguments should be 129 * provided to the notification manager, then the 130 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be 131 * provided multiple times. 132 * <BR> 133 * <BR> 134 * The notification manager must also be associated with a backend using a 135 * command like: 136 * <PRE> 137 * dsconfig set-backend-prop \ 138 * --backend-name "<I>userRoot</I>" \ 139 * --set "notification-manager:<I>{manager-ID}</I>" 140 * </PRE> 141 * The <I>{subscription-base-dn}</I> of the notification manager must be 142 * within the scope of the <I>base-dn</I> of the backend. 143 * <BR> 144 * <BR> 145 * The above configuration should be made on each server that holds a replica 146 * of the backend data to enable delivery of notifications for changes to that 147 * data. 148 */ 149@Extensible() 150@DirectoryServerExtension() 151@ThreadSafety(level= ThreadSafetyLevel.INTERFACE_THREADSAFE) 152public abstract class NotificationManager 153 implements UnboundIDExtension, 154 Reconfigurable<NotificationManagerConfig>, 155 ExampleUsageProvider 156{ 157 /** 158 * {@inheritDoc} 159 */ 160 public abstract String getExtensionName(); 161 162 163 164 /** 165 * {@inheritDoc} 166 */ 167 public abstract String[] getExtensionDescription(); 168 169 170 171 /** 172 * {@inheritDoc} 173 */ 174 public void defineConfigArguments(final ArgumentParser parser) 175 throws ArgumentException 176 { 177 // No arguments will be allowed by default. 178 } 179 180 181 182 /** 183 * Initializes this notification manager. 184 * 185 * @param serverContext A handle to the server context for the server in 186 * which this extension is running. 187 * @param config The general configuration for this notification 188 * manager. 189 * @param parser The argument parser which has been initialized from 190 * the configuration for this notification manager. 191 * 192 * @throws LDAPException If a problem occurs while initializing this 193 * notification manager. 194 */ 195 public void initializeNotificationManager( 196 final DirectoryServerContext serverContext, 197 final NotificationManagerConfig config, 198 final ArgumentParser parser) 199 throws LDAPException 200 { 201 // No initialization will be performed by default. 202 } 203 204 205 206 /** 207 * Initializes or re-initializes the notification destinations for this 208 * notification manager. This will be called once after the notification 209 * manager has been initialized and any time subsequently. 210 * 211 * @param destinations The list of defined notification destinations and 212 * their associated subscriptions. 213 * 214 * @throws LDAPException If a problem occurs while initializing the 215 * notification destinations for this notification 216 * manager. 217 */ 218 public abstract void initializeNotificationDestinations( 219 List<NotificationDestinationDetails> destinations) 220 throws LDAPException; 221 222 223 224 /** 225 * Determine whether the provided destination details are acceptable. 226 * If this method returns true then it is expected that a call to 227 * {@code setNotificationDestination} with the same details will not 228 * fail due to invalid details. 229 * @param destinationID The notification destination ID. 230 * @param destinationDetails The notification destination details. 231 * @param unacceptableReasons A list that may be used to hold the 232 * reasons that the provided details are 233 * not acceptable. 234 * @return {@code true} if the provided details are acceptable. 235 */ 236 public abstract boolean areDestinationDetailsAcceptable( 237 String destinationID, 238 List<ASN1OctetString> destinationDetails, 239 List<String> unacceptableReasons); 240 241 242 /** 243 * Create or update a notification destination. 244 * @param destinationID The notification destination ID. 245 * @param destinationDetails The notification destination details. 246 * @throws LDAPException If a problem occurs while creating or updating the 247 * notification destination. 248 */ 249 public abstract void setNotificationDestination( 250 String destinationID, 251 List<ASN1OctetString> destinationDetails) 252 throws LDAPException; 253 254 255 256 /** 257 * Determine whether the provided subscription details are acceptable. 258 * If this method returns true then it is expected that a call to 259 * setNotificationSubscription with the same details will not fail 260 * due to invalid details. 261 * @param destinationID The notification destination ID. 262 * @param subscriptionID The notification subscription ID. 263 * @param subscriptionDetails The notification subscription details. 264 * @param unacceptableReasons A list that may be used to hold the 265 * reasons that the provided details are 266 * not acceptable. 267 * @return {@code true} if the provided details are acceptable. 268 */ 269 public abstract boolean areSubscriptionDetailsAcceptable( 270 String destinationID, 271 String subscriptionID, 272 List<ASN1OctetString> subscriptionDetails, 273 List<String> unacceptableReasons); 274 275 276 277 /** 278 * Create or update a notification subscription. 279 * @param destinationID The notification destination ID. 280 * @param subscriptionID The notification subscription ID. 281 * @param subscriptionDetails The notification destination details. 282 * @throws LDAPException If a problem occurs while creating or updating the 283 * notification subscription. 284 */ 285 public abstract void setNotificationSubscription( 286 String destinationID, 287 String subscriptionID, 288 List<ASN1OctetString> subscriptionDetails) 289 throws LDAPException; 290 291 292 293 /** 294 * Delete a notification destination including any associated 295 * subscriptions. 296 * @param destinationID The notification destination ID. 297 */ 298 public abstract void deleteNotificationDestination(String destinationID); 299 300 301 302 /** 303 * Delete a notification subscription. 304 * @param destinationID The notification destination ID. 305 * @param subscriptionID The notification subscription ID. 306 */ 307 public abstract void deleteNotificationSubscription(String destinationID, 308 String subscriptionID); 309 310 311 312 /** 313 * Determine whether the server running this extension is preferred for the 314 * given notification destination. For example, this method could return 315 * {@code true} if this server is local to the destination. 316 * 317 * @param destinationID The notification destination ID. 318 * 319 * @return {@code true} if this server is preferred for the given 320 * notification destination. 321 */ 322 public abstract boolean isPreferredForDestination(String destinationID); 323 324 325 326 /** 327 * Determine if any notifications are required for the provided add 328 * request and return notification properties for each notification 329 * destination that requires a notification. 330 * 331 * @param addRequest The add request that is being processed. 332 * 333 * @return A list of notification properties with an element for each 334 * notification destination that requires a notification. An 335 * empty or {@code null} list indicates that the operation does not 336 * require any notifications. 337 */ 338 public abstract List<NotificationProperties> getAddNotificationProperties( 339 AddRequest addRequest); 340 341 342 343 /** 344 * Determine if any notifications are required for the provided delete 345 * request and return notification properties for each notification 346 * destination that requires a notification. 347 * 348 * @param deleteRequest The delete request that is being processed. 349 * 350 * @return A list of notification properties with an element for each 351 * notification destination that requires a notification. An 352 * empty or {@code null} list indicates that the operation does not 353 * require any notifications. 354 */ 355 public abstract List<NotificationProperties> getDeleteNotificationProperties( 356 DeleteRequest deleteRequest); 357 358 359 360 /** 361 * Determine if any notifications are required for the provided modify 362 * request and return notification properties for each notification 363 * destination that requires a notification. 364 * 365 * @param modifyRequest The modify request that is being processed. 366 * 367 * @return A list of notification properties with an element for each 368 * notification destination that requires a notification. An 369 * empty or {@code null} list indicates that the operation does not 370 * require any notifications. 371 */ 372 public abstract List<NotificationProperties> getModifyNotificationProperties( 373 ModifyRequest modifyRequest); 374 375 376 377 /** 378 * Determine if any notifications are required for the provided modify DN 379 * request and return notification properties for each notification 380 * destination that requires a notification. 381 * 382 * @param modifyDNRequest The modify DN request that is being processed. 383 * 384 * @return A list of notification properties with an element for each 385 * notification destination that requires a notification. An 386 * empty or {@code null} list indicates that the operation does not 387 * require any notifications. 388 */ 389 public abstract List<NotificationProperties> 390 getModifyDNNotificationProperties( 391 ModifyDNRequest modifyDNRequest); 392 393 394 395 /** 396 * Attempt delivery of a notification and return a result indicating whether 397 * delivery was successful, and whether delivery should be retried if this 398 * attempt was unsuccessful. Notification manager implementations should be 399 * careful not to return {@code RETRY} when all future attempts of the 400 * notification delivery will fail, e.g. a remote change failing due to a 401 * schema violation. If the extension can determine that the remote service 402 * is completely unavailable, then it is fine to continue to retry, but if 403 * the service is available and only failing for some changes, then 404 * continuing to retry is dangerous. There are methods on the {@code 405 * Notification} interface to determine how many attempts have been made 406 * and for how long attempts have been made. Above some threshold, the 407 * extension should return {@code FAILURE} instead of {@code RETRY}. 408 * <br> 409 * <br> 410 * This method must be written to be thread-safe because notifications of 411 * changes that do not depend on each other are processed in parallel (e.g. 412 * when the changes affect unrelated entries). 413 * 414 * @param notification The notification to be delivered. 415 * 416 * @return A delivery result indicating whether delivery was successful, 417 * and whether delivery should be retried if this attempt was 418 * unsuccessful. 419 */ 420 public abstract NotificationDeliveryResult attemptDelivery( 421 Notification notification); 422 423 424 425 /** 426 * {@inheritDoc} 427 */ 428 public boolean isConfigurationAcceptable( 429 final NotificationManagerConfig config, 430 final ArgumentParser parser, 431 final List<String> unacceptableReasons) 432 { 433 // No extended validation will be performed by default. 434 return true; 435 } 436 437 438 439 /** 440 * {@inheritDoc} 441 */ 442 public ResultCode applyConfiguration( 443 final NotificationManagerConfig config, 444 final ArgumentParser parser, 445 final List<String> adminActionsRequired, 446 final List<String> messages) 447 { 448 // By default, no configuration changes will be applied. If there are any 449 // arguments, then add an admin action message indicating that the extension 450 // needs to be restarted for any changes to take effect. 451 if (! parser.getNamedArguments().isEmpty()) 452 { 453 adminActionsRequired.add( 454 "No configuration change has actually been applied. The new " + 455 "configuration will not take effect until this notification " + 456 "manager is disabled and re-enabled or until the server is " + 457 "restarted."); 458 } 459 460 return ResultCode.SUCCESS; 461 } 462 463 464 465 /** 466 * Performs any cleanup which may be necessary when this notification manager 467 * is to be taken out of service. 468 */ 469 public void finalizeNotificationManager() 470 { 471 // No implementation is required. 472 } 473 474 475 476 /** 477 * {@inheritDoc} 478 */ 479 public Map<List<String>,String> getExamplesArgumentSets() 480 { 481 return Collections.emptyMap(); 482 } 483}