001/*
002 * Copyright 2011-2016 UnboundID Corp.
003 *
004 * This program is free software; you can redistribute it and/or modify
005 * it under the terms of the GNU General Public License (GPLv2 only)
006 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
007 * as published by the Free Software Foundation.
008 *
009 * This program is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
012 * GNU General Public License for more details.
013 *
014 * You should have received a copy of the GNU General Public License
015 * along with this program; if not, see <http://www.gnu.org/licenses>.
016 */
017
018package com.unboundid.scim.tools;
019
020import com.unboundid.ldap.sdk.ResultCode;
021import com.unboundid.scim.data.BaseResource;
022import com.unboundid.scim.sdk.Debug;
023import com.unboundid.scim.sdk.Resources;
024import com.unboundid.scim.sdk.SCIMEndpoint;
025import com.unboundid.scim.sdk.SCIMException;
026import com.unboundid.util.FixedRateBarrier;
027import com.unboundid.util.ValuePattern;
028
029import java.util.concurrent.CyclicBarrier;
030import java.util.concurrent.atomic.AtomicBoolean;
031import java.util.concurrent.atomic.AtomicLong;
032import java.util.concurrent.atomic.AtomicReference;
033
034
035
036/**
037 * This class provides a thread that may be used to repeatedly perform queries.
038 */
039public class QueryRateThread
040    extends Thread
041{
042  // Indicates whether a request has been made to stop running.
043  private final AtomicBoolean stopRequested;
044
045  // The counter used to track the number of resources returned.
046  private final AtomicLong resourceCounter;
047
048  // The counter used to track the number of errors encountered while querying.
049  private final AtomicLong errorCounter;
050
051  // The counter used to track the number of queries performed.
052  private final AtomicLong queryCounter;
053
054  // The value that will be updated with total duration of the queries.
055  private final AtomicLong queryDurations;
056
057  // The thread that is actually performing the queries.
058  private final AtomicReference<Thread> queryThread;
059
060  // The client to use for the queries.
061  private SCIMEndpoint<? extends BaseResource> client;
062
063  // The result code for this thread.
064  private final AtomicReference<ResultCode> resultCode;
065
066  // The barrier that will be used to coordinate starting among all the threads.
067  private final CyclicBarrier startBarrier;
068
069  // The set of requested attributes for query requests.
070  private final String[] attributes;
071
072  // The value pattern to use for the filters.
073  private final ValuePattern filterPattern;
074
075  // Indicates whether this is a query request or a get request.
076  private final boolean isQuery;
077
078  // The barrier to use for controlling the rate of queries.  null if no
079  // rate-limiting should be used.
080  private final FixedRateBarrier fixedRateBarrier;
081
082
083
084  /**
085   * Creates a new search rate thread with the provided information.
086   *
087   * @param  threadNumber     The thread number for this thread.
088   * @param  isQuery          Flag indicating whether the request is
089   *                          a query or a get.
090   * @param  client           The client to use for the queries.
091   * @param  filterPattern    The value pattern for the filters.
092   * @param  attributes       The set of attributes to return.
093   * @param  startBarrier     A barrier used to coordinate starting between all
094   *                          of the threads.
095   * @param  queryCounter     A value that will be used to keep track of the
096   *                          total number of queries performed.
097   * @param  resourceCounter  A value that will be used to keep track of the
098   *                          total number of resources returned.
099   * @param  queryDurations   A value that will be used to keep track of the
100   *                          total duration for all queries.
101   * @param  errorCounter     A value that will be used to keep track of the
102   *                          number of errors encountered while querying.
103   * @param  rateBarrier      The barrier to use for controlling the rate of
104   *                          queries.  {@code null} if no rate-limiting
105   *                          should be used.
106   */
107  QueryRateThread(final int threadNumber,
108                  final boolean isQuery,
109                  final SCIMEndpoint<? extends BaseResource> client,
110                  final ValuePattern filterPattern,
111                  final String[] attributes,
112                  final CyclicBarrier startBarrier,
113                  final AtomicLong queryCounter,
114                  final AtomicLong resourceCounter,
115                  final AtomicLong queryDurations,
116                  final AtomicLong errorCounter,
117                  final FixedRateBarrier rateBarrier)
118  {
119    setName("QueryRate Thread " + threadNumber);
120    setDaemon(true);
121
122    this.client          = client;
123    this.isQuery         = isQuery;
124    this.filterPattern   = filterPattern;
125    this.attributes      = attributes;
126    this.queryCounter    = queryCounter;
127    this.resourceCounter = resourceCounter;
128    this.queryDurations  = queryDurations;
129    this.errorCounter    = errorCounter;
130    this.startBarrier    = startBarrier;
131    fixedRateBarrier     = rateBarrier;
132
133    resultCode    = new AtomicReference<ResultCode>(null);
134    queryThread   = new AtomicReference<Thread>(null);
135    stopRequested = new AtomicBoolean(false);
136  }
137
138
139
140  /**
141   * Performs all search processing for this thread.
142   */
143  @Override()
144  public void run()
145  {
146    queryThread.set(currentThread());
147
148    try
149    {
150      startBarrier.await();
151    }
152    catch (Exception e)
153    {
154      Debug.debugException(e);
155    }
156
157    while (! stopRequested.get())
158    {
159      // If we're trying for a specific target rate, then we might need to
160      // wait until issuing the next search.
161      if (fixedRateBarrier != null)
162      {
163        fixedRateBarrier.await();
164      }
165
166      final String filterValue;
167      if (filterPattern != null)
168      {
169        filterValue = filterPattern.nextValue();
170      }
171      else
172      {
173        filterValue = null;
174      }
175
176      final long startTime = System.nanoTime();
177
178      try
179      {
180        if (isQuery)
181        {
182          final Resources<? extends BaseResource> resources =
183            client.query(filterValue, null, null, attributes);
184          if (resources != null)
185          {
186            resourceCounter.addAndGet(resources.getItemsPerPage());
187          }
188        }
189        else
190        {
191          client.get(filterValue, null, attributes);
192          resourceCounter.incrementAndGet();
193        }
194      }
195      catch (SCIMException e)
196      {
197        Debug.debugException(e);
198        errorCounter.incrementAndGet();
199
200        final ResultCode rc = ResultCode.OTHER;
201        resultCode.compareAndSet(null, rc);
202      }
203      catch (RuntimeException e)
204      {
205        Debug.debugException(e);
206
207        // If we are shutting down then just ignore the error.
208        if (stopRequested.get())
209        {
210          break;
211        }
212
213        throw e;
214      }
215
216      queryCounter.incrementAndGet();
217      queryDurations.addAndGet(System.nanoTime() - startTime);
218    }
219
220    queryThread.set(null);
221  }
222
223
224
225  /**
226   * Indicates that this thread should stop running.  It will not wait for the
227   * thread to complete before returning.
228   */
229  void signalShutdown()
230  {
231    stopRequested.set(true);
232
233    if (fixedRateBarrier != null)
234    {
235      fixedRateBarrier.shutdownRequested();
236    }
237  }
238
239
240
241  /**
242   * Waits for this thread to stop running.
243   *
244   * @return  A result code that provides information about whether any errors
245   *          were encountered during processing.
246   */
247  ResultCode waitForShutdown()
248  {
249    final Thread t = queryThread.get();
250    if (t != null)
251    {
252      try
253      {
254        t.join();
255      }
256      catch (Exception e)
257      {
258        Debug.debugException(e);
259      }
260    }
261
262    resultCode.compareAndSet(null, ResultCode.SUCCESS);
263    return resultCode.get();
264  }
265}