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