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-2018 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}