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.LDAPException; 021import com.unboundid.ldap.sdk.ResultCode; 022import com.unboundid.scim.data.BaseResource; 023import com.unboundid.scim.schema.ResourceDescriptor; 024import com.unboundid.scim.sdk.Debug; 025import com.unboundid.scim.sdk.ResourceNotFoundException; 026import com.unboundid.scim.sdk.SCIMEndpoint; 027import com.unboundid.scim.sdk.SCIMException; 028import com.unboundid.scim.sdk.SCIMService; 029import com.unboundid.util.ColumnFormatter; 030import com.unboundid.util.CommandLineTool; 031import com.unboundid.util.FixedRateBarrier; 032import com.unboundid.util.FormattableColumn; 033import com.unboundid.util.HorizontalAlignment; 034import com.unboundid.util.OutputFormat; 035import com.unboundid.util.ValuePattern; 036import com.unboundid.util.WakeableSleeper; 037import com.unboundid.util.args.ArgumentException; 038import com.unboundid.util.args.ArgumentParser; 039import com.unboundid.util.args.BooleanArgument; 040import com.unboundid.util.args.FileArgument; 041import com.unboundid.util.args.IntegerArgument; 042import com.unboundid.util.args.StringArgument; 043import com.unboundid.util.ssl.KeyStoreKeyManager; 044import com.unboundid.util.ssl.PromptTrustManager; 045import com.unboundid.util.ssl.SSLUtil; 046import com.unboundid.util.ssl.TrustAllTrustManager; 047import com.unboundid.util.ssl.TrustStoreTrustManager; 048 049import org.apache.http.auth.AuthScope; 050import org.apache.http.auth.UsernamePasswordCredentials; 051import org.apache.http.client.config.RequestConfig; 052import org.apache.http.config.Registry; 053import org.apache.http.config.RegistryBuilder; 054import org.apache.http.config.SocketConfig; 055import org.apache.http.conn.socket.ConnectionSocketFactory; 056import org.apache.http.conn.socket.PlainConnectionSocketFactory; 057import org.apache.http.conn.ssl.NoopHostnameVerifier; 058import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 059import org.apache.http.impl.client.BasicCredentialsProvider; 060import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; 061import org.glassfish.jersey.apache.connector.ApacheClientProperties; 062import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; 063import org.glassfish.jersey.client.ClientConfig; 064 065import javax.net.ssl.KeyManager; 066import javax.net.ssl.TrustManager; 067import javax.ws.rs.client.ClientRequestContext; 068import javax.ws.rs.client.ClientRequestFilter; 069import javax.ws.rs.core.MediaType; 070import java.io.IOException; 071import java.io.OutputStream; 072import java.net.URI; 073import java.net.URISyntaxException; 074import java.security.GeneralSecurityException; 075import java.text.ParseException; 076import java.util.Arrays; 077import java.util.LinkedHashMap; 078import java.util.LinkedHashSet; 079import java.util.List; 080import java.util.concurrent.CyclicBarrier; 081import java.util.concurrent.atomic.AtomicLong; 082import java.util.concurrent.atomic.AtomicReference; 083import java.util.logging.ConsoleHandler; 084 085import static com.unboundid.scim.sdk.Debug.debugException; 086import static com.unboundid.util.StaticUtils.NO_STRINGS; 087import static com.unboundid.scim.tools.ToolMessages.*; 088import static com.unboundid.util.StaticUtils.getExceptionMessage; 089 090/** 091 * This class provides a tool that can be used to query a SCIM server repeatedly 092 * using multiple threads. It can help provide an estimate of the query 093 * performance that a SCIM server is able to achieve. The query filter may be 094 * a value pattern as described in the {@link com.unboundid.util.ValuePattern} 095 * class. This makes it possible to query over a range of resources rather 096 * than repeatedly performing queries with the same filter. 097 * <BR><BR> 098 * All of the necessary information is provided using command line arguments. 099 * Supported arguments are as follows: 100 * <UL> 101 * <LI>"-h {address}" or "--hostname {address}" -- Specifies the address of 102 * the SCIM server. If this isn't specified, then a default of 103 * "localhost" will be used.</LI> 104 * <LI>"-p {port}" or "--port {port}" -- Specifies the port number of the 105 * SCIM server. If this isn't specified, then a default port of 80 106 * will be used.</LI> 107 * <LI>"--contextPath {path}" -- specifies the context path of 108 * the SCIM server. If no context path is specified, then the default 109 * value '/' is used.</LI> 110 * <LI>"--authID {userName}" -- Specifies the authentication ID to use when 111 * authenticating using basic auth.</LI> 112 * <LI>"-w {password}" or "--authPassword {password}" -- Specifies the 113 * password to use when authenticating using basic auth or a 114 * password-based SASL mechanism.</LI> 115 * <LI>"--bearerToken {b64token}" -- Specifies the OAuth2 bearer 116 * token to use when authenticating using OAuth</LI> 117 * <LI>"--resourceName {resource-name}" -- specifies the name of resources to 118 * be queried. If this isn't specified, then a default of "User" will 119 * be used.</LI> 120 * <LI>"-x" or "--xml" -- Specifies XML format in requests rather than 121 * JSON format.</LI> 122 * <LI>"-f {filter}" or "--filter {filter}" -- specifies the filter to use for 123 * the queries. It may be a simple filter, or it may be a value pattern 124 * to express a range of filters. If this isn't specified, then no 125 * filtering is requested.</LI> 126 * <LI>"-A {name}" or "--attribute {name}" -- specifies the name of an 127 * attribute that should be included in resources returned from the 128 * server. If this isn't specified, then all resource attributes will be 129 * requested. Multiple attributes may be requested with multiple instances 130 * of this argument.</LI> 131 * <LI>"-t {num}" or "--numThreads {num}" -- specifies the number of 132 * concurrent threads to use when performing the queries. If this is not 133 * provided, then a default of one thread will be used.</LI> 134 * <LI>"-i {sec}" or "--intervalDuration {sec}" -- specifies the length of 135 * time in seconds between lines out output. If this is not provided, 136 * then a default interval duration of five seconds will be used.</LI> 137 * <LI>"-I {num}" or "--numIntervals {num}" -- specifies the maximum number of 138 * intervals for which to run. If this is not provided, then it will 139 * run forever.</LI> 140 * <LI>"-r {queries-per-second}" or "--ratePerSecond {queries-per-second}" 141 * -- specifies the target number of queries to perform per second. It 142 * is still necessary to specify a sufficient number of threads for 143 * achieving this rate. If this option is not provided, then the tool 144 * will run at the maximum rate for the specified number of threads.</LI> 145 * <LI>"--warmUpIntervals {num}" -- specifies the number of intervals to 146 * complete before beginning overall statistics collection.</LI> 147 * <LI>"--timestampFormat {format}" -- specifies the format to use for 148 * timestamps included before each output line. The format may be one of 149 * "none" (for no timestamps), "with-date" (to include both the date and 150 * the time), or "without-date" (to include only time time).</LI> 151 * <LI>"-c" or "--csv" -- Generate output in CSV format rather than a 152 * display-friendly format.</LI> 153 * </UL> 154 */ 155public class SCIMQueryRate 156 extends CommandLineTool 157{ 158 // Arguments used to communicate with a SCIM server. 159 private FileArgument authPasswordFile; 160 private IntegerArgument port; 161 private StringArgument authID; 162 private StringArgument authPassword; 163 private StringArgument bearerToken; 164 private StringArgument contextPath; 165 private StringArgument host; 166 private BooleanArgument trustAll; 167 private BooleanArgument useSSL; 168 private FileArgument keyStorePasswordFile; 169 private FileArgument trustStorePasswordFile; 170 private StringArgument certificateNickname; 171 private StringArgument keyStoreFormat; 172 private StringArgument keyStorePath; 173 private StringArgument keyStorePassword; 174 private StringArgument trustStoreFormat; 175 private StringArgument trustStorePath; 176 private StringArgument trustStorePassword; 177 178 // The argument used to indicate whether to generate output in CSV format. 179 private BooleanArgument csvFormat; 180 181 // The argument used to indicate whether to use XML format in requests rather 182 // than JSON format. 183 private BooleanArgument xmlFormat; 184 185 // The argument used to specify the collection interval. 186 private IntegerArgument collectionInterval; 187 188 // The argument used to specify the number of intervals. 189 private IntegerArgument numIntervals; 190 191 // The argument used to specify the number of threads. 192 private IntegerArgument numThreads; 193 194 // The argument used to specify the seed to use for the random number 195 // generator. 196 private IntegerArgument randomSeed; 197 198 // The target rate of searches per second. 199 private IntegerArgument ratePerSecond; 200 201 // The number of warm-up intervals to perform. 202 private IntegerArgument warmUpIntervals; 203 204 // The argument used to specify the attributes to return. 205 private StringArgument attributes; 206 207 // The argument used to specify the filters for the queries. 208 private StringArgument filter; 209 210 // The argument used to specify a resource ID (or pattern). 211 private StringArgument resourceId; 212 213 // The argument used to specify the name of resources to be queried. 214 private StringArgument resourceName; 215 216 // The argument used to specify the timestamp format. 217 private StringArgument timestampFormat; 218 219 // The prompt trust manager that will be shared by all connections created 220 // for which it is appropriate. This will allow them to benefit from the 221 // common cache. 222 private final AtomicReference<PromptTrustManager> promptTrustManager = 223 new AtomicReference<PromptTrustManager>(); 224 225 226 /** 227 * Parse the provided command line arguments and make the appropriate set of 228 * changes. 229 * 230 * @param args The command line arguments provided to this program. 231 */ 232 public static void main(final String[] args) 233 { 234 final ResultCode resultCode = main(args, System.out, System.err); 235 if (resultCode != ResultCode.SUCCESS) 236 { 237 System.exit(resultCode.intValue()); 238 } 239 } 240 241 242 243 /** 244 * Parse the provided command line arguments and make the appropriate set of 245 * changes. 246 * 247 * @param args The command line arguments provided to this program. 248 * @param outStream The output stream to which standard out should be 249 * written. It may be {@code null} if output should be 250 * suppressed. 251 * @param errStream The output stream to which standard error should be 252 * written. It may be {@code null} if error messages 253 * should be suppressed. 254 * 255 * @return A result code indicating whether the processing was successful. 256 */ 257 public static ResultCode main(final String[] args, 258 final OutputStream outStream, 259 final OutputStream errStream) 260 { 261 final SCIMQueryRate queryRate = new SCIMQueryRate(outStream, errStream); 262 return queryRate.runTool(args); 263 } 264 265 266 267 /** 268 * Creates a new instance of this tool. 269 * 270 * @param outStream The output stream to which standard out should be 271 * written. It may be {@code null} if output should be 272 * suppressed. 273 * @param errStream The output stream to which standard error should be 274 * written. It may be {@code null} if error messages 275 * should be suppressed. 276 */ 277 public SCIMQueryRate(final OutputStream outStream, 278 final OutputStream errStream) 279 { 280 super(outStream, errStream); 281 } 282 283 284 285 /** 286 * Retrieves the name for this tool. 287 * 288 * @return The name for this tool. 289 */ 290 @Override() 291 public String getToolName() 292 { 293 return "scim-query-rate"; 294 } 295 296 297 298 /** 299 * Retrieves the description for this tool. 300 * 301 * @return The description for this tool. 302 */ 303 @Override() 304 public String getToolDescription() 305 { 306 return INFO_QUERY_TOOL_DESC.get(); 307 } 308 309 310 311 /** 312 * {@inheritDoc} 313 */ 314 @Override() 315 public void addToolArguments(final ArgumentParser parser) 316 throws ArgumentException 317 { 318 host = new StringArgument( 319 'h', "hostname", true, 1, 320 INFO_QUERY_TOOL_ARG_PLACEHOLDER_HOSTNAME.get(), 321 INFO_QUERY_TOOL_ARG_DESC_HOSTNAME.get(), 322 "localhost"); 323 parser.addArgument(host); 324 325 326 port = new IntegerArgument( 327 'p', "port", true, 1, 328 INFO_QUERY_TOOL_ARG_PLACEHOLDER_PORT.get(), 329 INFO_QUERY_TOOL_ARG_DESC_PORT.get(), 330 1, 65535, 80); 331 parser.addArgument(port); 332 333 334 contextPath = new StringArgument(null, "contextPath", false, 1, 335 INFO_QUERY_TOOL_ARG_PLACEHOLDER_CONTEXT_PATH.get(), 336 INFO_QUERY_TOOL_ARG_DESC_CONTEXT_PATH.get(), 337 Arrays.asList("/")); 338 parser.addArgument(contextPath); 339 340 341 authID = new StringArgument( 342 null, "authID", false, 1, 343 INFO_QUERY_TOOL_ARG_PLACEHOLDER_AUTHID.get(), 344 INFO_QUERY_TOOL_ARG_DESC_AUTHID.get()); 345 parser.addArgument(authID); 346 347 348 authPassword = new StringArgument( 349 'w', "authPassword", false, 1, 350 INFO_QUERY_TOOL_ARG_PLACEHOLDER_AUTH_PASSWORD.get(), 351 INFO_QUERY_TOOL_ARG_DESC_AUTH_PASSWORD.get()); 352 parser.addArgument(authPassword); 353 354 355 bearerToken = new StringArgument( 356 null, "bearerToken", false, 1, 357 INFO_QUERY_TOOL_ARG_PLACEHOLDER_BEARER_TOKEN.get(), 358 INFO_QUERY_TOOL_ARG_DESC_BEARER_TOKEN.get()); 359 parser.addArgument(bearerToken); 360 361 362 authPasswordFile = new FileArgument( 363 'j', "authPasswordFile", false, 1, 364 INFO_QUERY_TOOL_ARG_PLACEHOLDER_AUTH_PASSWORD_FILE.get(), 365 INFO_QUERY_TOOL_ARG_DESC_AUTH_PASSWORD_FILE.get(), 366 true, true, true, false); 367 parser.addArgument(authPasswordFile); 368 369 370 resourceName = new StringArgument( 371 null, "resourceName", false, 1, 372 INFO_QUERY_TOOL_ARG_PLACEHOLDER_RESOURCE_NAME.get(), 373 INFO_QUERY_TOOL_ARG_DESC_RESOURCE_NAME.get(), 374 null, Arrays.asList("User")); 375 parser.addArgument(resourceName); 376 377 378 xmlFormat = new BooleanArgument( 379 'x', "xml", 1, 380 INFO_QUERY_TOOL_ARG_DESC_XML_FORMAT.get()); 381 parser.addArgument(xmlFormat); 382 383 filter = new StringArgument( 384 'f', "filter", false, 1, 385 INFO_QUERY_TOOL_ARG_PLACEHOLDER_FILTER.get(), 386 INFO_QUERY_TOOL_ARG_DESC_FILTER.get()); 387 parser.addArgument(filter); 388 389 390 resourceId = new StringArgument( 391 'd', "resourceID", false, 1, 392 INFO_QUERY_TOOL_ARG_PLACEHOLDER_RESOURCE_ID.get(), 393 INFO_QUERY_TOOL_ARG_DESC_RESOURCE_ID.get()); 394 parser.addArgument(resourceId); 395 396 397 attributes = new StringArgument( 398 'A', "attribute", false, 0, 399 INFO_QUERY_TOOL_ARG_PLACEHOLDER_ATTRIBUTE.get(), 400 INFO_QUERY_TOOL_ARG_DESC_ATTRIBUTE.get()); 401 parser.addArgument(attributes); 402 403 404 numThreads = new IntegerArgument( 405 't', "numThreads", true, 1, 406 INFO_QUERY_TOOL_ARG_PLACEHOLDER_NUM_THREADS.get(), 407 INFO_QUERY_TOOL_ARG_DESC_NUM_THREADS.get(), 408 1, Integer.MAX_VALUE, 1); 409 parser.addArgument(numThreads); 410 411 412 collectionInterval = new IntegerArgument( 413 'i', "intervalDuration", true, 1, 414 INFO_QUERY_TOOL_ARG_PLACEHOLDER_INTERVAL_DURATION.get(), 415 INFO_QUERY_TOOL_ARG_DESC_INTERVAL_DURATION.get(), 1, 416 Integer.MAX_VALUE, 5); 417 parser.addArgument(collectionInterval); 418 419 420 numIntervals = new IntegerArgument( 421 'I', "numIntervals", true, 1, 422 INFO_QUERY_TOOL_ARG_PLACEHOLDER_NUM_INTERVALS.get(), 423 INFO_QUERY_TOOL_ARG_DESC_NUM_INTERVALS.get(), 424 1, Integer.MAX_VALUE, 425 Integer.MAX_VALUE); 426 parser.addArgument(numIntervals); 427 428 ratePerSecond = new IntegerArgument( 429 'r', "ratePerSecond", false, 1, 430 INFO_QUERY_TOOL_ARG_PLACEHOLDER_RATE_PER_SECOND.get(), 431 INFO_QUERY_TOOL_ARG_DESC_RATE_PER_SECOND.get(), 432 1, Integer.MAX_VALUE); 433 parser.addArgument(ratePerSecond); 434 435 warmUpIntervals = new IntegerArgument( 436 null, "warmUpIntervals", true, 1, 437 INFO_QUERY_TOOL_ARG_PLACEHOLDER_WARM_UP_INTERVALS.get(), 438 INFO_QUERY_TOOL_ARG_DESC_WARM_UP_INTERVALS.get(), 439 0, Integer.MAX_VALUE, 0); 440 parser.addArgument(warmUpIntervals); 441 442 final LinkedHashSet<String> allowedFormats = new LinkedHashSet<String>(3); 443 allowedFormats.add("none"); 444 allowedFormats.add("with-date"); 445 allowedFormats.add("without-date"); 446 timestampFormat = new StringArgument( 447 null, "timestampFormat", true, 1, 448 INFO_QUERY_TOOL_ARG_PLACEHOLDER_TIMESTAMP_FORMAT.get(), 449 INFO_QUERY_TOOL_ARG_DESC_TIMESTAMP_FORMAT.get(), 450 allowedFormats, "none"); 451 parser.addArgument(timestampFormat); 452 453 csvFormat = new BooleanArgument( 454 'c', "csv", 1, 455 INFO_QUERY_TOOL_ARG_DESC_CSV_FORMAT.get()); 456 parser.addArgument(csvFormat); 457 458 randomSeed = new IntegerArgument( 459 'R', "randomSeed", false, 1, 460 INFO_QUERY_TOOL_ARG_PLACEHOLDER_RANDOM_SEED.get(), 461 INFO_QUERY_TOOL_ARG_DESC_RANDOM_SEED.get()); 462 parser.addArgument(randomSeed); 463 464 useSSL = new BooleanArgument('Z', "useSSL", 1, 465 INFO_SCIM_TOOL_DESCRIPTION_USE_SSL.get()); 466 parser.addArgument(useSSL); 467 468 trustAll = new BooleanArgument('X', "trustAll", 1, 469 INFO_SCIM_TOOL_DESCRIPTION_TRUST_ALL.get()); 470 parser.addArgument(trustAll); 471 472 keyStorePath = new StringArgument('K', "keyStorePath", false, 1, 473 INFO_SCIM_TOOL_PLACEHOLDER_PATH.get(), 474 INFO_SCIM_TOOL_DESCRIPTION_KEY_STORE_PATH.get()); 475 parser.addArgument(keyStorePath); 476 477 keyStorePassword = new StringArgument('W', "keyStorePassword", false, 1, 478 INFO_SCIM_TOOL_PLACEHOLDER_PASSWORD.get(), 479 INFO_SCIM_TOOL_DESCRIPTION_KEY_STORE_PASSWORD.get()); 480 parser.addArgument(keyStorePassword); 481 482 keyStorePasswordFile = new FileArgument('u', "keyStorePasswordFile", false, 483 1, INFO_SCIM_TOOL_PLACEHOLDER_PATH.get(), 484 INFO_SCIM_TOOL_DESCRIPTION_KEY_STORE_PASSWORD_FILE.get()); 485 parser.addArgument(keyStorePasswordFile); 486 487 keyStoreFormat = new StringArgument(null, "keyStoreFormat", false, 1, 488 INFO_SCIM_TOOL_PLACEHOLDER_FORMAT.get(), 489 INFO_SCIM_TOOL_DESCRIPTION_KEY_STORE_FORMAT.get()); 490 parser.addArgument(keyStoreFormat); 491 492 trustStorePath = new StringArgument('P', "trustStorePath", false, 1, 493 INFO_SCIM_TOOL_PLACEHOLDER_PATH.get(), 494 INFO_SCIM_TOOL_DESCRIPTION_TRUST_STORE_PATH.get()); 495 parser.addArgument(trustStorePath); 496 497 trustStorePassword = new StringArgument('T', "trustStorePassword", false, 1, 498 INFO_SCIM_TOOL_PLACEHOLDER_PASSWORD.get(), 499 INFO_SCIM_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD.get()); 500 parser.addArgument(trustStorePassword); 501 502 trustStorePasswordFile = new FileArgument('U', "trustStorePasswordFile", 503 false, 1, INFO_SCIM_TOOL_PLACEHOLDER_PATH.get(), 504 INFO_SCIM_TOOL_DESCRIPTION_TRUST_STORE_PASSWORD_FILE.get()); 505 parser.addArgument(trustStorePasswordFile); 506 507 trustStoreFormat = new StringArgument(null, "trustStoreFormat", false, 1, 508 INFO_SCIM_TOOL_PLACEHOLDER_FORMAT.get(), 509 INFO_SCIM_TOOL_DESCRIPTION_TRUST_STORE_FORMAT.get()); 510 parser.addArgument(trustStoreFormat); 511 512 certificateNickname = new StringArgument('N', "certNickname", false, 1, 513 INFO_SCIM_TOOL_PLACEHOLDER_CERT_NICKNAME.get(), 514 INFO_SCIM_TOOL_DESCRIPTION_CERT_NICKNAME.get()); 515 parser.addArgument(certificateNickname); 516 517 parser.addDependentArgumentSet(authID, authPassword, authPasswordFile); 518 parser.addExclusiveArgumentSet(authPassword, authPasswordFile, bearerToken); 519 parser.addExclusiveArgumentSet(authID, bearerToken); 520 parser.addExclusiveArgumentSet(keyStorePassword, keyStorePasswordFile); 521 parser.addExclusiveArgumentSet(trustStorePassword, trustStorePasswordFile); 522 parser.addExclusiveArgumentSet(trustAll, trustStorePath); 523 parser.addExclusiveArgumentSet(filter, resourceId); 524 } 525 526 527 528 /** 529 * {@inheritDoc} 530 */ 531 @Override() 532 public LinkedHashMap<String[],String> getExampleUsages() 533 { 534 final LinkedHashMap<String[],String> examples = 535 new LinkedHashMap<String[],String>(); 536 537 final String[] args1 = 538 { 539 "--hostname", "server.example.com", 540 "--port", "80", 541 "--authID", "admin", 542 "--authPassword", "password", 543 "--xml", 544 "--filter", "userName eq \"user.[1-1000000]\"", 545 "--attribute", "userName", 546 "--attribute", "name", 547 "--numThreads", "8" 548 }; 549 examples.put(args1, INFO_QUERY_TOOL_EXAMPLE_1.get()); 550 551 final String[] args2 = 552 { 553 "--hostname", "server.example.com", 554 "--port", "80", 555 "--authID", "admin", 556 "--authPassword", "password", 557 "--resourceID", "uid=user.[1-1000000],ou=people,dc=example,dc=com", 558 "--attribute", "userName", 559 "--attribute", "name", 560 "--numThreads", "8" 561 }; 562 examples.put(args2, INFO_QUERY_TOOL_EXAMPLE_2.get()); 563 564 return examples; 565 } 566 567 568 569 /** 570 * Performs the actual processing for this tool. In this case, it gets a 571 * connection to the directory server and uses it to perform the requested 572 * searches. 573 * 574 * @return The result code for the processing that was performed. 575 */ 576 @Override() 577 public ResultCode doToolProcessing() 578 { 579 //Initalize the Debugger 580 Debug.setEnabled(true); 581 Debug.getLogger().addHandler(new ConsoleHandler()); 582 Debug.getLogger().setUseParentHandlers(false); 583 584 // Determine the random seed to use. 585 final Long seed; 586 if (randomSeed.isPresent()) 587 { 588 seed = Long.valueOf(randomSeed.getValue()); 589 } 590 else 591 { 592 seed = null; 593 } 594 595 // Create a value pattern for the filter. 596 final ValuePattern filterPattern; 597 boolean isQuery = true; 598 if (filter.isPresent()) 599 { 600 try 601 { 602 filterPattern = new ValuePattern(filter.getValue(), seed); 603 } 604 catch (ParseException pe) 605 { 606 Debug.debugException(pe); 607 err(ERR_QUERY_TOOL_BAD_FILTER_PATTERN.get(pe.getMessage())); 608 return ResultCode.PARAM_ERROR; 609 } 610 } 611 else if (resourceId.isPresent()) 612 { 613 isQuery = false; 614 try 615 { 616 filterPattern = new ValuePattern(resourceId.getValue()); 617 } 618 catch (ParseException pe) 619 { 620 Debug.debugException(pe); 621 err(ERR_QUERY_TOOL_BAD_RESOURCE_ID_PATTERN.get(pe.getMessage())); 622 return ResultCode.PARAM_ERROR; 623 } 624 } 625 else 626 { 627 filterPattern = null; 628 } 629 630 // Get the attributes to return. 631 final String[] attrs; 632 if (attributes.isPresent()) 633 { 634 final List<String> attrList = attributes.getValues(); 635 attrs = new String[attrList.size()]; 636 attrList.toArray(attrs); 637 } 638 else 639 { 640 attrs = NO_STRINGS; 641 } 642 643 644 // If the --ratePerSecond option was specified, then limit the rate 645 // accordingly. 646 FixedRateBarrier fixedRateBarrier = null; 647 if (ratePerSecond.isPresent()) 648 { 649 final int intervalSeconds = collectionInterval.getValue(); 650 final int ratePerInterval = ratePerSecond.getValue() * intervalSeconds; 651 652 fixedRateBarrier = 653 new FixedRateBarrier(1000L * intervalSeconds, ratePerInterval); 654 } 655 656 657 // Determine whether to include timestamps in the output and if so what 658 // format should be used for them. 659 final boolean includeTimestamp; 660 final String timeFormat; 661 if (timestampFormat.getValue().equalsIgnoreCase("with-date")) 662 { 663 includeTimestamp = true; 664 timeFormat = "dd/MM/yyyy HH:mm:ss"; 665 } 666 else if (timestampFormat.getValue().equalsIgnoreCase("without-date")) 667 { 668 includeTimestamp = true; 669 timeFormat = "HH:mm:ss"; 670 } 671 else 672 { 673 includeTimestamp = false; 674 timeFormat = null; 675 } 676 677 678 // Determine whether any warm-up intervals should be run. 679 final long totalIntervals; 680 final boolean warmUp; 681 int remainingWarmUpIntervals = warmUpIntervals.getValue(); 682 if (remainingWarmUpIntervals > 0) 683 { 684 warmUp = true; 685 totalIntervals = 0L + numIntervals.getValue() + remainingWarmUpIntervals; 686 } 687 else 688 { 689 warmUp = true; 690 totalIntervals = 0L + numIntervals.getValue(); 691 } 692 693 694 // Create the table that will be used to format the output. 695 final OutputFormat outputFormat; 696 if (csvFormat.isPresent()) 697 { 698 outputFormat = OutputFormat.CSV; 699 } 700 else 701 { 702 outputFormat = OutputFormat.COLUMNS; 703 } 704 705 final ColumnFormatter formatter = new ColumnFormatter(includeTimestamp, 706 timeFormat, outputFormat, " ", 707 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Recent", 708 "Queries/Sec"), 709 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Recent", 710 "Avg Dur ms"), 711 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Recent", 712 "Resources/Query"), 713 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Recent", 714 "Errors/Sec"), 715 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Overall", 716 "Queries/Sec"), 717 new FormattableColumn(15, HorizontalAlignment.RIGHT, "Overall", 718 "Avg Dur ms")); 719 720 721 // Create values to use for statistics collection. 722 final AtomicLong queryCounter = new AtomicLong(0L); 723 final AtomicLong resourceCounter = new AtomicLong(0L); 724 final AtomicLong errorCounter = new AtomicLong(0L); 725 final AtomicLong queryDurations = new AtomicLong(0L); 726 727 728 // Determine the length of each interval in milliseconds. 729 final long intervalMillis = 1000L * collectionInterval.getValue(); 730 731 732 // We will use Apache's HttpClient library for this tool. 733 SSLUtil sslUtil; 734 try 735 { 736 sslUtil = createSSLUtil(); 737 } 738 catch (LDAPException e) 739 { 740 debugException(e); 741 err(e.getMessage()); 742 return e.getResultCode(); 743 } 744 745 RegistryBuilder<ConnectionSocketFactory> registryBuilder = 746 RegistryBuilder.create(); 747 final String schemeName; 748 if (sslUtil != null) 749 { 750 try 751 { 752 SSLConnectionSocketFactory sslConnectionSocketFactory = 753 new SSLConnectionSocketFactory(sslUtil.createSSLContext("TLS"), 754 new NoopHostnameVerifier()); 755 schemeName = "https"; 756 registryBuilder.register(schemeName, sslConnectionSocketFactory); 757 } 758 catch (GeneralSecurityException e) 759 { 760 debugException(e); 761 err(ERR_SCIM_TOOL_CANNOT_CREATE_SSL_CONTEXT.get( 762 getExceptionMessage(e))); 763 return ResultCode.LOCAL_ERROR; 764 } 765 } 766 else 767 { 768 schemeName = "http"; 769 registryBuilder.register(schemeName, new PlainConnectionSocketFactory()); 770 } 771 final Registry<ConnectionSocketFactory> socketFactoryRegistry = 772 registryBuilder.build(); 773 774 RequestConfig requestConfig = RequestConfig.custom() 775 .setConnectionRequestTimeout(30000) 776 .setExpectContinueEnabled(true).build(); 777 778 SocketConfig socketConfig = SocketConfig.custom() 779 .setSoTimeout(30000) 780 .setSoReuseAddress(true) 781 .build(); 782 783 final PoolingHttpClientConnectionManager mgr = 784 new PoolingHttpClientConnectionManager(socketFactoryRegistry); 785 mgr.setMaxTotal(numThreads.getValue()); 786 mgr.setDefaultMaxPerRoute(numThreads.getValue()); 787 mgr.setDefaultSocketConfig(socketConfig); 788 mgr.setValidateAfterInactivity(-1); 789 790 ClientConfig jerseyConfig = new ClientConfig(); 791 792 jerseyConfig.property(ApacheClientProperties.CONNECTION_MANAGER, mgr); 793 jerseyConfig.property(ApacheClientProperties.REQUEST_CONFIG, requestConfig); 794 ApacheConnectorProvider connectorProvider = new ApacheConnectorProvider(); 795 jerseyConfig.connectorProvider(connectorProvider); 796 797 if (authID.isPresent()) 798 { 799 try 800 { 801 final String password; 802 if (authPassword.isPresent()) 803 { 804 password = authPassword.getValue(); 805 } 806 else if (authPasswordFile.isPresent()) 807 { 808 password = authPasswordFile.getNonBlankFileLines().get(0); 809 } 810 else 811 { 812 password = null; 813 } 814 815 BasicCredentialsProvider provider = new BasicCredentialsProvider(); 816 provider.setCredentials( 817 new AuthScope(host.getValue(), port.getValue()), 818 new UsernamePasswordCredentials(authID.getValue(), password) 819 ); 820 821 jerseyConfig.property( 822 ApacheClientProperties.CREDENTIALS_PROVIDER, provider); 823 jerseyConfig.property( 824 ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true); 825 } 826 catch (IOException e) 827 { 828 Debug.debugException(e); 829 err(ERR_QUERY_TOOL_SET_BASIC_AUTH.get(e.getMessage())); 830 return ResultCode.LOCAL_ERROR; 831 } 832 } 833 else if (bearerToken.isPresent()) 834 { 835 jerseyConfig.register( 836 new ClientRequestFilter() 837 { 838 public void filter(final ClientRequestContext clientRequestContext) 839 throws IOException 840 { 841 try 842 { 843 clientRequestContext.getHeaders().add( 844 "Authorization", "Bearer " + bearerToken.getValue()); 845 } 846 catch (Exception ex) 847 { 848 throw new RuntimeException( 849 "Unable to add authorization handler", ex); 850 } 851 } 852 } 853 ); 854 } 855 856 // Create the SCIM client to use for the queries. 857 final URI uri; 858 try 859 { 860 final String path; 861 if (contextPath.getValue().startsWith("/")) 862 { 863 path = contextPath.getValue(); 864 } 865 else 866 { 867 path = "/" + contextPath.getValue(); 868 } 869 uri = new URI(schemeName, null, host.getValue(), port.getValue(), 870 path, null, null); 871 } 872 catch (URISyntaxException e) 873 { 874 Debug.debugException(e); 875 err(ERR_QUERY_TOOL_CANNOT_CREATE_URL.get(e.getMessage())); 876 return ResultCode.OTHER; 877 } 878 final SCIMService service = new SCIMService(uri, jerseyConfig); 879 880 if (xmlFormat.isPresent()) 881 { 882 service.setContentType(MediaType.APPLICATION_XML_TYPE); 883 service.setAcceptType(MediaType.APPLICATION_XML_TYPE); 884 } 885 886 // Retrieve the resource schema. 887 final ResourceDescriptor resourceDescriptor; 888 try 889 { 890 resourceDescriptor = 891 service.getResourceDescriptor(resourceName.getValue(), null); 892 if(resourceDescriptor == null) 893 { 894 throw new ResourceNotFoundException("Resource " + 895 resourceName.getValue() + 896 " is not defined by the service provider"); 897 } 898 } 899 catch (SCIMException e) 900 { 901 Debug.debugException(e); 902 err(ERR_QUERY_TOOL_RETRIEVE_RESOURCE_SCHEMA.get(e.getMessage())); 903 return ResultCode.OTHER; 904 } 905 906 final SCIMEndpoint<? extends BaseResource> endpoint = 907 service.getEndpoint(resourceDescriptor, 908 BaseResource.BASE_RESOURCE_FACTORY); 909 910 // Create the threads to use for the searches. 911 final CyclicBarrier barrier = new CyclicBarrier(numThreads.getValue() + 1); 912 final QueryRateThread[] threads = 913 new QueryRateThread[numThreads.getValue()]; 914 for (int i=0; i < threads.length; i++) 915 { 916 threads[i] = 917 new QueryRateThread(i, isQuery, endpoint, filterPattern, attrs, 918 barrier, queryCounter, resourceCounter, queryDurations, 919 errorCounter, fixedRateBarrier); 920 threads[i].start(); 921 } 922 923 924 // Display the table header. 925 for (final String headerLine : formatter.getHeaderLines(true)) 926 { 927 out(headerLine); 928 } 929 930 931 // Indicate that the threads can start running. 932 try 933 { 934 barrier.await(); 935 } 936 catch (Exception e) 937 { 938 Debug.debugException(e); 939 } 940 long overallStartTime = System.nanoTime(); 941 long nextIntervalStartTime = System.currentTimeMillis() + intervalMillis; 942 943 944 boolean setOverallStartTime = false; 945 long lastDuration = 0L; 946 long lastNumEntries = 0L; 947 long lastNumErrors = 0L; 948 long lastNumSearches = 0L; 949 long lastEndTime = System.nanoTime(); 950 for (long i=0; i < totalIntervals; i++) 951 { 952 final long startTimeMillis = System.currentTimeMillis(); 953 final long sleepTimeMillis = nextIntervalStartTime - startTimeMillis; 954 nextIntervalStartTime += intervalMillis; 955 try 956 { 957 if (sleepTimeMillis > 0) 958 { 959 Thread.sleep(sleepTimeMillis); 960 } 961 } 962 catch (Exception e) 963 { 964 Debug.debugException(e); 965 } 966 967 final long endTime = System.nanoTime(); 968 final long intervalDuration = endTime - lastEndTime; 969 970 final long numSearches; 971 final long numEntries; 972 final long numErrors; 973 final long totalDuration; 974 if (warmUp && (remainingWarmUpIntervals > 0)) 975 { 976 numSearches = queryCounter.getAndSet(0L); 977 numEntries = resourceCounter.getAndSet(0L); 978 numErrors = errorCounter.getAndSet(0L); 979 totalDuration = queryDurations.getAndSet(0L); 980 } 981 else 982 { 983 numSearches = queryCounter.get(); 984 numEntries = resourceCounter.get(); 985 numErrors = errorCounter.get(); 986 totalDuration = queryDurations.get(); 987 } 988 989 final long recentNumSearches = numSearches - lastNumSearches; 990 final long recentNumEntries = numEntries - lastNumEntries; 991 final long recentNumErrors = numErrors - lastNumErrors; 992 final long recentDuration = totalDuration - lastDuration; 993 994 final double numSeconds = intervalDuration / 1000000000.0d; 995 final double recentSearchRate = recentNumSearches / numSeconds; 996 final double recentErrorRate = recentNumErrors / numSeconds; 997 998 final double recentAvgDuration; 999 final double recentEntriesPerSearch; 1000 if (recentNumSearches > 0L) 1001 { 1002 recentEntriesPerSearch = 1.0d * recentNumEntries / recentNumSearches; 1003 recentAvgDuration = 1.0d * recentDuration / recentNumSearches / 1000000; 1004 } 1005 else 1006 { 1007 recentEntriesPerSearch = 0.0d; 1008 recentAvgDuration = 0.0d; 1009 } 1010 1011 1012 if (warmUp && (remainingWarmUpIntervals > 0)) 1013 { 1014 out(formatter.formatRow(recentSearchRate, recentAvgDuration, 1015 recentEntriesPerSearch, recentErrorRate, "warming up", 1016 "warming up")); 1017 1018 remainingWarmUpIntervals--; 1019 if (remainingWarmUpIntervals == 0) 1020 { 1021 out(INFO_QUERY_TOOL_WARM_UP_COMPLETED.get()); 1022 setOverallStartTime = true; 1023 } 1024 } 1025 else 1026 { 1027 if (setOverallStartTime) 1028 { 1029 overallStartTime = lastEndTime; 1030 setOverallStartTime = false; 1031 } 1032 1033 final double numOverallSeconds = 1034 (endTime - overallStartTime) / 1000000000.0d; 1035 final double overallSearchRate = numSearches / numOverallSeconds; 1036 1037 final double overallAvgDuration; 1038 if (numSearches > 0L) 1039 { 1040 overallAvgDuration = 1.0d * totalDuration / numSearches / 1000000; 1041 } 1042 else 1043 { 1044 overallAvgDuration = 0.0d; 1045 } 1046 1047 out(formatter.formatRow(recentSearchRate, recentAvgDuration, 1048 recentEntriesPerSearch, recentErrorRate, overallSearchRate, 1049 overallAvgDuration)); 1050 1051 lastNumSearches = numSearches; 1052 lastNumEntries = numEntries; 1053 lastNumErrors = numErrors; 1054 lastDuration = totalDuration; 1055 } 1056 1057 lastEndTime = endTime; 1058 } 1059 1060 1061 // Stop all of the threads. 1062 ResultCode resultCode = ResultCode.SUCCESS; 1063 for (final QueryRateThread t : threads) 1064 { 1065 t.signalShutdown(); 1066 } 1067 1068 // Interrupt any blocked threads after a grace period. 1069 final WakeableSleeper sleeper = new WakeableSleeper(); 1070 sleeper.sleep(1000); 1071 mgr.shutdown(); 1072 1073 for (final QueryRateThread t : threads) 1074 { 1075 final ResultCode r = t.waitForShutdown(); 1076 if (resultCode == ResultCode.SUCCESS) 1077 { 1078 resultCode = r; 1079 } 1080 } 1081 1082 return resultCode; 1083 } 1084 1085 1086 1087 /** 1088 * Creates the SSLUtil instance to use for secure communication. 1089 * 1090 * @return The SSLUtil instance to use for secure communication, or 1091 * {@code null} if secure communication is not needed. 1092 * 1093 * @throws LDAPException If a problem occurs while creating the SSLUtil 1094 * instance. 1095 */ 1096 private SSLUtil createSSLUtil() 1097 throws LDAPException 1098 { 1099 if (useSSL.isPresent()) 1100 { 1101 KeyManager keyManager = null; 1102 if (keyStorePath.isPresent()) 1103 { 1104 char[] pw = null; 1105 if (keyStorePassword.isPresent()) 1106 { 1107 pw = keyStorePassword.getValue().toCharArray(); 1108 } 1109 else if (keyStorePasswordFile.isPresent()) 1110 { 1111 try 1112 { 1113 pw = keyStorePasswordFile.getNonBlankFileLines().get(0). 1114 toCharArray(); 1115 } 1116 catch (Exception e) 1117 { 1118 Debug.debugException(e); 1119 throw new LDAPException(ResultCode.LOCAL_ERROR, 1120 ERR_SCIM_TOOL_CANNOT_READ_KEY_STORE_PASSWORD.get( 1121 getExceptionMessage(e)), e); 1122 } 1123 } 1124 1125 try 1126 { 1127 keyManager = new KeyStoreKeyManager(keyStorePath.getValue(), pw, 1128 keyStoreFormat.getValue(), certificateNickname.getValue()); 1129 } 1130 catch (Exception e) 1131 { 1132 Debug.debugException(e); 1133 throw new LDAPException(ResultCode.LOCAL_ERROR, 1134 ERR_SCIM_TOOL_CANNOT_CREATE_KEY_MANAGER.get( 1135 getExceptionMessage(e)), e); 1136 } 1137 } 1138 1139 TrustManager trustManager; 1140 if (trustAll.isPresent()) 1141 { 1142 trustManager = new TrustAllTrustManager(false); 1143 } 1144 else if (trustStorePath.isPresent()) 1145 { 1146 char[] pw = null; 1147 if (trustStorePassword.isPresent()) 1148 { 1149 pw = trustStorePassword.getValue().toCharArray(); 1150 } 1151 else if (trustStorePasswordFile.isPresent()) 1152 { 1153 try 1154 { 1155 pw = trustStorePasswordFile.getNonBlankFileLines().get(0). 1156 toCharArray(); 1157 } 1158 catch (Exception e) 1159 { 1160 Debug.debugException(e); 1161 throw new LDAPException(ResultCode.LOCAL_ERROR, 1162 ERR_SCIM_TOOL_CANNOT_READ_TRUST_STORE_PASSWORD.get( 1163 getExceptionMessage(e)), e); 1164 } 1165 } 1166 1167 trustManager = new TrustStoreTrustManager(trustStorePath.getValue(), pw, 1168 trustStoreFormat.getValue(), true); 1169 } 1170 else 1171 { 1172 trustManager = promptTrustManager.get(); 1173 if (trustManager == null) 1174 { 1175 final PromptTrustManager m = new PromptTrustManager(); 1176 promptTrustManager.compareAndSet(null, m); 1177 trustManager = promptTrustManager.get(); 1178 } 1179 } 1180 1181 return new SSLUtil(keyManager, trustManager); 1182 } 1183 else 1184 { 1185 return null; 1186 } 1187 } 1188}