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.proxy.api;
028
029
030
031import java.util.Collections;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036import com.unboundid.directory.sdk.common.internal.ExampleUsageProvider;
037import com.unboundid.directory.sdk.common.internal.Reconfigurable;
038import com.unboundid.directory.sdk.common.internal.UnboundIDExtension;
039import com.unboundid.directory.sdk.common.operation.ExtendedRequest;
040import com.unboundid.directory.sdk.common.types.OperationContext;
041import com.unboundid.directory.sdk.proxy.config.
042            ProxiedExtendedOperationHandlerConfig;
043import com.unboundid.directory.sdk.proxy.internal.DirectoryProxyServerExtension;
044import com.unboundid.directory.sdk.proxy.types.BackendSet;
045import com.unboundid.directory.sdk.proxy.types.EntryBalancingRequestProcessor;
046import com.unboundid.directory.sdk.proxy.types.ProxyingRequestProcessor;
047import com.unboundid.directory.sdk.proxy.types.ProxyServerContext;
048import com.unboundid.ldap.sdk.ExtendedResult;
049import com.unboundid.ldap.sdk.LDAPException;
050import com.unboundid.ldap.sdk.ResultCode;
051import com.unboundid.util.Extensible;
052import com.unboundid.util.ObjectPair;
053import com.unboundid.util.ThreadSafety;
054import com.unboundid.util.ThreadSafetyLevel;
055import com.unboundid.util.args.ArgumentException;
056import com.unboundid.util.args.ArgumentParser;
057
058
059
060/**
061 * This class defines an API that must be implemented by extensions which are
062 * used to forward extended operations to one or more backend servers.  This
063 * API makes it possible to select the backend set(s) to which the operation
064 * should be forwarded, and to aggregate the responses into a single response to
065 * return to the client.
066 * <BR>
067 * <H2>Configuring Proxied Extended Operation Handlers</H2>
068 * In order to configure a proxied extended operation handler created using this
069 * API, use a command like:
070 * <PRE>
071 *      dsconfig create-extended-operation-handler \
072 *           --handler-name "<I>{handler-name}</I>" \
073 *           --type third-party-proxied \
074 *           --set enabled:true \
075 *           --set "extension-class:<I>{class-name}</I>" \
076 *           --set "extension-argument:<I>{name=value}</I>"
077 * </PRE>
078 * where "<I>{handler-name}</I>" is the name to use for the proxied extended
079 * operation handler instance, "<I>{class-name}</I>" is the fully-qualified name
080 * of the Java class that extends {@code
081 * com.unboundid.directory.sdk.proxy.api.ProxiedExtendedOperationHandler}, and
082 * "<I>{name=value}</I>" represents name-value pairs for any arguments to
083 * provide to the proxied extended operation handler.  If multiple arguments
084 * should be provided to the proxied extended operation handler, then the
085 * "<CODE>--set extension-argument:<I>{name=value}</I></CODE>" option should be
086 * provided multiple times.
087 */
088@Extensible()
089@DirectoryProxyServerExtension(appliesToLocalContent=false,
090     appliesToRemoteContent=true)
091@ThreadSafety(level=ThreadSafetyLevel.INTERFACE_THREADSAFE)
092public abstract class ProxiedExtendedOperationHandler
093       implements UnboundIDExtension,
094                  Reconfigurable<ProxiedExtendedOperationHandlerConfig>,
095                  ExampleUsageProvider
096{
097  /**
098   * Creates a new instance of this proxied extended operation handler.  All
099   * proxied extended operation handler implementations must include a default
100   * constructor, but any initialization should generally be done in the
101   * {@code initializeProxiedExtendedOperationHandler} method.
102   */
103  public ProxiedExtendedOperationHandler()
104  {
105    // No implementation is required.
106  }
107
108
109
110  /**
111   * {@inheritDoc}
112   */
113  @Override()
114  public abstract String getExtensionName();
115
116
117
118  /**
119   * {@inheritDoc}
120   */
121  @Override()
122  public abstract String[] getExtensionDescription();
123
124
125
126  /**
127   * {@inheritDoc}
128   */
129  @Override()
130  public void defineConfigArguments(final ArgumentParser parser)
131         throws ArgumentException
132  {
133    // No arguments will be allowed by default.
134  }
135
136
137
138  /**
139   * Initializes this proxied extended operation handler.
140   *
141   * @param  serverContext  A handle to the server context for the server in
142   *                        which this extension is running.
143   * @param  config         The general configuration for this extended
144   *                        operation handler.
145   * @param  parser         The argument parser which has been initialized from
146   *                        the configuration for this extended operation
147   *                        handler.
148   *
149   * @throws  LDAPException  If a problem occurs while initializing this
150   *                         extended operation handler.
151   */
152  public void initializeProxiedExtendedOperationHandler(
153                   final ProxyServerContext serverContext,
154                   final ProxiedExtendedOperationHandlerConfig config,
155                   final ArgumentParser parser)
156         throws LDAPException
157  {
158    // No initialization will be performed by default.
159  }
160
161
162
163  /**
164   * {@inheritDoc}
165   */
166  @Override()
167  public boolean isConfigurationAcceptable(
168                      final ProxiedExtendedOperationHandlerConfig config,
169                      final ArgumentParser parser,
170                      final List<String> unacceptableReasons)
171  {
172    // No extended validation will be performed by default.
173    return true;
174  }
175
176
177
178  /**
179   * {@inheritDoc}
180   */
181  @Override()
182  public ResultCode applyConfiguration(
183                         final ProxiedExtendedOperationHandlerConfig config,
184                         final ArgumentParser parser,
185                         final List<String> adminActionsRequired,
186                         final List<String> messages)
187  {
188    // By default, no configuration changes will be applied.  If there are any
189    // arguments, then add an admin action message indicating that the extension
190    // needs to be restarted for any changes to take effect.
191    if (! parser.getNamedArguments().isEmpty())
192    {
193      adminActionsRequired.add(
194           "No configuration change has actually been applied.  The new " +
195                "configuration will not take effect until this proxied " +
196                "extended operation handler is disabled and re-enabled or " +
197                "until the server is restarted.");
198    }
199
200    return ResultCode.SUCCESS;
201  }
202
203
204
205  /**
206   * Performs any cleanup which may be necessary when this proxied extended
207   * operation handler is to be taken out of service.
208   */
209  public void finalizeProxiedExtendedOperationHandler()
210  {
211    // No implementation is required.
212  }
213
214
215
216  /**
217   * Retrieves the name of the extended operation with the provided OID.
218   *
219   * @param  oid  The OID of the extended operation for which to retrieve the
220   *              corresponding name.
221   *
222   * @return  The name of the extended operation with the specified OID, or
223   *          {@code null} if the specified OID is not recognized by this
224   *          proxied extended operation handler.
225   */
226  public abstract String getExtendedOperationName(final String oid);
227
228
229
230  /**
231   * Retrieves the OIDs of the extended operation types supported by this
232   * proxied extended operation handler.
233   *
234   * @return  The OIDs of the extended operation types supported by this proxied
235   *          extended operation handler.  It must not be {@code null} or
236   *          empty, and the contents of the set returned must not change over
237   *          the life of this proxied extended operation handler.
238   */
239  public abstract Set<String> getSupportedExtensions();
240
241
242
243  /**
244   * Retrieves the OIDs of any controls supported by this proxied extended
245   * operation handler.
246   *
247   * @return  The OIDs of any controls supported by this proxied extended
248   *          operation handler.  It may be {@code null} or empty if this
249   *          proxied extended operation handler does not support any controls.
250   */
251  public Set<String> getSupportedControls()
252  {
253    return Collections.emptySet();
254  }
255
256
257
258  /**
259   * Retrieves the OIDs of any features supported by this proxied extended
260   * operation handler that should be advertised in the server root DSE.
261   *
262   * @return  The OIDs of any features supported by this proxied extended
263   *          operation handler.  It may be {@code null} or empty if this
264   *          proxied extended operation handler does not support any features.
265   */
266  public Set<String> getSupportedFeatures()
267  {
268    return Collections.emptySet();
269  }
270
271
272
273  /**
274   * Selects the entry-balancing backend set(s) to which the provided extended
275   * request should be forwarded.  This method will only be invoked for
276   * operations in which the requester's client connection policy includes one
277   * or more subtree views which reference an entry-balancing request processor.
278   * <BR><BR>
279   * This method returns two groups of backend sets, with the first representing
280   * an initial guess (e.g., based on information obtained from the
281   * entry-balancing global index), and the second representing a fallback if
282   * the initial guess was found to be incorrect.
283   * <BR><BR>
284   * If it can be determined that no backend sets associated with the provided
285   * entry-balancing request processor should be used to process the extended
286   * operation, then the object returned may have both elements set to
287   * {@code null} or empty sets.  If it is possible to definitively determine
288   * the set of backend sets to which the operation should be forwarded and no
289   * fallback option is required, then the first element of the returned object
290   * should be populated with a non-empty set and the second element should be
291   * {@code null} or empty.
292   *
293   * @param  operationContext  The operation context for the extended operation.
294   * @param  request           The extended request to be processed.
295   * @param  requestProcessor  The entry-balancing request processor in which
296   *                           the extended operation should be processed.
297   *
298   * @return  The set of backend sets to which the request should be forwarded.
299   *          It may be {@code null} (or it may be an {@code ObjectPair} with
300   *          both elements empty or {@code null}) if the request should not be
301   *          forwarded to any backend set for the entry-balancing request
302   *          processor.
303   *
304   * @throws  LDAPException  If the request should not be forwarded to any of
305   *                         the backend sets for the provided entry-balancing
306   *                         request processor, and the result contained in the
307   *                         exception should be used instead.
308   */
309  public abstract ObjectPair<Set<BackendSet>,Set<BackendSet>> selectBackendSets(
310                       final OperationContext operationContext,
311                       final ExtendedRequest request,
312                       final EntryBalancingRequestProcessor requestProcessor)
313         throws LDAPException;
314
315
316
317  /**
318   * Obtains the extended result that should be used as a result of processing
319   * an operation in one or more entry-balanced backend sets, or throws an
320   * exception to indicate that the request should instead be forwarded to the
321   * fallback server set(s).
322   * <BR><BR>
323   * This method will only be invoked for cases in which the
324   * {@link #selectBackendSets} method indicates that multiple backend sets
325   * should be accessed in the course of processing an extended request.
326   *
327   * @param  operationContext   The operation context for the extended
328   *                            operation.
329   * @param  request            The extended request that was processed.
330   * @param  requestProcessor   The entry-balancing request processor in which
331   *                            the extended operation was processed.
332   * @param  results            A list of the extended results obtained from
333   *                            processing the operation in the selected set of
334   *                            backend sets, with each result paired with
335   *                            information about the backend set from which it
336   *                            was obtained.
337   * @param  fallbackAvailable  Indicates whether a fallback group of backend
338   *                            sets is available and may be used as a second
339   *                            attempt at processing the operation if the
340   *                            result from the initial attempt is not
341   *                            acceptable.
342   *
343   * @return  An extended result that represents the merged result from the
344   *          provided list of results.  It must not be {@code null}.
345   *
346   * @throws  LDAPException  To indicate that the initial set of results was not
347   *                         acceptable and that the operation should instead be
348   *                         forwarded to the fallback group of backend sets.
349   *                         If no fallback set of results is available, then an
350   *                         extended result will be generated from the content
351   *                         of this exception.
352   */
353  public abstract ExtendedResult mergeEntryBalancedResults(
354              final OperationContext operationContext,
355              final ExtendedRequest request,
356              final EntryBalancingRequestProcessor requestProcessor,
357              final List<ObjectPair<ExtendedResult,BackendSet>> results,
358              final boolean fallbackAvailable)
359         throws LDAPException;
360
361
362
363  /**
364   * Indicates whether the provided extended request should be forwarded to one
365   * of the servers associated with the provided proxying request processor.
366   * Note that this method will not be invoked for proxying request processors
367   * associated with an entry-balancing request processor.
368   *
369   * @param  operationContext  The operation context for the extended operation.
370   * @param  request           The extended request to be processed.
371   * @param  requestProcessor  The proxying request processor for which to
372   *                           make the determination.
373   *
374   * @return  {@code true} if the extended request should be forwarded to one of
375   *          the servers associated with the proxying request processor, or
376   *          {@code false} if not.
377   *
378   * @throws  LDAPException  If the request should not be forwarded to a
379   *                         backend server associated with the proxying request
380   *                         processor, but the result contained in the
381   *                         exception should be used instead.
382   */
383  public abstract boolean shouldForward(final OperationContext operationContext,
384                               final ExtendedRequest request,
385                               final ProxyingRequestProcessor requestProcessor)
386         throws LDAPException;
387
388
389
390  /**
391   * Creates the final extended result to return to the client from the provided
392   * list of results obtained from the set of entry-balanced and/or proxying
393   * request processors to which the request was forwarded.
394   *
395   * @param  operationContext   The operation context for the extended
396   *                            operation.
397   * @param  request            The extended request that was processed.
398   * @param  results            The results from all request processors to which
399   *                            the request was forwarded.  It may be empty if
400   *                            the request was not forwarded to any backend
401   *                            servers, in which case this method must
402   *                            construct an appropriate result.  It may have
403   *                            only a single element if the request was only
404   *                            forwarded to one server, and in many cases it
405   *                            may be desirable to simply use that result as
406   *                            the final result.  It may also have multiple
407   *                            elements if the request was forwarded to
408   *                            multiple backend servers, in which case this
409   *                            method must determine whether to return one of
410   *                            them to the client, or to construct a new result
411   *                            to return instead.
412   *
413   * @return  The final extended result to be returned to the client.
414   */
415  public abstract ExtendedResult createFinalResult(
416                                      final OperationContext operationContext,
417                                      final ExtendedRequest request,
418                                      final List<ExtendedResult> results);
419
420
421
422  /**
423   * {@inheritDoc}
424   */
425  @Override()
426  public Map<List<String>,String> getExamplesArgumentSets()
427  {
428    return Collections.emptyMap();
429  }
430}