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}