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}