001/*
002 * Copyright 2016-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2016-2024 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2016-2024 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.ldap.listener;
037
038
039
040import java.util.List;
041
042import com.unboundid.ldap.protocol.AbandonRequestProtocolOp;
043import com.unboundid.ldap.protocol.AddRequestProtocolOp;
044import com.unboundid.ldap.protocol.BindRequestProtocolOp;
045import com.unboundid.ldap.protocol.CompareRequestProtocolOp;
046import com.unboundid.ldap.protocol.DeleteRequestProtocolOp;
047import com.unboundid.ldap.protocol.ExtendedRequestProtocolOp;
048import com.unboundid.ldap.protocol.LDAPMessage;
049import com.unboundid.ldap.protocol.ModifyRequestProtocolOp;
050import com.unboundid.ldap.protocol.ModifyDNRequestProtocolOp;
051import com.unboundid.ldap.protocol.SearchRequestProtocolOp;
052import com.unboundid.ldap.sdk.Control;
053import com.unboundid.ldap.sdk.LDAPException;
054import com.unboundid.util.FixedRateBarrier;
055import com.unboundid.util.NotMutable;
056import com.unboundid.util.NotNull;
057import com.unboundid.util.Nullable;
058import com.unboundid.util.ThreadSafety;
059import com.unboundid.util.ThreadSafetyLevel;
060import com.unboundid.util.Validator;
061
062
063
064/**
065 * This class provides an implementation of an LDAP listener request handler
066 * that can be used to apply rate limiting to client requests.  It uses one or
067 * more {@link FixedRateBarrier} instances to enforce the rate limiting, and
068 * provides the ability to control rate limiting on a per-operation-type basis.
069 */
070@NotMutable()
071@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
072public final class RateLimiterRequestHandler
073       extends LDAPListenerRequestHandler
074{
075  // The rate limiters that will be used for each type of operation.
076  @Nullable private final FixedRateBarrier abandonRateLimiter;
077  @Nullable private final FixedRateBarrier addRateLimiter;
078  @Nullable private final FixedRateBarrier bindRateLimiter;
079  @Nullable private final FixedRateBarrier compareRateLimiter;
080  @Nullable private final FixedRateBarrier deleteRateLimiter;
081  @Nullable private final FixedRateBarrier extendedRateLimiter;
082  @Nullable private final FixedRateBarrier modifyRateLimiter;
083  @Nullable private final FixedRateBarrier modifyDNRateLimiter;
084  @Nullable private final FixedRateBarrier searchRateLimiter;
085
086  // The downstream request handler that will be used to process the requests
087  // after any appropriate rate limiting has been performed.
088  @NotNull private final LDAPListenerRequestHandler downstreamRequestHandler;
089
090
091
092  /**
093   * Creates a new rate limiter request handler that will limit the rate of
094   * operations to the specified maximum number per second.  The rate limiting
095   * will be enforced for all types of operations except abandon and unbind.
096   * No rate limiting will be enforced for abandon or unbind operations.
097   *
098   * @param  downstreamRequestHandler  The downstream request handler that will
099   *                                   be used to actually process the requests
100   *                                   after any appropriate rate limiting has
101   *                                   been performed.  It must not be
102   *                                   {@code null}.
103   * @param  maxPerSecond              The maximum number of operations that
104   *                                   will be allowed per second, across all
105   *                                   types of operations except abandon and
106   *                                   unbind.  It must be greater than zero.
107   */
108  public RateLimiterRequestHandler(
109       @NotNull final LDAPListenerRequestHandler downstreamRequestHandler,
110       final int maxPerSecond)
111  {
112    Validator.ensureNotNull(downstreamRequestHandler);
113    Validator.ensureTrue(maxPerSecond > 0);
114
115    this.downstreamRequestHandler = downstreamRequestHandler;
116
117    final FixedRateBarrier rateLimiter =
118         new FixedRateBarrier(1000L, maxPerSecond);
119
120    abandonRateLimiter  = null;
121    addRateLimiter      = rateLimiter;
122    bindRateLimiter     = rateLimiter;
123    compareRateLimiter  = rateLimiter;
124    deleteRateLimiter   = rateLimiter;
125    extendedRateLimiter = rateLimiter;
126    modifyRateLimiter   = rateLimiter;
127    modifyDNRateLimiter = rateLimiter;
128    searchRateLimiter   = rateLimiter;
129  }
130
131
132
133  /**
134   * Creates a new rate limiter request handler that will use the provided
135   * {@link FixedRateBarrier} to perform rate limiting for all types of
136   * operations except abandon and unbind.  No rate limiting will be enforced
137   * for abandon or unbind operations.
138   *
139   * @param  downstreamRequestHandler  The downstream request handler that will
140   *                                   be used to actually process the requests
141   *                                   after any appropriate rate limiting has
142   *                                   been performed.  It must not be
143   *                                   {@code null}.
144   * @param  rateLimiter               The fixed-rate barrier that will be used
145   *                                   to achieve the rate limiting for all
146   *                                   types of operations except abandon and
147   *                                   unbind.  It may be {@code null} if no
148   *                                   rate limiting should be performed for any
149   *                                   operation types.
150   */
151  public RateLimiterRequestHandler(
152       @NotNull final LDAPListenerRequestHandler downstreamRequestHandler,
153       @Nullable final FixedRateBarrier rateLimiter)
154  {
155    this(downstreamRequestHandler, null, rateLimiter, rateLimiter, rateLimiter,
156         rateLimiter, rateLimiter, rateLimiter, rateLimiter, rateLimiter);
157  }
158
159
160
161  /**
162   * Creates a new rate limiter request handler that can use the provided
163   * {@link FixedRateBarrier} instances to perform rate limiting for different
164   * types of operations.  The same barrier instance can be provided for
165   * multiple operation types if performance for those operations should be
166   * limited in aggregate rather than individually (e.g., if you don't want the
167   * total combined rate of search and modify operations to exceed a given
168   * threshold, then you could provide the same barrier instance for the
169   * {@code modifyRateLimiter} and {@code searchRateLimiter} arguments).
170   *
171   * @param  downstreamRequestHandler  The downstream request handler that will
172   *                                   be used to actually process the requests
173   *                                   after any appropriate rate limiting has
174   *                                   been performed.  It must not be
175   *                                   {@code null}.
176   * @param  abandonRateLimiter        The fixed-rate barrier to use when
177   *                                   processing abandon operations.  It may be
178   *                                   {@code null} if no rate limiting should
179   *                                   be enforced for abandon operations.
180   * @param  addRateLimiter            The fixed-rate barrier to use when
181   *                                   processing add operations.  It may be
182   *                                   {@code null} if no rate limiting should
183   *                                   be enforced for add operations.
184   * @param  bindRateLimiter           The fixed-rate barrier to use when
185   *                                   processing bind operations.  It may be
186   *                                   {@code null} if no rate limiting should
187   *                                   be enforced for bind operations.
188   * @param  compareRateLimiter        The fixed-rate barrier to use when
189   *                                   processing compare operations.  It may be
190   *                                   {@code null} if no rate limiting should
191   *                                   be enforced for compare operations.
192   * @param  deleteRateLimiter         The fixed-rate barrier to use when
193   *                                   processing delete operations.  It may be
194   *                                   {@code null} if no rate limiting should
195   *                                   be enforced for delete operations.
196   * @param  extendedRateLimiter       The fixed-rate barrier to use when
197   *                                   processing extended operations.  It may
198   *                                   be {@code null} if no rate limiting
199   *                                   should be enforced for extended
200   *                                   operations.
201   * @param  modifyRateLimiter         The fixed-rate barrier to use when
202   *                                   processing modify operations.  It may be
203   *                                   {@code null} if no rate limiting should
204   *                                   be enforced for modify operations.
205   * @param  modifyDNRateLimiter       The fixed-rate barrier to use when
206   *                                   processing modify DN operations.  It may
207   *                                   be {@code null} if no rate limiting
208   *                                   should be enforced for modify DN
209   *                                   operations.
210   * @param  searchRateLimiter         The fixed-rate barrier to use when
211   *                                   processing search operations.  It may be
212   *                                   {@code null} if no rate limiting should
213   *                                   be enforced for search operations.
214   */
215  public RateLimiterRequestHandler(
216       @Nullable final LDAPListenerRequestHandler downstreamRequestHandler,
217       @Nullable final FixedRateBarrier abandonRateLimiter,
218       @Nullable final FixedRateBarrier addRateLimiter,
219       @Nullable final FixedRateBarrier bindRateLimiter,
220       @Nullable final FixedRateBarrier compareRateLimiter,
221       @Nullable final FixedRateBarrier deleteRateLimiter,
222       @Nullable final FixedRateBarrier extendedRateLimiter,
223       @Nullable final FixedRateBarrier modifyRateLimiter,
224       @Nullable final FixedRateBarrier modifyDNRateLimiter,
225       @Nullable final FixedRateBarrier searchRateLimiter)
226  {
227    Validator.ensureNotNull(downstreamRequestHandler);
228
229    this.downstreamRequestHandler = downstreamRequestHandler;
230    this.abandonRateLimiter       = abandonRateLimiter;
231    this.addRateLimiter           = addRateLimiter;
232    this.bindRateLimiter          = bindRateLimiter;
233    this.compareRateLimiter       = compareRateLimiter;
234    this.deleteRateLimiter        = deleteRateLimiter;
235    this.extendedRateLimiter      = extendedRateLimiter;
236    this.modifyRateLimiter        = modifyRateLimiter;
237    this.modifyDNRateLimiter      = modifyDNRateLimiter;
238    this.searchRateLimiter        = searchRateLimiter;
239  }
240
241
242
243  /**
244   * {@inheritDoc}
245   */
246  @Override()
247  @NotNull
248  public RateLimiterRequestHandler newInstance(
249              @NotNull final LDAPListenerClientConnection connection)
250         throws LDAPException
251  {
252    return new RateLimiterRequestHandler(
253         downstreamRequestHandler.newInstance(connection), abandonRateLimiter,
254         addRateLimiter, bindRateLimiter, compareRateLimiter, deleteRateLimiter,
255         extendedRateLimiter, modifyRateLimiter, modifyDNRateLimiter,
256         searchRateLimiter);
257  }
258
259
260
261  /**
262   * {@inheritDoc}
263   */
264  @Override()
265  public void processAbandonRequest(final int messageID,
266                   @NotNull final AbandonRequestProtocolOp request,
267                   @NotNull final List<Control> controls)
268  {
269    if (abandonRateLimiter != null)
270    {
271      abandonRateLimiter.await();
272    }
273
274    downstreamRequestHandler.processAbandonRequest(messageID, request,
275         controls);
276  }
277
278
279
280  /**
281   * {@inheritDoc}
282   */
283  @Override()
284  @NotNull()
285  public LDAPMessage processAddRequest(final int messageID,
286                          @NotNull final AddRequestProtocolOp request,
287                          @NotNull final List<Control> controls)
288  {
289    if (addRateLimiter != null)
290    {
291      addRateLimiter.await();
292    }
293
294    return downstreamRequestHandler.processAddRequest(messageID, request,
295         controls);
296  }
297
298
299
300  /**
301   * {@inheritDoc}
302   */
303  @Override()
304  @NotNull()
305  public LDAPMessage processBindRequest(final int messageID,
306                          @NotNull final BindRequestProtocolOp request,
307                          @NotNull final List<Control> controls)
308  {
309    if (bindRateLimiter != null)
310    {
311      bindRateLimiter.await();
312    }
313
314    return downstreamRequestHandler.processBindRequest(messageID, request,
315         controls);
316  }
317
318
319
320  /**
321   * {@inheritDoc}
322   */
323  @Override()
324  @NotNull()
325  public LDAPMessage processCompareRequest(final int messageID,
326                          @NotNull final CompareRequestProtocolOp request,
327                          @NotNull final List<Control> controls)
328  {
329    if (compareRateLimiter != null)
330    {
331      compareRateLimiter.await();
332    }
333
334    return downstreamRequestHandler.processCompareRequest(messageID, request,
335         controls);
336  }
337
338
339
340  /**
341   * {@inheritDoc}
342   */
343  @Override()
344  @NotNull()
345  public LDAPMessage processDeleteRequest(final int messageID,
346                          @NotNull final DeleteRequestProtocolOp request,
347                          @NotNull final List<Control> controls)
348  {
349    if (deleteRateLimiter != null)
350    {
351      deleteRateLimiter.await();
352    }
353
354    return downstreamRequestHandler.processDeleteRequest(messageID, request,
355         controls);
356  }
357
358
359
360  /**
361   * {@inheritDoc}
362   */
363  @Override()
364  @NotNull()
365  public LDAPMessage processExtendedRequest(final int messageID,
366                          @NotNull final ExtendedRequestProtocolOp request,
367                          @NotNull final List<Control> controls)
368  {
369    if (extendedRateLimiter != null)
370    {
371      extendedRateLimiter.await();
372    }
373
374    return downstreamRequestHandler.processExtendedRequest(messageID, request,
375         controls);
376  }
377
378
379
380  /**
381   * {@inheritDoc}
382   */
383  @Override()
384  @NotNull()
385  public LDAPMessage processModifyRequest(final int messageID,
386                          @NotNull final ModifyRequestProtocolOp request,
387                          @NotNull final List<Control> controls)
388  {
389    if (modifyRateLimiter != null)
390    {
391      modifyRateLimiter.await();
392    }
393
394    return downstreamRequestHandler.processModifyRequest(messageID, request,
395         controls);
396  }
397
398
399
400  /**
401   * {@inheritDoc}
402   */
403  @Override()
404  @NotNull()
405  public LDAPMessage processModifyDNRequest(final int messageID,
406                          @NotNull final ModifyDNRequestProtocolOp request,
407                          @NotNull final List<Control> controls)
408  {
409    if (modifyDNRateLimiter != null)
410    {
411      modifyDNRateLimiter.await();
412    }
413
414    return downstreamRequestHandler.processModifyDNRequest(messageID, request,
415         controls);
416  }
417
418
419
420  /**
421   * {@inheritDoc}
422   */
423  @Override()
424  @NotNull()
425  public LDAPMessage processSearchRequest(final int messageID,
426                          @NotNull final SearchRequestProtocolOp request,
427                          @NotNull final List<Control> controls)
428  {
429    if (searchRateLimiter != null)
430    {
431      searchRateLimiter.await();
432    }
433
434    return downstreamRequestHandler.processSearchRequest(messageID, request,
435         controls);
436  }
437}