KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sun > slamd > example > TemplateBasedAddAndDelRateJobClass


1 /*
2  * Sun Public License
3  *
4  * The contents of this file are subject to the Sun Public License Version
5  * 1.0 (the "License"). You may not use this file except in compliance with
6  * the License. A copy of the License is available at http://www.sun.com/
7  *
8  * The Original Code is the SLAMD Distributed Load Generation Engine.
9  * The Initial Developer of the Original Code is Neil A. Wilson.
10  * Portions created by Neil A. Wilson are Copyright (C) 2004.
11  * Some preexisting portions Copyright (C) 2002-2004 Sun Microsystems, Inc.
12  * All Rights Reserved.
13  *
14  * Contributor(s): Neil A. Wilson
15  */

16 package com.sun.slamd.example;
17
18
19
20 import java.io.*;
21 import java.security.*;
22 import java.util.*;
23 import netscape.ldap.*;
24 import netscape.ldap.controls.*;
25 import netscape.ldap.factory.*;
26 import com.sun.slamd.job.*;
27 import com.sun.slamd.parameter.*;
28 import com.sun.slamd.stat.*;
29
30
31
32 /**
33  * This class implements a SLAMD job class for performing repeated add
34  * operations against an LDAP directory server, and then deleting the entries.
35  * that were added. The entries added will be based on a provided template that
36  * can be used to customize their contents, and whenever an entry is added it
37  * will be stored in an internal list so that it can be deleted again later.
38  * It is not necessarily possible to programatically determine the DNs of the
39  * entries that were added because they could contain random strings.
40  *
41  *
42  * @author Neil A. Wilson
43  */

44 public class TemplateBasedAddAndDelRateJobClass
45        extends JobClass
46 {
47   /**
48    * The system property used to specify the location of the JSSE key store.
49    */

50   public static final String JavaDoc SSL_KEY_STORE_PROPERTY =
51        "javax.net.ssl.keyStore";
52
53
54
55   /**
56    * The system property used to specify the password for the JSSE key store.
57    */

58   public static final String JavaDoc SSL_KEY_PASSWORD_PROPERTY =
59        "javax.net.ssl.keyStorePassword";
60
61
62
63   /**
64    * The system property used to specify the location of the JSSE trust store.
65    */

66   public static final String JavaDoc SSL_TRUST_STORE_PROPERTY =
67        "javax.net.ssl.trustStore";
68
69
70
71   /**
72    * The system property used to specify the password for the JSSE trust store.
73    */

74   public static final String JavaDoc SSL_TRUST_PASSWORD_PROPERTY =
75        "javax.net.ssl.trustStorePassword";
76
77
78
79   /**
80    * The display name for the stat tracker that will be used to track the time
81    * required to perform each add.
82    */

83   public static final String JavaDoc STAT_TRACKER_ADD_TIME = "Add Time (ms)";
84
85
86
87   /**
88    * The display name for the stat tracker that will be used to track the number
89    * of adds performed.
90    */

91   public static final String JavaDoc STAT_TRACKER_ADD_COUNT = "Adds Performed";
92
93
94
95   /**
96    * The display name for the stat tracker that will be used to accumulate the
97    * number of adds performed.
98    */

99   public static final String JavaDoc STAT_TRACKER_ADD_TOTAL = "Total Adds";
100
101
102
103   /**
104    * The display name for the stat tracker that will be used to track the result
105    * codes for the add operations.
106    */

107   public static final String JavaDoc STAT_TRACKER_ADD_RESULT_CODES = "Add Result Codes";
108
109
110
111   /**
112    * The display name for the stat tracker that will be used to track the time
113    * required to perform each delete.
114    */

115   public static final String JavaDoc STAT_TRACKER_DELETE_TIME = "Delete Time (ms)";
116
117
118
119   /**
120    * The display name for the stat tracker that will be used to track the number
121    * of deletes performed.
122    */

123   public static final String JavaDoc STAT_TRACKER_DELETE_COUNT = "Deletes Performed";
124
125
126
127   /**
128    * The display name for the stat tracker that will be used to accumulate the
129    * number of deletes performed.
130    */

131   public static final String JavaDoc STAT_TRACKER_DELETE_TOTAL = "Total Deletes";
132
133
134
135   /**
136    * The display name for the stat tracker that will be used to track the result
137    * codes for the delete operations.
138    */

139   public static final String JavaDoc STAT_TRACKER_DELETE_RESULT_CODES =
140        "Delete Result Codes";
141
142
143
144   /**
145    * The set of characters that should be included in numeric values.
146    */

147   public static final char[] NUMERIC_CHARS = "0123456789".toCharArray();
148
149
150
151   /**
152    * The set of characters that should be included in alphabetic values.
153    */

154   public static final char[] ALPHA_CHARS =
155        "abcdefghijklmnopqrstuvwxyz".toCharArray();
156
157
158
159   /**
160    * The set of characters that should be included in alphanumeric values.
161    */

162   public static final char[] ALPHANUMERIC_CHARS =
163        "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
164
165
166
167   /**
168    * The set of characters that should be included in hexadecimal values.
169    */

170   public static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
171
172
173
174   /**
175    * The set of characters that should be included in base64 values.
176    */

177   public static final char[] BASE64_CHARS =
178        ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
179         "0123456789+/").toCharArray();
180
181
182
183   /**
184    * The set of months that will be used if the name of a month is required.
185    */

186   public static final String JavaDoc[] MONTH_NAMES =
187   {
188     "January",
189     "February",
190     "March",
191     "April",
192     "May",
193     "June",
194     "July",
195     "August",
196     "September",
197     "October",
198     "November",
199     "December"
200   };
201
202
203
204   /**
205    * The text of the template that will be used by default.
206    */

207   public static final String JavaDoc[] DEFAULT_TEMPLATE_LINES = new String JavaDoc[]
208   {
209     "objectClass: top",
210     "objectClass: person",
211     "objectClass: organizationalPerson",
212     "objectClass: inetOrgPerson",
213     "employeeNumber: <entrynumber>",
214     "uid: user.{employeeNumber}",
215     "givenName: User",
216     "sn: {employeeNumber}",
217     "cn: User {sn}",
218     "initials: {givenName:1}{sn:1}",
219     "mail: {uid}@example.com",
220     "userPassword: password",
221     "telephoneNumber: <random:telephone>",
222     "mobile: <random:telephone>",
223     "pager: <random:telephone>",
224     "homePhone: <random:telephone>"
225   };
226
227
228
229   // The parameter that indicates whether the client should trust any SSL cert.
230
BooleanParameter blindTrustParameter =
231     new BooleanParameter("blind_trust", "Blindly Trust Any Certificate",
232                          "Indicates whether the client should blindly trust " +
233                          "any certificate presented by the server, or " +
234                          "whether the key and trust stores should be used.",
235                          true);
236
237   // The parameter that indicates whether the server should clean up any
238
// tombstones that may be left in the directory after performing the deletes.
239
BooleanParameter cleanTombstonesParameter =
240        new BooleanParameter("clean_tombstones", "Clean Up Tombstones",
241                             "Indicates whether the client should clean up "+
242                             "tombstones that may be in the database after " +
243                             "the delete has completed. This should only be " +
244                             "used for improving the reliability of the " +
245                             "testing in a replicated environment and should " +
246                             "never be used in a production server.", false);
247
248   // The parameter that indicates whether to disconnect after each add
249
BooleanParameter disconnectParameter =
250        new BooleanParameter("disconnect", "Always Disconnect",
251                             "Indicates whether to close the connection after " +
252                             "each add", false);
253
254   // The parameter that indicates whether the connection should use SSL
255
BooleanParameter useSSLParameter =
256     new BooleanParameter("usessl", "Use SSL",
257                          "Indicates whether SSL should be used for all " +
258                          "communication with the directory server", false);
259
260   // The parameter that indicates the delay that should be used between each
261
// request sent by a thread.
262
IntegerParameter delayParameter =
263        new IntegerParameter("delay", "Time Between Requests (ms)",
264                             "Specifies the length of time in milliseconds " +
265                             "each thread should wait between add " +
266                             "requests. Note that this delay will be " +
267                             "between consecutive requests and not between " +
268                             "the response of one operation and the request " +
269                             "for the next. If an add takes longer than " +
270                             "this length of time, then there will be no delay.",
271                             true, 0, true, 0, false, 0);
272
273   // The parameter that indicates the delay that should be used between the end
274
// of the adds and the beginning of the deletes.
275
IntegerParameter deleteDelayParameter =
276        new IntegerParameter("delete_delay", "Time Between Adds and Deletes (s)",
277                             "Specifies the length of time in seconds that " +
278                             "the job should sleep after all threads have " +
279                             "completed the add operations before starting to " +
280                             "process the deletes.", true, 0, true, 0, false, 0);
281
282   // The parameter that indicates the port number for the directory server
283
IntegerParameter portParameter =
284        new IntegerParameter("ldapport", "Directory Server Port",
285                             "The port number for the LDAP directory server",
286                             true, 389, true, 1, true, 65535);
287
288   // The parameter that specifies the starting number for the entries created.
289
IntegerParameter startValueParameter =
290        new IntegerParameter("start_value", "Initial Entry Value Number",
291                             "The number to use as the value of the " +
292                             "entryNumber tag for the first entry to create",
293                             true, 1, false, 0, false, 0);
294
295   // The parameter that specifies the ending number for the entries created.
296
IntegerParameter endValueParameter =
297        new IntegerParameter("end_value", "Final Entry Value Number",
298                             "The number to use as the value of the " +
299                             "entryNumber tag for the last entry to create",
300                             false, 0, false, 0, false, 0);
301
302   // The parameter that indicates the maximum time limit for add operations.
303
IntegerParameter timeLimitParameter =
304        new IntegerParameter("time_limit", "Add Time Limit",
305                             "The maximum length of time in seconds that the " +
306                             "thread should wait for an operation to be " +
307                             "performed before cancelling it and trying " +
308                             "another.", false, 0, true, 0, false, 0);
309
310   // The parameter that indicates the delay to use before cleaning up
311
// tombstones.
312
IntegerParameter tombstoneDelayParameter =
313       new IntegerParameter("tombstone_delay",
314                            "Delay Before Cleaning Tombstones (s)",
315                            "Specifies the length of time in seconds that the " +
316                            "job should wait after completing the deletes " +
317                            "before starting the tombstone cleanup.", true, 30,
318                            true, 0, false, 0);
319
320   // The template to use to create the entries.
321
MultiLineTextParameter templateParameter =
322        new MultiLineTextParameter("template", "Entry Template",
323                                   "The template to use when creating the " +
324                                   "entries. Consult the job reference guide " +
325                                   "for a description of the tags that may be " +
326                                   "used in the template",
327                                   DEFAULT_TEMPLATE_LINES, true);
328
329   // The placeholder parameter used as a spacer in the admin interface.
330
PlaceholderParameter placeholder = new PlaceholderParameter();
331
332   // The parameter that indicates the DN to use when binding to the server
333
StringParameter bindDNParameter =
334        new StringParameter("binddn", "Bind DN",
335                            "The DN to use to bind to the server", false, "");
336
337   // The parameter that indicates the base below which entries will be added.
338
StringParameter baseDNParameter =
339        new StringParameter("basedn", "Base DN ",
340                            "The base below which to add the entries",
341                            true, "");
342
343   // The parameter that indicates the address of the directory server
344
StringParameter hostParameter =
345        new StringParameter("ldaphost", "Directory Server Host",
346                            "The DNS hostname or IP address of the LDAP " +
347                            "directory server", true, "");
348
349   // The parameter that indicates the DN to use to proxy the adds.
350
StringParameter proxyAsDNParameter =
351        new StringParameter("proxy_as_dn", "Proxy As DN",
352                            "The DN of the user whose credentials should be " +
353                            "used to perform the adds through the use " +
354                            "of the proxied authorization control.", false, "");
355
356   // The parameter that indicates the RDN attribute for the new entries.
357
StringParameter rdnAttrParameter =
358        new StringParameter("rdn_attr", "RDN Attribute",
359                            "The RDN attribute to use when creating the " +
360                            "entries.", true, "uid");
361
362   // The parameter that specifies the location of the SSL key store
363
StringParameter keyStoreParameter =
364     new StringParameter("sslkeystore", "SSL Key Store",
365                         "The path to the JSSE key store to use for an " +
366                         "SSL-based connection", false, "");
367
368   // The parameter that specifies the location of the SSL trust store
369
StringParameter trustStoreParameter =
370     new StringParameter("ssltruststore", "SSL Trust Store",
371                         "The path to the JSSE trust store to use for an " +
372                         "SSL-based connection", false, "");
373
374   // The parameter that indicates the bind password
375
PasswordParameter bindPWParameter =
376        new PasswordParameter("bindpw", "Bind Password",
377                              "The password for the bind DN", false, "");
378
379   // The parameter that specifies the password for the SSL key store
380
PasswordParameter keyPWParameter =
381     new PasswordParameter("sslkeypw", "SSL Key Store Password",
382                           "The password for the JSSE key store", false, "");
383
384   // The parameter that specifies the password for the SSL key store
385
PasswordParameter trustPWParameter =
386     new PasswordParameter("ssltrustpw", "SSL Trust Store Password",
387                           "The password for the JSSE trust store", false, "");
388
389
390   // Instance variables that correspond to the parameter values
391
static boolean alwaysDisconnect;
392   static boolean blindTrust;
393   static boolean cleanTombstones;
394   static boolean useProxyAuth;
395   static boolean useSSL;
396   static int deleteDelay;
397   static int endValue;
398   static int ldapPort;
399   static int nextValue;
400   static int startValue;
401   static int timeLimit;
402   static int tombstoneDelay;
403   static long delay;
404   static String JavaDoc baseDN;
405   static String JavaDoc bindDN;
406   static String JavaDoc bindPassword;
407   static String JavaDoc ldapHost;
408   static String JavaDoc lowerRDNAttr;
409   static String JavaDoc proxyAsDN;
410   static String JavaDoc rdnAttr;
411   static String JavaDoc sslKeyStore;
412   static String JavaDoc sslKeyPassword;
413   static String JavaDoc sslTrustStore;
414   static String JavaDoc sslTrustPassword;
415   static String JavaDoc[] attrNames;
416   static String JavaDoc[] attrValues;
417   static String JavaDoc[] lowerNames;
418   static String JavaDoc[] separators;
419   static String JavaDoc[] templateLines;
420
421
422   // The connection to the directory server over which the adds will be
423
// performed.
424
LDAPConnection conn;
425
426
427   // Variables used for status counters
428
AccumulatingTracker totalAdds;
429   AccumulatingTracker totalDeletes;
430   CategoricalTracker addResultCodes;
431   CategoricalTracker deleteResultCodes;
432   IncrementalTracker addCount;
433   IncrementalTracker deleteCount;
434   TimeTracker addTime;
435   TimeTracker deleteTime;
436
437
438   // One random number generator for use throughout the client and another to
439
// use for only the current thread.
440
static Random parentRandom = new Random();
441   static String JavaDoc guidBase = null;
442   Random random;
443   char[] chars5000;
444
445
446   // A list of the DNs of the entries created by this thread. This will be used
447
// to determine which entries to delete.
448
ArrayList entriesAdded;
449
450
451   // Variables used for the reference counter that indicates how many threads
452
// are currently processing add operations.
453
static int numActiveAddThreads;
454   static Object JavaDoc addThreadMutex;
455
456
457
458
459   /**
460    * The default constructor used to create a new instance of the add thread.
461    * The only thing it should do is to invoke the superclass constructor. All
462    * other initialization should be performed in the <CODE>initialize</CODE>
463    * method.
464    */

465   public TemplateBasedAddAndDelRateJobClass()
466   {
467     super();
468
469     templateParameter.setVisibleRows(10);
470     templateParameter.setVisibleColumns(80);
471     chars5000 = new char[5000];
472     random = new Random(parentRandom.nextLong());
473
474     if (guidBase == null)
475     {
476       guidBase = generateRandomValue(HEX_CHARS, 12);
477     }
478   }
479
480
481
482   /**
483    * Retrieves the name of the job performed by this job thread.
484    *
485    * @return The name of the job performed by this job thread.
486    */

487   public String JavaDoc getJobName()
488   {
489     return "LDAP Template-Based Add and Delete Rate";
490   }
491
492
493
494   /**
495    * Retrieves a description of the job performed by this job thread.
496    *
497    * @return A description of the job performed by this job thread.
498    */

499   public String JavaDoc getJobDescription()
500   {
501     return "This job can be used to perform repeated add operations against " +
502            "an LDAP directory server, followed by repeated delete " +
503            "operations. The entries created will be based on a user-defined " +
504            "template.";
505   }
506
507
508
509   /**
510    * Retrieves the name of the category in which this job class exists. This is
511    * used to help arrange the job classes in the administrative interface.
512    *
513    * @return The name of the category in which this job class exists.
514    */

515   public String JavaDoc getJobCategoryName()
516   {
517     return "LDAP";
518   }
519
520
521
522   /**
523    * Overrides the number of clients that may be used for this job to ensure
524    * that only a single client will be used.
525    *
526    * @return The number of clients that will always be used for this job.
527    */

528   public int overrideNumClients()
529   {
530     return 1;
531   }
532
533
534
535   /**
536    * Retrieve a parameter list that can be used to determine all of the
537    * customizeable options that are available for this job.
538    *
539    * @return A parameter list that can be used to determine all of the
540    * customizeable options that are available for this job.
541    */

542   public ParameterList getParameterStubs()
543   {
544     Parameter[] parameters = new Parameter[]
545     {
546       placeholder,
547       hostParameter,
548       portParameter,
549       bindDNParameter,
550       bindPWParameter,
551       proxyAsDNParameter,
552       placeholder,
553       baseDNParameter,
554       rdnAttrParameter,
555       startValueParameter,
556       endValueParameter,
557       templateParameter,
558       placeholder,
559       deleteDelayParameter,
560       timeLimitParameter,
561       delayParameter,
562       placeholder,
563       useSSLParameter,
564       blindTrustParameter,
565       keyStoreParameter,
566       keyPWParameter,
567       trustStoreParameter,
568       trustPWParameter,
569       placeholder,
570       disconnectParameter,
571       cleanTombstonesParameter,
572       tombstoneDelayParameter
573     };
574
575     return new ParameterList(parameters);
576   }
577
578
579
580   /**
581    * Retrieves the set of stat trackers that will be maintained by this job
582    * class. The stat trackers returned by this method do not have to actually
583    * contain any statistics -- the display name and stat tracker class should
584    * be the only information that callers of this method should rely upon. Note
585    * that this list can be different from the list of statistics actually
586    * collected by the job in some cases (e.g., if the job may not return all the
587    * stat trackers it advertises in all cases, or if the job may return stat
588    * trackers that it did not advertise), but it is a possibility that only the
589    * stat trackers returned by this method will be accessible for some features
590    * in the SLAMD server.
591    *
592    * @param clientID The client ID that should be used for the
593    * returned stat trackers.
594    * @param threadID The thread ID that should be used for the
595    * returned stat trackers.
596    * @param collectionInterval The collection interval that should be used for
597    * the returned stat trackers.
598    *
599    * @return The set of stat trackers that will be maintained by this job
600    * class.
601    */

602   public StatTracker[] getStatTrackerStubs(String JavaDoc clientID, String JavaDoc threadID,
603                                            int collectionInterval)
604   {
605     return new StatTracker[]
606     {
607       new IncrementalTracker(clientID, threadID, STAT_TRACKER_ADD_COUNT,
608                              collectionInterval),
609       new TimeTracker(clientID, threadID, STAT_TRACKER_ADD_TIME,
610                       collectionInterval),
611       new CategoricalTracker(clientID, threadID, STAT_TRACKER_ADD_RESULT_CODES,
612                              collectionInterval),
613       new IncrementalTracker(clientID, threadID, STAT_TRACKER_DELETE_COUNT,
614                              collectionInterval),
615       new TimeTracker(clientID, threadID, STAT_TRACKER_DELETE_TIME,
616                       collectionInterval),
617       new CategoricalTracker(clientID, threadID,
618                              STAT_TRACKER_DELETE_RESULT_CODES,
619                              collectionInterval)
620     };
621   }
622
623
624
625   /**
626    * Retrieves the stat trackers that are maintained for this job thread.
627    *
628    * @return The stat trackers that are maintained for this job thread.
629    */

630   public StatTracker[] getStatTrackers()
631   {
632     return new StatTracker[]
633     {
634       addCount,
635       addTime,
636       addResultCodes,
637       totalAdds,
638       deleteCount,
639       deleteTime,
640       deleteResultCodes,
641       totalDeletes
642     };
643   }
644
645
646
647   /**
648    * Provides a means of validating the information used to schedule the job,
649    * including the scheduling information and list of parameters.
650    *
651    * @param numClients The number of clients that should be used to
652    * run the job.
653    * @param threadsPerClient The number of threads that should be created on
654    * each client to run the job.
655    * @param threadStartupDelay The delay in milliseconds that should be used
656    * when starting the client threads.
657    * @param startTime The time that the job should start running.
658    * @param stopTime The time that the job should stop running.
659    * @param duration The maximum length of time in seconds that the
660    * job should be allowed to run.
661    * @param collectionInterval The collection interval that should be used
662    * when gathering statistics for the job.
663    * @param parameters The set of parameters provided to this job that
664    * can be used to customize its behavior.
665    *
666    * @throws InvalidValueException If the provided information is not
667    * appropriate for running this job.
668    */

669   public void validateJobInfo(int numClients, int threadsPerClient,
670                               int threadStartupDelay, Date startTime,
671                               Date stopTime, int duration,
672                               int collectionInterval, ParameterList parameters)
673          throws InvalidValueException
674   {
675     // Make sure the job was only scheduled for a single client.
676
if (numClients != 1)
677     {
678       throw new InvalidValueException("A template-based add and delete job " +
679                                       "may only run on a single client.");
680     }
681
682     // Make sure the template provided is parseable.
683
MultiLineTextParameter templateParam =
684          parameters.getMultiLineTextParameter(templateParameter.getName());
685     if (templateParam == null)
686     {
687       throw new InvalidValueException("No value provided for required " +
688                                       "parameter " +
689                                       templateParameter.getDisplayName());
690     }
691
692     templateLines = templateParam.getNonBlankLines();
693     if (templateLines.length == 0)
694     {
695       throw new InvalidValueException("No value provided for required " +
696                                       "parameter " +
697                                       templateParameter.getDisplayName());
698     }
699
700     StringParameter rdnAttrParam =
701          parameters.getStringParameter(rdnAttrParameter.getName());
702     if (rdnAttrParam == null)
703     {
704       throw new InvalidValueException("No value provided for required " +
705                                       "parameter " +
706                                       rdnAttrParameter.getDisplayName());
707     }
708     rdnAttr = rdnAttrParam.getStringValue();
709     lowerRDNAttr = rdnAttr.toLowerCase();
710
711     try
712     {
713       validateTemplate();
714     }
715     catch (UnableToRunException utre)
716     {
717       throw new InvalidValueException(utre.getMessage(), utre);
718     }
719   }
720
721
722
723   /**
724    * Indicates whether this job class implements logic that makes it possible to
725    * test the validity of job parameters before scheduling the job for execution
726    * (e.g., to see if the server is reachable using the information provided).
727    *
728    * @return <CODE>true</CODE> if this job provides a means of testing the job
729    * parameters, or <CODE>false</CODE> if not.
730    */

731   public boolean providesParameterTest()
732   {
733     return true;
734   }
735
736
737
738   /**
739    * Provides a means of testing the provided job parameters to determine
740    * whether they are valid (e.g., to see if the server is reachable) before
741    * scheduling the job for execution. This method will be executed by the
742    * SLAMD server system itself and not by any of the clients.
743    *
744    * @param parameters The job parameters to be tested.
745    * @param outputMessages The lines of output that were generated as part of
746    * the testing process. Each line of output should
747    * be added to this list as a separate string, and
748    * empty strings (but not <CODE>null</CODE> values)
749    * are allowed to provide separation between
750    * different messages. No formatting should be
751    * provided for these messages, however, since they
752    * may be displayed in either an HTML or plain text
753    * interface.
754    *
755    * @return <CODE>true</CODE> if the test completed successfully, or
756    * <CODE>false</CODE> if not. Note that even if the test did not
757    * complete successfully, the user will be presented with a warning
758    * but will still be allowed to schedule the job using the provided
759    * parameters. This is necessary because the parameters may still be
760    * valid even if the server couldn't validate them at the time the
761    * job was scheduled (e.g., if the server wasn't running or could not
762    * be reached by the SLAMD server even though it could be by the
763    * clients).
764    */

765   public boolean testJobParameters(ParameterList parameters,
766                                    ArrayList outputMessages)
767   {
768     // Get all the parameters that we might need to perform the test.
769
StringParameter hostParam =
770          parameters.getStringParameter(hostParameter.getName());
771     if ((hostParam == null) || (! hostParam.hasValue()))
772     {
773       outputMessages.add("ERROR: No directory server address was provided.");
774       return false;
775     }
776     String JavaDoc host = hostParam.getStringValue();
777
778
779     IntegerParameter portParam =
780          parameters.getIntegerParameter(portParameter.getName());
781     if ((portParam == null) || (! hostParam.hasValue()))
782     {
783       outputMessages.add("ERROR: No directory server port was provided.");
784       return false;
785     }
786     int port = portParam.getIntValue();
787
788
789     boolean useSSL = false;
790     BooleanParameter useSSLParam =
791          parameters.getBooleanParameter(useSSLParameter.getName());
792     if (useSSLParam != null)
793     {
794       useSSL = useSSLParam.getBooleanValue();
795     }
796
797
798     boolean blindTrust = true;
799     BooleanParameter blindTrustParam =
800          parameters.getBooleanParameter(blindTrustParameter.getName());
801     if (blindTrustParam != null)
802     {
803       blindTrust = blindTrustParam.getBooleanValue();
804     }
805
806
807     String JavaDoc keyStore = null;
808     StringParameter keyStoreParam =
809          parameters.getStringParameter(keyStoreParameter.getName());
810     if ((keyStoreParam != null) && keyStoreParam.hasValue())
811     {
812       keyStore = keyStoreParam.getStringValue();
813       File keyStoreFile = new File(keyStore);
814       if (useSSL && (! blindTrust) && (! keyStoreFile.exists()))
815       {
816         outputMessages.add("WARNING: Key store file \"" + keyStore +
817                            "\" not found on SLAMD server system. This test " +
818                            "will blindly trust any SSL certificate " +
819                            "presented by the directory server.");
820         outputMessages.add("");
821         blindTrust = true;
822       }
823       else
824       {
825         System.setProperty(SSL_KEY_STORE_PROPERTY, keyStore);
826       }
827     }
828
829
830     String JavaDoc keyStorePassword = "";
831     StringParameter keyPassParam =
832          parameters.getStringParameter(keyPWParameter.getName());
833     if ((keyPassParam != null) && keyPassParam.hasValue())
834     {
835       keyStorePassword = keyPassParam.getStringValue();
836       System.setProperty(SSL_KEY_PASSWORD_PROPERTY, keyStorePassword);
837     }
838
839
840     String JavaDoc trustStore = null;
841     StringParameter trustStoreParam =
842          parameters.getStringParameter(trustStoreParameter.getName());
843     if ((trustStoreParam != null) && trustStoreParam.hasValue())
844     {
845       trustStore = trustStoreParam.getStringValue();
846       File trustStoreFile = new File(trustStore);
847       if (useSSL && (! blindTrust) && (! trustStoreFile.exists()))
848       {
849         outputMessages.add("WARNING: trust store file \"" + trustStore +
850                            "\" not found on SLAMD server system. This test " +
851                            "will blindly trust any SSL certificate " +
852                            "presented by the directory server.");
853         outputMessages.add("");
854         blindTrust = true;
855       }
856       else
857       {
858         System.setProperty(SSL_TRUST_STORE_PROPERTY, trustStore);
859       }
860     }
861
862
863     String JavaDoc trustStorePassword = "";
864     StringParameter trustPassParam =
865          parameters.getStringParameter(trustPWParameter.getName());
866     if ((trustPassParam != null) && trustPassParam.hasValue())
867     {
868       trustStorePassword = trustPassParam.getStringValue();
869       System.setProperty(SSL_TRUST_PASSWORD_PROPERTY, trustStorePassword);
870     }
871
872
873     String JavaDoc bindDN = "";
874     StringParameter bindDNParam =
875          parameters.getStringParameter(bindDNParameter.getName());
876     if ((bindDNParam != null) && bindDNParam.hasValue())
877     {
878       bindDN = bindDNParam.getStringValue();
879     }
880
881
882     String JavaDoc bindPassword = "";
883     PasswordParameter bindPWParam =
884          parameters.getPasswordParameter(bindPWParameter.getName());
885     if ((bindPWParam != null) && bindPWParam.hasValue())
886     {
887       bindPassword = bindPWParam.getStringValue();
888     }
889
890
891     String JavaDoc proxyAsDN = null;
892     StringParameter proxyAsDNParam =
893          parameters.getStringParameter(proxyAsDNParameter.getName());
894     if ((proxyAsDNParam != null) && proxyAsDNParam.hasValue())
895     {
896       proxyAsDN = proxyAsDNParam.getStringValue();
897     }
898
899
900     StringParameter baseDNParam =
901          parameters.getStringParameter(baseDNParameter.getName());
902     if ((baseDNParam == null) || (! baseDNParam.hasValue()))
903     {
904       outputMessages.add("ERROR: No base DN was provided.");
905       return false;
906     }
907     String JavaDoc baseDN = baseDNParam.getStringValue();
908
909
910     // Create the LDAPConnection object that we will use to communicate with the
911
// directory server.
912
LDAPConnection conn;
913     if (useSSL)
914     {
915       if (blindTrust)
916       {
917         try
918         {
919           conn = new LDAPConnection(new JSSEBlindTrustSocketFactory());
920         }
921         catch (Exception JavaDoc e)
922         {
923           outputMessages.add("ERROR: Unable to instantiate the blind trust " +
924                              "socket factory for use in creating the SSL " +
925                              "connection: " + stackTraceToString(e));
926           return false;
927         }
928       }
929       else
930       {
931         conn = new LDAPConnection(new JSSESocketFactory(null));
932       }
933     }
934     else
935     {
936       conn = new LDAPConnection();
937     }
938
939
940     // Attempt to establish a connection to the directory server.
941
try
942     {
943       if (useSSL)
944       {
945         outputMessages.add("Attempting to establish an SSL-based connection " +
946                            "to " + host + ":" + port + "....");
947       }
948       else
949       {
950         outputMessages.add("Attempting to establish a connection to " + host +
951                            ":" + port + "....");
952       }
953       conn.connect(host, port);
954       outputMessages.add("Connected successfully.");
955       outputMessages.add("");
956     }
957     catch (Exception JavaDoc e)
958     {
959       outputMessages.add("ERROR: Unable to connect to the directory " +
960                          "server: " + stackTraceToString(e));
961       return false;
962     }
963
964
965     // Attempt to bind to the directory server using the bind DN and password.
966
try
967     {
968       outputMessages.add("Attempting to perform an LDAPv3 bind to the " +
969                          "directory server with a DN of '" + bindDN + "'....");
970       conn.bind(3, bindDN, bindPassword);
971       outputMessages.add("Bound successfully.");
972       outputMessages.add("");
973     }
974     catch (Exception JavaDoc e)
975     {
976       try
977       {
978         conn.disconnect();
979       } catch (Exception JavaDoc e2) {}
980
981       outputMessages.add("ERROR: Unable to bind to the directory server: " +
982                          stackTraceToString(e));
983       return false;
984     }
985
986
987     // If a proxy user was specified, make sure that it exists.
988
if (proxyAsDN != null)
989     {
990       try
991       {
992         outputMessages.add("Checking to make sure that the proxied user '" +
993                            proxyAsDN + "' exists in the directory....");
994         LDAPEntry proxyUserEntry = conn.read(proxyAsDN, new String JavaDoc[] { "1.1" });
995         if (proxyUserEntry == null)
996         {
997           try
998           {
999             conn.disconnect();
1000          } catch (Exception JavaDoc e2) {}
1001
1002          outputMessages.add("ERROR: Unable to retrieve the proxied user's " +
1003                             "entry.");
1004          return false;
1005        }
1006        else
1007        {
1008          outputMessages.add("Successfully read the proxied user's entry.");
1009          outputMessages.add("");
1010        }
1011      }
1012      catch (Exception JavaDoc e)
1013      {
1014        try
1015        {
1016          conn.disconnect();
1017        } catch (Exception JavaDoc e2) {}
1018
1019        outputMessages.add("ERROR: Unable to retrieve the proxied user's " +
1020                           "entry: " + stackTraceToString(e));
1021        return false;
1022      }
1023    }
1024
1025
1026    // Make sure that the entry specified as the base DN exists.
1027
try
1028    {
1029      outputMessages.add("Checking to make sure that the base DN entry '" +
1030                         baseDN + "' exists in the directory....");
1031      LDAPEntry baseDNEntry = conn.read(baseDN, new String JavaDoc[] { "1.1" });
1032      if (baseDNEntry == null)
1033      {
1034        try
1035        {
1036          conn.disconnect();
1037        } catch (Exception JavaDoc e2) {}
1038
1039        outputMessages.add("ERROR: Unable to retrieve the base DN entry.");
1040        return false;
1041      }
1042      else
1043      {
1044        outputMessages.add("Successfully read the base DN entry.");
1045        outputMessages.add("");
1046      }
1047    }
1048    catch (Exception JavaDoc e)
1049    {
1050      try
1051      {
1052        conn.disconnect();
1053      } catch (Exception JavaDoc e2) {}
1054
1055      outputMessages.add("ERROR: Unable to retrieve the base DN entry: " +
1056                         stackTraceToString(e));
1057      return false;
1058    }
1059
1060
1061    // At this point, all tests have passed. Close the connection and return
1062
// true.
1063
try
1064    {
1065      conn.disconnect();
1066    } catch (Exception JavaDoc e) {}
1067
1068    outputMessages.add("All tests completed successfully.");
1069    return true;
1070  }
1071
1072
1073
1074  /**
1075   * Initializes all of the instance variables that correspond to job
1076   * parameters.
1077   *
1078   * @param clientID The client ID for the current client.
1079   * @param parameters The set of parameters that have been defined for this
1080   * job.
1081   *
1082   * @throws UnableToRunException If any part of the initialization fails.
1083   */

1084  public void initializeClient(String JavaDoc clientID, ParameterList parameters)
1085         throws UnableToRunException
1086  {
1087    // Get the address of the target directory server
1088
ldapHost = null;
1089    hostParameter = parameters.getStringParameter(hostParameter.getName());
1090    if (hostParameter != null)
1091    {
1092      ldapHost = hostParameter.getStringValue();
1093    }
1094
1095    // Get the port for the target directory server
1096
ldapPort = 389;
1097    portParameter = parameters.getIntegerParameter(portParameter.getName());
1098    if (portParameter != null)
1099    {
1100      ldapPort = portParameter.getIntValue();
1101    }
1102
1103    // Get the bind DN for the target directory server
1104
bindDN = "";
1105    bindDNParameter = parameters.getStringParameter(bindDNParameter.getName());
1106    if (bindDNParameter != null)
1107    {
1108      bindDN = bindDNParameter.getStringValue();
1109    }
1110
1111    // Get the bind password for the target directory server
1112
bindPassword = "";
1113    bindPWParameter =
1114         parameters.getPasswordParameter(bindPWParameter.getName());
1115    if (bindPWParameter != null)
1116    {
1117      bindPassword = bindPWParameter.getStringValue();
1118    }
1119
1120    // Get the DN of the proxy as user.
1121
useProxyAuth = false;
1122    proxyAsDNParameter =
1123         parameters.getStringParameter(proxyAsDNParameter.getName());
1124    if ((proxyAsDNParameter != null) && (proxyAsDNParameter.hasValue()))
1125    {
1126      useProxyAuth = true;
1127      proxyAsDN = proxyAsDNParameter.getStringValue();
1128    }
1129
1130    // Get the base DN under which to add the entries.
1131
baseDN = null;
1132    baseDNParameter = parameters.getStringParameter(baseDNParameter.getName());
1133    if ((baseDNParameter != null) && (baseDNParameter.hasValue()))
1134    {
1135      baseDN = baseDNParameter.getStringValue();
1136    }
1137
1138    // Get the name of the RDN attribute to use.
1139
rdnAttr = "uid";
1140    rdnAttrParameter =
1141         parameters.getStringParameter(rdnAttrParameter.getName());
1142    if ((rdnAttrParameter != null) && (rdnAttrParameter.hasValue()))
1143    {
1144      rdnAttr = rdnAttrParameter.getStringValue().toLowerCase();
1145    }
1146    lowerRDNAttr = rdnAttr.toLowerCase();
1147
1148    // Get the initial value of the entry to create.
1149
startValue = 0;
1150    startValueParameter =
1151         parameters.getIntegerParameter(startValueParameter.getName());
1152    if ((startValueParameter != null) && (startValueParameter.hasValue()))
1153    {
1154      startValue = startValueParameter.getIntValue();
1155    }
1156    nextValue = startValue;
1157
1158    // Get the final RDN value to create.
1159
endValue = Integer.MAX_VALUE;
1160    endValueParameter =
1161         parameters.getIntegerParameter(endValueParameter.getName());
1162    if ((endValueParameter != null) && (endValueParameter.hasValue()))
1163    {
1164      endValue = endValueParameter.getIntValue();
1165      if (endValue <= 0)
1166      {
1167        endValue = Integer.MAX_VALUE;
1168      }
1169    }
1170
1171    // Get the template to use to create the entry.
1172
templateLines = DEFAULT_TEMPLATE_LINES;
1173    templateParameter =
1174         parameters.getMultiLineTextParameter(templateParameter.getName());
1175    if ((templateParameter != null) && templateParameter.hasValue())
1176    {
1177      templateLines = templateParameter.getNonBlankLines();
1178    }
1179    validateTemplate();
1180
1181    // Get the delay between adds and deletes.
1182
deleteDelay = 0;
1183    deleteDelayParameter =
1184         parameters.getIntegerParameter(deleteDelayParameter.getName());
1185    if ((deleteDelayParameter != null) && deleteDelayParameter.hasValue())
1186    {
1187      deleteDelay = deleteDelayParameter.getIntValue();
1188    }
1189
1190    // Get the maximum add time limit.
1191
timeLimit = 0;
1192    timeLimitParameter =
1193         parameters.getIntegerParameter(timeLimitParameter.getName());
1194    if (timeLimitParameter != null)
1195    {
1196      timeLimit = timeLimitParameter.getIntValue();
1197    }
1198
1199    // Get the delay between requests.
1200
delay = 0;
1201    delayParameter = parameters.getIntegerParameter(delayParameter.getName());
1202    if (delayParameter != null)
1203    {
1204      delay = delayParameter.getIntValue();
1205    }
1206
1207    // Get the flag indicating whether we should use SSL or not
1208
useSSL = false;
1209    useSSLParameter = parameters.getBooleanParameter(useSSLParameter.getName());
1210    if (useSSLParameter != null)
1211    {
1212      useSSL = useSSLParameter.getBooleanValue();
1213    }
1214
1215    // If we are to use SSL, then get all the other SSL-related info
1216
if (useSSL)
1217    {
1218      // Whether to blindly trust any certificate.
1219
blindTrustParameter =
1220           parameters.getBooleanParameter(blindTrustParameter.getName());
1221      if (blindTrustParameter != null)
1222      {
1223        blindTrust = blindTrustParameter.getBooleanValue();
1224      }
1225
1226      // The location of the JSSE key store
1227
sslKeyStore = null;
1228      keyStoreParameter =
1229           parameters.getStringParameter(keyStoreParameter.getName());
1230      if ((keyStoreParameter != null) && (keyStoreParameter.hasValue()))
1231      {
1232        sslKeyStore = keyStoreParameter.getStringValue();
1233        System.setProperty(SSL_KEY_STORE_PROPERTY, sslKeyStore);
1234      }
1235
1236      // The JSSE key store password
1237
sslKeyPassword = null;
1238      keyPWParameter =
1239           parameters.getPasswordParameter(keyPWParameter.getName());
1240      if ((keyPWParameter != null) && (keyPWParameter.hasValue()))
1241      {
1242        sslKeyPassword = keyPWParameter.getStringValue();
1243        System.setProperty(SSL_KEY_PASSWORD_PROPERTY, sslKeyPassword);
1244      }
1245
1246      // The location of the JSSE trust store
1247
sslTrustStore = null;
1248      trustStoreParameter =
1249           parameters.getStringParameter(trustStoreParameter.getName());
1250      if ((trustStoreParameter != null) && (trustStoreParameter.hasValue()))
1251      {
1252        sslTrustStore = trustStoreParameter.getStringValue();
1253        System.setProperty(SSL_TRUST_STORE_PROPERTY, sslTrustStore);
1254      }
1255
1256      // The JSSE trust store password
1257
sslTrustPassword = null;
1258      trustPWParameter =
1259           parameters.getPasswordParameter(trustPWParameter.getName());
1260      if ((trustPWParameter != null) && (trustPWParameter.hasValue()))
1261      {
1262        sslTrustPassword = trustPWParameter.getStringValue();
1263        System.setProperty(SSL_TRUST_PASSWORD_PROPERTY, sslTrustPassword);
1264      }
1265    }
1266
1267    // Get the flag indicating whether we should disconnect after each op.
1268
alwaysDisconnect = false;
1269    disconnectParameter =
1270         parameters.getBooleanParameter(disconnectParameter.getName());
1271    if (disconnectParameter != null)
1272    {
1273      alwaysDisconnect = disconnectParameter.getBooleanValue();
1274    }
1275
1276    // Get the flag indicating whether we should clean up tombstones.
1277
cleanTombstones = false;
1278    cleanTombstonesParameter =
1279         parameters.getBooleanParameter(cleanTombstonesParameter.getName());
1280    if (cleanTombstonesParameter != null)
1281    {
1282      cleanTombstones = cleanTombstonesParameter.getBooleanValue();
1283    }
1284
1285    // Get the delay to use before cleaning up tombstones.
1286
tombstoneDelay = 0;
1287    tombstoneDelayParameter =
1288         parameters.getIntegerParameter(tombstoneDelayParameter.getName());
1289    if ((tombstoneDelayParameter != null) &&
1290        tombstoneDelayParameter.hasValue())
1291    {
1292      tombstoneDelay = tombstoneDelayParameter.getIntValue();
1293    }
1294
1295    addThreadMutex = new Object JavaDoc();
1296  }
1297
1298
1299
1300  /**
1301   * Initializes this job thread to be used to actually run the job on the
1302   * client. The provided parameter list should be processed to customize the
1303   * behavior of this job thread, and any other initialization that needs to be
1304   * done in order for the job to run should be performed here as well.
1305   *
1306   * @param clientID The client ID for this job thread.
1307   * @param threadID The thread ID for this job thread.
1308   * @param collectionInterval The length of time in seconds to use as the
1309   * statistics collection interval.
1310   * @param parameters The set of parameters provided to this job that
1311   * can be used to customize its behavior.
1312   *
1313   * @throws UnableToRunException If a problem occurs that prevents the thread
1314   * from being able to run properly.
1315   */

1316  public void initializeThread(String JavaDoc clientID, String JavaDoc threadID,
1317                               int collectionInterval, ParameterList parameters)
1318         throws UnableToRunException
1319  {
1320    // Set up the stat trackers
1321
addCount = new IncrementalTracker(clientID, threadID,
1322                                      STAT_TRACKER_ADD_COUNT,
1323                                      collectionInterval);
1324    addTime = new TimeTracker(clientID, threadID, STAT_TRACKER_ADD_TIME,
1325                              collectionInterval);
1326    addResultCodes = new CategoricalTracker(clientID, threadID,
1327                                            STAT_TRACKER_ADD_RESULT_CODES,
1328                                            collectionInterval);
1329    totalAdds = new AccumulatingTracker(clientID, threadID,
1330                                        STAT_TRACKER_ADD_TOTAL,
1331                                        collectionInterval);
1332    deleteCount = new IncrementalTracker(clientID, threadID,
1333                                         STAT_TRACKER_DELETE_COUNT,
1334                                         collectionInterval);
1335    deleteTime = new TimeTracker(clientID, threadID, STAT_TRACKER_DELETE_TIME,
1336                                 collectionInterval);
1337    deleteResultCodes = new CategoricalTracker(clientID, threadID,
1338                                               STAT_TRACKER_DELETE_RESULT_CODES,
1339                                               collectionInterval);
1340    totalDeletes = new AccumulatingTracker(clientID, threadID,
1341                                           STAT_TRACKER_DELETE_TOTAL,
1342                                           collectionInterval);
1343
1344
1345    // Enable real-time reporting of the data for these stat trackers.
1346
RealTimeStatReporter statReporter = getStatReporter();
1347    if (statReporter != null)
1348    {
1349      String JavaDoc jobID = getJobID();
1350      addCount.enableRealTimeStats(statReporter, jobID);
1351      addTime.enableRealTimeStats(statReporter, jobID);
1352      totalAdds.enableRealTimeStats(statReporter, jobID);
1353      deleteCount.enableRealTimeStats(statReporter, jobID);
1354      deleteTime.enableRealTimeStats(statReporter, jobID);
1355      totalDeletes.enableRealTimeStats(statReporter, jobID);
1356    }
1357
1358
1359    // If the connection is to use SSL, then establish a preliminary connection
1360
// now. The first connection can take a significant amount of time to
1361
// establish, and we want to get it out of the way early before the timer
1362
// starts (if a duration is specified). Don't worry about any exceptions
1363
// that may get thrown here because there's no easy way to report them back
1364
// but they will be repeated and handled when the job starts running anyway,
1365
// so no big deal.
1366
if (useSSL)
1367    {
1368      try
1369      {
1370        if (blindTrust)
1371        {
1372          conn = new LDAPConnection(new JSSEBlindTrustSocketFactory());
1373        }
1374        else
1375        {
1376          conn = new LDAPConnection(new JSSESocketFactory(null));
1377        }
1378        conn.connect(3, ldapHost, ldapPort, bindDN, bindPassword);
1379        conn.disconnect();
1380      }
1381      catch (Exception JavaDoc e) {}
1382    }
1383
1384
1385    // Initialize the array list that will be used to hold the DNs of the
1386
// entries created by this thread.
1387
entriesAdded = new ArrayList();
1388  }
1389
1390
1391
1392  /**
1393   * Perform the work of this job thread by establishing the connection(s) to
1394   * the directory server and issuing all the appropriate queries. The job will
1395   * continue until the specified number of iterations have been performed, the
1396   * stop time has been reached, the maximum duration has been reached, or the
1397   * SLAMD server indicates that a stop has been requested.
1398   */

1399  public void runJob()
1400  {
1401    // Set a variable that we can use to determine if the connection is alive or
1402
// not
1403
boolean connected = false;
1404
1405    // Set a variable that should be used to determine whether all the entries
1406
// have been added yet or not.
1407
boolean allAdded = false;
1408
1409    // Set a variable that can be used to determine how long we should sleep
1410
// between adds.
1411
long addStartTime = 0;
1412
1413    // Create a variable that we will use for the LDAP connection
1414
if (useSSL)
1415    {
1416      if (blindTrust)
1417      {
1418        try
1419        {
1420          conn = new LDAPConnection(new JSSEBlindTrustSocketFactory());
1421        }
1422        catch (LDAPException le)
1423        {
1424          logMessage(le.getMessage());
1425          indicateStoppedDueToError();
1426          return;
1427        }
1428      }
1429      else
1430      {
1431        conn = new LDAPConnection(new JSSESocketFactory(null));
1432      }
1433    }
1434    else
1435    {
1436      conn = new LDAPConnection();
1437    }
1438
1439
1440    // Increment the add reference counter.
1441
synchronized (addThreadMutex)
1442    {
1443      numActiveAddThreads++;
1444    }
1445
1446
1447    // Start the stat trackers for the add tests.
1448
addCount.startTracker();
1449    addTime.startTracker();
1450    addResultCodes.startTracker();
1451    totalAdds.startTracker();
1452
1453
1454    // Create a loop that will run until it needs to stop
1455
while ((! shouldStop()) && (! allAdded))
1456    {
1457      // If the connection is currently not connected, then establish it
1458
if (! connected)
1459      {
1460        try
1461        {
1462          conn.connect(3, ldapHost, ldapPort, bindDN, bindPassword);
1463          connected = true;
1464        }
1465        catch (LDAPException le)
1466        {
1467          logMessage("ERROR -- Could not connect to " + ldapHost + ":" +
1468                           ldapPort + " (" + le + ") -- aborting thread");
1469          addResultCodes.increment(String.valueOf(le.getLDAPResultCode()));
1470          indicateStoppedDueToError();
1471          break;
1472        }
1473      }
1474
1475      LDAPConstraints constraints = conn.getConstraints();
1476      if (useProxyAuth)
1477      {
1478        LDAPProxiedAuthControl proxyAuthControl =
1479             new LDAPProxiedAuthControl(proxyAsDN, true);
1480        constraints.setServerControls(proxyAuthControl);
1481      }
1482      constraints.setTimeLimit(1000 * timeLimit);
1483
1484
1485      // Create a flag used to determine if the add was successful.
1486
LDAPEntry entryToAdd = createEntry();
1487      if (entryToAdd == null)
1488      {
1489        allAdded = true;
1490      }
1491      else
1492      {
1493        // Record the current time as the start of the add.
1494
addTime.startTimer();
1495        if (delay > 0)
1496        {
1497          addStartTime = System.currentTimeMillis();
1498        }
1499
1500        // Perform the add
1501
int resultCode = LDAPException.SUCCESS;
1502        try
1503        {
1504          conn.add(entryToAdd, constraints);
1505          entriesAdded.add(entryToAdd.getDN());
1506        }
1507        catch (LDAPException le)
1508        {
1509          resultCode = le.getLDAPResultCode();
1510        }
1511
1512
1513        // Record the current time as the end of the add.
1514
addCount.increment();
1515        totalAdds.increment();
1516        addTime.stopTimer();
1517        addResultCodes.increment(String.valueOf(resultCode));
1518      }
1519
1520      // If the connection should be broken, then do so
1521
if (alwaysDisconnect)
1522      {
1523        try
1524        {
1525          conn.disconnect();
1526        } catch (LDAPException le) {}
1527        connected = false;
1528      }
1529
1530      // If we need to sleep, then do so
1531
if ((delay > 0) && (! shouldStop()))
1532      {
1533        long now = System.currentTimeMillis();
1534        long sleepTime = delay - (now - addStartTime);
1535        if (sleepTime > 0)
1536        {
1537          try
1538          {
1539            Thread.sleep(sleepTime);
1540          } catch (InterruptedException JavaDoc ie) {}
1541        }
1542      }
1543    }
1544
1545
1546    // Stop all the stat trackers for the add operations.
1547
addCount.stopTracker();
1548    addTime.stopTracker();
1549    addResultCodes.stopTracker();
1550    totalAdds.stopTracker();
1551
1552
1553    // If we need to stop, then do so now.
1554
if (shouldStop())
1555    {
1556      // If the connection is still established, then close it.
1557
try
1558      {
1559        conn.disconnect();
1560      } catch (LDAPException le) {}
1561
1562      // Decrement the add reference counter.
1563
synchronized (addThreadMutex)
1564      {
1565        numActiveAddThreads--;
1566      }
1567
1568      return;
1569    }
1570
1571
1572    // Decrement the add reference counter. If this is the last thread to
1573
// finish with the adds, then also sleep if necessary.
1574
synchronized (addThreadMutex)
1575    {
1576      if (numActiveAddThreads > 1)
1577      {
1578        numActiveAddThreads--;
1579      }
1580      else
1581      {
1582        try
1583        {
1584          Thread.sleep(deleteDelay * 1000);
1585        } catch (InterruptedException JavaDoc ie) {}
1586
1587        // Signal to all the other threads that the deletes can start now.
1588
numActiveAddThreads--;
1589      }
1590    }
1591
1592
1593    // Wait for the other threads to finish their adds.
1594
while ((numActiveAddThreads > 0) && (! shouldStop()))
1595    {
1596      try
1597      {
1598        Thread.sleep(10);
1599      } catch (InterruptedException JavaDoc ie) {}
1600    }
1601
1602
1603    // If we have been requested to stop, then do so.
1604
if (shouldStop())
1605    {
1606      try
1607      {
1608        conn.disconnect();
1609      } catch (LDAPException le) {}
1610
1611      return;
1612    }
1613
1614
1615    // Start all the delete trackers.
1616
deleteCount.startTracker();
1617    deleteTime.startTracker();
1618    deleteResultCodes.startTracker();
1619    totalDeletes.startTracker();
1620
1621
1622    // Create a new loop that will iterate through all entries to delete.
1623
Iterator iterator = entriesAdded.iterator();
1624    while (iterator.hasNext() && (! shouldStop()))
1625    {
1626      // If the connection is currently not connected, then establish it
1627
if (! connected)
1628      {
1629        try
1630        {
1631          conn.connect(3, ldapHost, ldapPort, bindDN, bindPassword);
1632          connected = true;
1633        }
1634        catch (LDAPException le)
1635        {
1636          logMessage("ERROR -- Could not connect to " + ldapHost + ":" +
1637                           ldapPort + " (" + le + ") -- aborting thread");
1638          addResultCodes.increment(String.valueOf(le.getLDAPResultCode()));
1639          indicateStoppedDueToError();
1640          break;
1641        }
1642      }
1643
1644      LDAPConstraints constraints = conn.getConstraints();
1645      if (useProxyAuth)
1646      {
1647        LDAPProxiedAuthControl proxyAuthControl =
1648             new LDAPProxiedAuthControl(proxyAsDN, true);
1649        constraints.setServerControls(proxyAuthControl);
1650      }
1651      constraints.setTimeLimit(1000 * timeLimit);
1652
1653
1654      // Record the current time as the start of the delete.
1655
if (delay > 0)
1656      {
1657        addStartTime = System.currentTimeMillis();
1658      }
1659
1660
1661      // Perform the delete.
1662
int resultCode = LDAPException.SUCCESS;
1663      deleteTime.startTimer();
1664      try
1665      {
1666        conn.delete((String JavaDoc) iterator.next(), constraints);
1667      }
1668      catch (LDAPException le)
1669      {
1670        resultCode = le.getLDAPResultCode();
1671      }
1672      deleteTime.stopTimer();
1673      deleteCount.increment();
1674      totalDeletes.increment();
1675      deleteResultCodes.increment(String.valueOf(resultCode));
1676
1677      // If the connection should be broken, then do so
1678
if (alwaysDisconnect)
1679      {
1680        try
1681        {
1682          conn.disconnect();
1683        } catch (LDAPException le) {}
1684        connected = false;
1685      }
1686
1687      // If we need to sleep, then do so
1688
if ((delay > 0) && (! shouldStop()))
1689      {
1690        long now = System.currentTimeMillis();
1691        long sleepTime = delay - (now - addStartTime);
1692        if (sleepTime > 0)
1693        {
1694          try
1695          {
1696            Thread.sleep(sleepTime);
1697          } catch (InterruptedException JavaDoc ie) {}
1698        }
1699      }
1700    }
1701
1702
1703    // If the connection is still established, then close it
1704
try
1705    {
1706      conn.disconnect();
1707    } catch (LDAPException le) {}
1708
1709
1710    // Tell the stat trackers that they should stop tracking
1711
deleteCount.stopTracker();
1712    deleteTime.stopTracker();
1713    deleteResultCodes.stopTracker();
1714    totalDeletes.stopTracker();
1715  }
1716
1717
1718
1719  /**
1720   * Performs any per-client finalization that should be done for this job. In
1721   * this case, it checks to see if we need to clean up any tombstones that
1722   * may still exist in the server.
1723   */

1724  public void finalizeClient()
1725  {
1726    if (! cleanTombstones)
1727    {
1728      return;
1729    }
1730
1731
1732    // Sleep for the required length of time.
1733
if (tombstoneDelay > 0)
1734    {
1735      try
1736      {
1737        Thread.sleep(1000 * tombstoneDelay);
1738      } catch (InterruptedException JavaDoc ie) {}
1739    }
1740
1741
1742    // Establish the connection to the server.
1743
if (useSSL)
1744    {
1745      if (blindTrust)
1746      {
1747        try
1748        {
1749          conn = new LDAPConnection(new JSSEBlindTrustSocketFactory());
1750        }
1751        catch (LDAPException le)
1752        {
1753          logMessage("Unable to create LDAP connection to clean up " +
1754                     "tombstones -- " + le.getMessage());
1755          indicateCompletedWithErrors();
1756          return;
1757        }
1758      }
1759      else
1760      {
1761        conn = new LDAPConnection(new JSSESocketFactory(null));
1762      }
1763    }
1764    else
1765    {
1766      conn = new LDAPConnection();
1767    }
1768
1769    try
1770    {
1771      conn.connect(3, ldapHost, ldapPort, bindDN, bindPassword);
1772    }
1773    catch (LDAPException le)
1774    {
1775      logMessage("Unable to connect to server to clean up tombstones -- " +
1776                 le.getMessage());
1777      indicateCompletedWithErrors();
1778      return;
1779    }
1780
1781
1782    // Perform a search to find the tombstone entries.
1783
LDAPSearchResults results = null;
1784    try
1785    {
1786      results = conn.search(baseDN, LDAPConnection.SCOPE_SUB,
1787                            "(&(objectClass=nsTombstone)(!(nsds50RUV=*)))",
1788                            new String JavaDoc[] { "1.1" }, false);
1789    }
1790    catch (LDAPException le)
1791    {
1792      logMessage("Unable to search for tombstone entries -- " +
1793                 le.getMessage());
1794      indicateCompletedWithErrors();
1795
1796      try
1797      {
1798        conn.disconnect();
1799      } catch (Exception JavaDoc e) {}
1800
1801      return;
1802    }
1803
1804
1805    // Iterate through each matching entry and try to delete it.
1806
while (results.hasMoreElements())
1807    {
1808      Object JavaDoc element = results.nextElement();
1809      if (element instanceof LDAPEntry)
1810      {
1811        String JavaDoc entryDN = ((LDAPEntry) element).getDN();
1812        try
1813        {
1814          conn.delete(entryDN);
1815        } catch (LDAPException le) {}
1816      }
1817    }
1818
1819
1820    // Close the connection to the server.
1821
try
1822    {
1823      conn.disconnect();
1824    } catch (Exception JavaDoc e) {}
1825  }
1826
1827
1828
1829  /**
1830   * Attempts to force this thread to exit by closing the connection to the
1831   * directory server and setting it to <CODE>null</CODE>.
1832   */

1833  public void destroy()
1834  {
1835    if (conn != null)
1836    {
1837      try
1838      {
1839        conn.disconnect();
1840      } catch (Exception JavaDoc e) {}
1841
1842      conn = null;
1843    }
1844  }
1845
1846
1847
1848  /**
1849   * Parses the provided template and ensures that it is acceptable for use in
1850   * creating entries.
1851   *
1852   * @throws UnableToRunException If the provided template is not acceptable
1853   * for some reason.
1854   */

1855  public void validateTemplate()
1856         throws UnableToRunException
1857  {
1858    boolean rdnAttrFound = false;
1859    attrNames = new String JavaDoc[templateLines.length];
1860    lowerNames = new String JavaDoc[templateLines.length];
1861    separators = new String JavaDoc[templateLines.length];
1862    attrValues = new String JavaDoc[templateLines.length];
1863
1864    for (int i=0; i < templateLines.length; i++)
1865    {
1866      int colonPos = templateLines[i].indexOf(':');
1867      if (colonPos < 0)
1868      {
1869        throw new UnableToRunException("No colon found in template line \"" +
1870                                       templateLines[i] + "\" to separate " +
1871                                       "the attribute name from the value.");
1872      }
1873      else if (colonPos == 0)
1874      {
1875        throw new UnableToRunException("No attribute name found in template " +
1876                                       "line \"" + templateLines[i] + "\".");
1877      }
1878      else if (colonPos == (templateLines[i].length() - 1))
1879      {
1880        throw new UnableToRunException("No attribute value found in template " +
1881                                       "line \"" + templateLines[i] + "\".");
1882      }
1883      attrNames[i] = templateLines[i].substring(0, colonPos);
1884      lowerNames[i] = attrNames[i].toLowerCase();
1885      if (lowerNames[i].equals(lowerRDNAttr))
1886      {
1887        rdnAttrFound = true;
1888      }
1889
1890      char nextChar = templateLines[i].charAt(colonPos+1);
1891      if (nextChar == ' ')
1892      {
1893        separators[i] = ":";
1894        attrValues[i] = templateLines[i].substring(colonPos+2).trim();
1895      }
1896      else if (nextChar == ':')
1897      {
1898        if (colonPos == (templateLines[i].length() - 2))
1899        {
1900          throw new UnableToRunException("No attribute value found in " +
1901                                         "template line \"" + templateLines[i] +
1902                                         "\".");
1903        }
1904        else if (templateLines[i].charAt(colonPos+2) == ' ')
1905        {
1906          separators[i] = "::";
1907          attrValues[i] = templateLines[i].substring(colonPos+3).trim();
1908        }
1909        else
1910        {
1911          throw new UnableToRunException("Invalid character sequence found " +
1912                                         "in template line \"" +
1913                                         templateLines[i] + "\" -- illegal " +
1914                                         "character '" +
1915                                         templateLines[i].charAt(colonPos+2) +
1916                                         "' in column " + (colonPos+2));
1917        }
1918      }
1919      else
1920      {
1921        throw new UnableToRunException("Invalid character sequence found " +
1922                                       "in template line \"" +
1923                                       templateLines[i] + "\" -- illegal " +
1924                                       "character '" +
1925                                       templateLines[i].charAt(colonPos+1) +
1926                                       "' in column " + (colonPos+1));
1927      }
1928    }
1929
1930    if (! rdnAttrFound)
1931    {
1932      throw new UnableToRunException("No value provided for RDN attribute \"" +
1933                                     rdnAttr + "\" in template definition.");
1934    }
1935  }
1936
1937
1938
1939  /**
1940   * Creates a randomly-generated LDAP entry to be added to the directory.
1941   *
1942   * @return The randomly-generated entry.
1943   */

1944  public LDAPEntry createEntry()
1945  {
1946    int entryValue = nextValue++;
1947    int entryNumCreated = entryValue - startValue + 1;
1948    if (nextValue > endValue)
1949    {
1950      return null;
1951    }
1952
1953    LDAPAttributeSet attributeSet = new LDAPAttributeSet();
1954    String JavaDoc rdnValue = null;
1955    for (int i=0; i < attrNames.length; i++)
1956    {
1957      String JavaDoc value = processValue(attrValues[i], attributeSet, entryValue,
1958                                  entryNumCreated);
1959      if (value == null)
1960      {
1961        continue;
1962      }
1963
1964      if ((rdnValue == null) && (lowerNames[i].equals(lowerRDNAttr)))
1965      {
1966        rdnValue = value;
1967      }
1968      attributeSet.add(new LDAPAttribute(attrNames[i], value));
1969    }
1970
1971    return new LDAPEntry(rdnAttr + "=" + rdnValue + "," + baseDN,
1972                         attributeSet);
1973  }
1974
1975
1976
1977  /**
1978   * Generates the appropriate value from the given line in the template.
1979   *
1980   * @param value The value to be processed.
1981   * @param attributeSet The attribute set containing the values of the
1982   * attributes previously defined.
1983   * @param entryNumber The unique number assigned to the entry being
1984   * created.
1985   * @param entryInSequence A counter used to determine how many entries have
1986   * been created so far.
1987   *
1988   * @return The generated value.
1989   */

1990  public String JavaDoc processValue(String JavaDoc value, LDAPAttributeSet attributeSet,
1991                             int entryNumber, int entryInSequence)
1992  {
1993    boolean needReprocess = true;
1994    int pos;
1995
1996    // If the value contains "<presence:", then determine if it should
1997
// actually be included in this entry. If not, then just go to the next
1998
// attribute
1999
if ((pos = value.indexOf("<presence:")) >= 0)
2000    {
2001      int closePos = value.indexOf(">", pos);
2002      if (closePos > pos)
2003      {
2004        String JavaDoc numStr = value.substring(pos+10, closePos);
2005        try
2006        {
2007          int percentage = Integer.parseInt(numStr);
2008          int randomValue = ((random.nextInt() & 0x7FFFFFFF) % 100) + 1;
2009          if (randomValue <= percentage)
2010          {
2011            // We have determined that this value should be included in the
2012
// entry, so remove the "<presence:x>" tag and let it go on to do
2013
// the rest of the processing on this entry
2014
value = value.substring(0, pos) + value.substring(closePos+1);
2015          }
2016          else
2017          {
2018            // We have determined that this value should not be included in
2019
// the entry, so return null.
2020
return null;
2021          }
2022        }
2023        catch (NumberFormatException JavaDoc nfe)
2024        {
2025          return null;
2026        }
2027      }
2028    }
2029
2030    // If the value contains "<ifpresent:{attrname}>", then determine if it
2031
// should actually be included in this entry. If not, then just go to the
2032
// next attribute.
2033
if ((pos = value.indexOf("<ifpresent:")) >= 0)
2034    {
2035      int closePos = value.indexOf(">", pos);
2036      if (closePos > pos)
2037      {
2038        int colonPos = value.indexOf(":", pos+11);
2039        if ((colonPos > 0) && (colonPos < closePos))
2040        {
2041          // Look for a specific value to be present.
2042
boolean matchFound = false;
2043          String JavaDoc attrName = value.substring(pos+11, colonPos);
2044          String JavaDoc matchValue = value.substring(colonPos+1, closePos);
2045
2046          String JavaDoc[] values = null;
2047          LDAPAttribute attr = attributeSet.getAttribute(attrName);
2048          if (attr != null)
2049          {
2050            values = attr.getStringValueArray();
2051          }
2052
2053          for (int j=0; ((values != null) && (j < values.length)); j++)
2054          {
2055            if (matchValue.equalsIgnoreCase(values[j]))
2056            {
2057              value = value.substring(0, pos) + value.substring(closePos+1);
2058              matchFound = true;
2059              break;
2060            }
2061          }
2062
2063          if (! matchFound)
2064          {
2065            return null;
2066          }
2067        }
2068        else
2069        {
2070          // Just look for the attribute to be present.
2071
String JavaDoc attrName = value.substring(pos+11, closePos);
2072          String JavaDoc[] values = null;
2073          LDAPAttribute attr = attributeSet.getAttribute(attrName);
2074          if (attr != null)
2075          {
2076            values = attr.getStringValueArray();
2077          }
2078          if ((values == null) || (values.length == 0))
2079          {
2080            // The requested attribute is not present, so skip this line.
2081
return null;
2082          }
2083          else
2084          {
2085            value = value.substring(0, pos) + value.substring(closePos+1);
2086          }
2087        }
2088      }
2089    }
2090
2091    // If the value contains "<ifabsent:{attrname}>", then determine if it
2092
// should actually be included in this entry. If not, then just go to the
2093
// next attribute.
2094
if ((pos = value.indexOf("<ifabsent:")) >= 0)
2095    {
2096      int closePos = value.indexOf(">", pos);
2097      if (closePos > pos)
2098      {
2099        int colonPos = value.indexOf(":", pos+10);
2100        if ((colonPos > 0) && (colonPos < closePos))
2101        {
2102          // Look for a specific value to be present.
2103
boolean matchFound = false;
2104          String JavaDoc attrName = value.substring(pos+10, colonPos);
2105          String JavaDoc matchValue = value.substring(colonPos+1, closePos);
2106
2107          String JavaDoc[] values = null;
2108          LDAPAttribute attr = attributeSet.getAttribute(attrName);
2109          if (attr != null)
2110          {
2111            values = attr.getStringValueArray();
2112          }
2113
2114          for (int j=0; ((values != null) && (j < values.length)); j++)
2115          {
2116            if (matchValue.equalsIgnoreCase(values[j]))
2117            {
2118              matchFound = true;
2119              break;
2120            }
2121          }
2122
2123          if (matchFound)
2124          {
2125            return null;
2126          }
2127          else
2128          {
2129            value = value.substring(0, pos) + value.substring(closePos+1);
2130          }
2131        }
2132        else
2133        {
2134          // Just look for the attribute to be present.
2135
String JavaDoc attrName = value.substring(pos+10, closePos);
2136          String JavaDoc[] values = null;
2137          LDAPAttribute attr = attributeSet.getAttribute(attrName);
2138          if (attr != null)
2139          {
2140            values = attr.getStringValueArray();
2141          }
2142          if ((values != null) && (values.length > 0))
2143          {
2144            // The requested attribute is present, so skip this line.
2145
return null;
2146          }
2147          else
2148          {
2149            value = value.substring(0, pos) + value.substring(closePos+1);
2150          }
2151        }
2152      }
2153    }
2154
2155    while (needReprocess && (value.indexOf("<") >= 0))
2156    {
2157      needReprocess = false;
2158
2159
2160      // If the value contains "<entryNumber>" then replace that with the first
2161
// name
2162
if ((pos = value.indexOf("<entrynumber>")) >= 0)
2163      {
2164        value = value.substring(0, pos) + entryNumber +
2165                value.substring(pos + 13);
2166        needReprocess = true;
2167      }
2168      if ((pos = value.indexOf("<entryNumber>")) >= 0)
2169      {
2170        value = value.substring(0, pos) + entryNumber +
2171                value.substring(pos + 13);
2172        needReprocess = true;
2173      }
2174
2175      // If the value contains "<random:chars:characters:length>" then
2176
// generate a random string of length characters from the provided
2177
// character set.
2178
if ((pos = value.indexOf("<random:chars:")) >= 0)
2179      {
2180        // Get the set of characters to use in the resulting value.
2181
int colonPos = value.indexOf(":", pos+14);
2182        int closePos = value.indexOf(">", colonPos+1);
2183        String JavaDoc charSet = value.substring(pos+14, colonPos);
2184
2185        // See if there is an additional colon followed by a number. If so,
2186
// then the length will be a random number between the two.
2187
int count;
2188        int colonPos2 = value.indexOf(":", colonPos+1);
2189        if ((colonPos2 > 0) && (colonPos2 < closePos))
2190        {
2191          int minValue = Integer.parseInt(value.substring(colonPos+1,
2192                                                          colonPos2));
2193          int maxValue = Integer.parseInt(value.substring(colonPos2+1,
2194                                                          closePos));
2195          int span = maxValue - minValue + 1;
2196          count = (random.nextInt() & 0x7FFFFFFF) % span + minValue;
2197        }
2198        else
2199        {
2200          count = Integer.parseInt(value.substring(colonPos+1, closePos));
2201        }
2202
2203        String JavaDoc randVal = generateRandomValue(charSet.toCharArray(), count);
2204        value = value.substring(0, pos) + randVal +
2205                value.substring(closePos+1);
2206        needReprocess = true;
2207      }
2208
2209      // If the value contains "<random:alpha:num>" then generate a random
2210
// alphabetic value and use it.
2211
if ((pos = value.indexOf("<random:alpha:")) >= 0)
2212      {
2213        // See if there is an additional colon followed by a number. If so,
2214
// then the length will be a random number between the two.
2215
int count;
2216        int closePos = value.indexOf(">", pos+14);
2217        int colonPos = value.indexOf(":", pos+14);
2218        if ((colonPos > 0) && (colonPos < closePos))
2219        {
2220          int minValue = Integer.parseInt(value.substring(pos+14, colonPos));
2221          int maxValue = Integer.parseInt(value.substring(colonPos+1,
2222                                                          closePos));
2223          int span = maxValue - minValue + 1;
2224          count = (random.nextInt() & 0x7FFFFFFF) % span + minValue;
2225         }
2226        else
2227        {
2228          count = Integer.parseInt(value.substring(pos+14, closePos));
2229        }
2230
2231        // Generate the new value.
2232
String JavaDoc randVal = generateRandomValue(ALPHA_CHARS, count);
2233        value = value.substring(0, pos) + randVal +
2234                value.substring(closePos + 1);
2235        needReprocess = true;
2236      }
2237
2238      // If the value contains "<random:numeric:num>" then generate a random
2239
// numeric value and use it. This can also take the form
2240
// "<random:numeric:min:max>" or "<random:numeric:min:max:length>".
2241
if ((pos = value.indexOf("<random:numeric:")) >= 0)
2242      {
2243        int closePos = value.indexOf('>', pos);
2244
2245        // See if there is an extra colon. If so, then generate a random
2246
// number between x and y. Otherwise, generate a random number with
2247
// the specified number of digits.
2248
int extraColonPos = value.indexOf(':', pos+16);
2249        if ((extraColonPos > 0) && (extraColonPos < closePos))
2250        {
2251          // See if there is one more colon separating the max from the
2252
// length. If so, then get it and create a padded value of at least
2253
// length digts. If not, then just generate the random value.
2254
int extraColonPos2 = value.indexOf(':', extraColonPos+1);
2255          if ((extraColonPos2 > 0) && (extraColonPos2 < closePos))
2256          {
2257            String JavaDoc lowerBoundStr = value.substring(pos+16, extraColonPos);
2258            String JavaDoc upperBoundStr = value.substring(extraColonPos+1,
2259                                                   extraColonPos2);
2260            String JavaDoc lengthStr = value.substring(extraColonPos2+1, closePos);
2261            int lowerBound = Integer.parseInt(lowerBoundStr);
2262            int upperBound = Integer.parseInt(upperBoundStr);
2263            int length = Integer.parseInt(lengthStr);
2264            int span = (upperBound - lowerBound + 1);
2265            int randomValue = (random.nextInt() & 0x7FFFFFFF) % span +
2266                              lowerBound;
2267            String JavaDoc valueStr = String.valueOf(randomValue);
2268            while (valueStr.length() < length)
2269            {
2270              valueStr = "0" + valueStr;
2271            }
2272            value = value.substring(0, pos) + valueStr +
2273                    value.substring(closePos+1);
2274          }
2275          else
2276          {
2277            String JavaDoc lowerBoundStr = value.substring(pos+16, extraColonPos);
2278            String JavaDoc upperBoundStr = value.substring(extraColonPos+1, closePos);
2279            int lowerBound = Integer.parseInt(lowerBoundStr);
2280            int upperBound = Integer.parseInt(upperBoundStr);
2281            int span = (upperBound - lowerBound + 1);
2282            int randomValue = (random.nextInt() & 0x7FFFFFFF) % span +
2283                              lowerBound;
2284            value = value.substring(0, pos) + randomValue +
2285                    value.substring(closePos+1);
2286          }
2287        }
2288        else
2289        {
2290          // Get the number of characters to include in the value
2291
int numPos = pos + 16;
2292          int count = Integer.parseInt(value.substring(numPos, closePos));
2293          String JavaDoc randVal = generateRandomValue(NUMERIC_CHARS, count);
2294          value = value.substring(0, pos) + randVal +
2295                  value.substring(closePos+1);
2296        }
2297
2298        needReprocess = true;
2299      }
2300
2301      // If the value contains "<random:alphanumeric:num>" then generate a
2302
// random alphanumeric value and use it
2303
if ((pos = value.indexOf("<random:alphanumeric:")) >= 0)
2304      {
2305        // See if there is an additional colon followed by a number. If so,
2306
// then the length will be a random number between the two.
2307
int count;
2308        int closePos = value.indexOf(">", pos+21);
2309        int colonPos = value.indexOf(":", pos+21);
2310        if ((colonPos > 0) && (colonPos < closePos))
2311        {
2312          int minValue = Integer.parseInt(value.substring(pos+21, colonPos));
2313          int maxValue = Integer.parseInt(value.substring(colonPos+1,
2314                                                          closePos));
2315          int span = maxValue - minValue + 1;
2316          count = (random.nextInt() & 0x7FFFFFFF) % span + minValue;
2317         }
2318        else
2319        {
2320          count = Integer.parseInt(value.substring(pos+21, closePos));
2321        }
2322
2323        // Generate the new value.
2324
String JavaDoc randVal = generateRandomValue(ALPHANUMERIC_CHARS, count);
2325        value = value.substring(0, pos) + randVal +
2326                value.substring(closePos + 1);
2327        needReprocess = true;
2328      }
2329
2330      // If the value contains "<random:hex:num>" then generate a random
2331
// hexadecimal value and use it
2332
if ((pos = value.indexOf("<random:hex:")) >= 0)
2333      {
2334        // See if there is an additional colon followed by a number. If so,
2335
// then the length will be a random number between the two.
2336
int count;
2337        int closePos = value.indexOf(">", pos+12);
2338        int colonPos = value.indexOf(":", pos+12);
2339        if ((colonPos > 0) && (colonPos < closePos))
2340        {
2341          int minValue = Integer.parseInt(value.substring(pos+12, colonPos));
2342          int maxValue = Integer.parseInt(value.substring(colonPos+1,
2343                                                          closePos));
2344          int span = maxValue - minValue + 1;
2345          count = (random.nextInt() & 0x7FFFFFFF) % span + minValue;
2346         }
2347        else
2348        {
2349          count = Integer.parseInt(value.substring(pos+12, closePos));
2350        }
2351
2352        // Generate the new value.
2353
String JavaDoc randVal = generateRandomValue(HEX_CHARS, count);
2354        value = value.substring(0, pos) + randVal +
2355                value.substring(closePos + 1);
2356        needReprocess = true;
2357      }
2358
2359      // If the value contains "<random:base64:num>" then generate a random
2360
// base64 value and use it
2361
if ((pos = value.indexOf("<random:base64:")) >= 0)
2362      {
2363        // See if there is an additional colon followed by a number. If so,
2364
// then the length will be a random number between the two.
2365
int count;
2366        int closePos = value.indexOf(">", pos+15);
2367        int colonPos = value.indexOf(":", pos+15);
2368        if ((colonPos > 0) && (colonPos < closePos))
2369        {
2370          int minValue = Integer.parseInt(value.substring(pos+15, colonPos));
2371          int maxValue = Integer.parseInt(value.substring(colonPos+1,
2372                                                          closePos));
2373          int span = maxValue - minValue + 1;
2374          count = (random.nextInt() & 0x7FFFFFFF) % span + minValue;
2375         }
2376        else
2377        {
2378          count = Integer.parseInt(value.substring(pos+15, closePos));
2379        }
2380
2381        // Generate the new value.
2382
String JavaDoc randVal = generateRandomValue(BASE64_CHARS, count);
2383        switch (count % 4)
2384        {
2385          case 1: randVal += "===";
2386                   break;
2387          case 2: randVal += "==";
2388                   break;
2389          case 3: randVal += "=";
2390                   break;
2391        }
2392        value = value.substring(0, pos) + randVal +
2393                value.substring(closePos + 1);
2394        needReprocess = true;
2395      }
2396
2397      // If the value contains "<random:telephone>" then generate a random
2398
// telephone number and use it
2399
if ((pos = value.indexOf("<random:telephone>")) >= 0)
2400      {
2401        // Get the number of characters to include in the value
2402
String JavaDoc randVal = generateRandomValue(NUMERIC_CHARS, 10);
2403        value = value.substring(0, pos) + randVal.substring(0, 3) + "-" +
2404                randVal.substring(3, 6) + "-" + randVal.substring(6) +
2405                value.substring(pos + 18);
2406        needReprocess = true;
2407      }
2408
2409      // If the value contains "<random:month>" then choose a random month
2410
// name. Optionally, look for "<random:month:length>" and use at most
2411
// length characters of the month name.
2412
if ((pos = value.indexOf("<random:month")) >= 0)
2413      {
2414        int closePos = value.indexOf('>', pos+13);
2415        String JavaDoc monthStr = MONTH_NAMES[(random.nextInt() & 0x7FFFFFFF) % 12];
2416
2417        // See if there is another colon that specifies the length.
2418
int colonPos = value.indexOf(':', pos+13);
2419        if ((colonPos > 0) && (colonPos < closePos))
2420        {
2421          String JavaDoc lengthStr = value.substring(colonPos+1, closePos);
2422          int length = Integer.parseInt(lengthStr);
2423          if (monthStr.length() > length)
2424          {
2425            monthStr = monthStr.substring(0, length);
2426          }
2427        }
2428
2429        value = value.substring(0, pos) + monthStr +
2430                value.substring(closePos+1);
2431        needReprocess = true;
2432      }
2433
2434      // If the value contains "<guid>" then generate a GUID and use it
2435
if ((pos = value.indexOf("<guid>")) >= 0)
2436      {
2437        // Get the number of characters to include in the value
2438
value = value.substring(0, pos) + generateGUID() +
2439                value.substring(pos + 6);
2440        needReprocess = true;
2441      }
2442
2443      // If the value contains "<sequential>" then use the next sequential
2444
// value for that attribute
2445
if ((pos = value.indexOf("<sequential")) >= 0)
2446      {
2447        int closePos = value.indexOf(">", pos);
2448
2449        // If a starting point was specified, then use it. If not, then use 0.
2450
int colonPos = value.indexOf(":", pos);
2451        int startingValue = 0;
2452        if ((colonPos > pos) && (colonPos < closePos))
2453        {
2454          startingValue = Integer.parseInt(value.substring(colonPos+1,
2455                                                           closePos));
2456        }
2457
2458        value = value.substring(0, pos) + (startingValue + entryInSequence) +
2459                value.substring(closePos+1);
2460        needReprocess = true;
2461      }
2462    }
2463
2464    needReprocess = true;
2465    while (needReprocess && ((pos = value.indexOf("{")) >= 0))
2466    {
2467      // If there is a backslash in front of the curly brace, then we don't
2468
// want to consider it an attribute name.
2469
if ((pos > 0) && (value.charAt(pos-1) == '\\'))
2470      {
2471        boolean keepGoing = true;
2472        boolean nonEscaped = false;
2473        while (keepGoing)
2474        {
2475          value = value.substring(0, pos-1) + value.substring(pos);
2476
2477          pos = value.indexOf('{', pos);
2478          if (pos < 0)
2479          {
2480            keepGoing = false;
2481          }
2482          else if (value.charAt(pos-1) != '\\')
2483          {
2484            nonEscaped = true;
2485          }
2486        }
2487
2488        if (! nonEscaped)
2489        {
2490          break;
2491        }
2492      }
2493
2494
2495      // If the value has "{attr}", then try to replace it with the value of
2496
// that attribute. Note that attribute replacement will only work
2497
// properly for attributes that are defined in the template before the
2498
// attribute that attempts to use its value. If the specified attribute
2499
// has more than one value, then the first value found will be used.
2500
int closePos = value.indexOf("}", pos);
2501      if (closePos > 0)
2502      {
2503        int colonPos = value.indexOf(":", pos);
2504        int substringChars = -1;
2505        String JavaDoc attrName = null;
2506        if ((colonPos > 0) && (colonPos < closePos))
2507        {
2508          attrName = value.substring(pos+1, colonPos).toLowerCase();
2509          String JavaDoc numStr = value.substring(colonPos+1, closePos);
2510          try
2511          {
2512            substringChars = Integer.parseInt(numStr);
2513          }
2514          catch (NumberFormatException JavaDoc nfe)
2515          {
2516            writeVerbose(nfe.toString());
2517            return null;
2518          }
2519        }
2520        else
2521        {
2522          attrName = value.substring(pos+1, closePos).toLowerCase();
2523        }
2524
2525        String JavaDoc attrValue = "";
2526        LDAPAttribute attr = attributeSet.getAttribute(attrName);
2527        if (attr != null)
2528        {
2529          String JavaDoc[] values = attr.getStringValueArray();
2530          if ((values != null) && (values.length > 0))
2531          {
2532            attrValue = values[0];
2533          }
2534        }
2535        if ((colonPos > 0) && (colonPos < closePos) && (substringChars > 0) &&
2536            (attrValue.length() > substringChars))
2537        {
2538          attrValue = attrValue.substring(0, substringChars);
2539        }
2540
2541        StringBuffer JavaDoc valueBuffer = new StringBuffer JavaDoc(1000);
2542        valueBuffer.append(value.substring(0, pos)).append(attrValue).
2543                         append(value.substring(closePos+1));
2544        value = valueBuffer.toString();
2545        needReprocess = true;
2546      }
2547    }
2548
2549    if ((pos = value.indexOf("<base64:")) >= 0)
2550    {
2551      String JavaDoc charset;
2552      String JavaDoc valueToEncode;
2553
2554      int closePos = value.indexOf('>', pos+8);
2555      int colonPos = value.indexOf(':', pos+8);
2556      if ((closePos > 0) && (colonPos > 0) && (colonPos < closePos))
2557      {
2558        charset = value.substring(pos+8, colonPos);
2559        valueToEncode = value.substring(colonPos+1, closePos);
2560      }
2561      else
2562      {
2563        charset = "UTF-8";
2564        valueToEncode = value.substring(pos+8, closePos);
2565      }
2566
2567      try
2568      {
2569        String JavaDoc encodedStr =
2570                    Base64Encoder.encode(valueToEncode.getBytes(charset));
2571        value = value.substring(0, pos) + encodedStr +
2572                value.substring(closePos+1);
2573      }
2574      catch (UnsupportedEncodingException uee)
2575      {
2576        writeVerbose(uee.toString());
2577        return null;
2578      }
2579    }
2580
2581    return value;
2582  }
2583
2584
2585
2586  /**
2587   * Retrieves a string containing the specified number of randomly-chosen
2588   * characters.
2589   *
2590   * @param charSet The character set from which to take the characters to use
2591   * in the generated value.
2592   * @param length The number of characters to include in the string.
2593   *
2594   * @return A string containing the specified number of randomly-chosen
2595   * characters.
2596   */

2597  public String JavaDoc generateRandomValue(char[] charSet, int length)
2598  {
2599    // A performance optimization in an attempt to avoid continually
2600
// re-allocating character arrays. If the requested array is less than
2601
// 5000 characters long, then use the space we've already allocated for this
2602
// purpose. If it is longer than 5000 characters, then we'll take the
2603
// hit of re-allocating the memory.
2604
if (length <= 5000)
2605    {
2606      for (int i=0; i < length; i++)
2607      {
2608        chars5000[i] = charSet[(random.nextInt() & 0x7FFFFFFF) %
2609                               charSet.length];
2610      }
2611
2612      return new String JavaDoc(chars5000, 0, length);
2613    }
2614    else
2615    {
2616      char[] retArray = new char[length];
2617
2618      for (int i=0; i < length; i++)
2619      {
2620        retArray[i] = charSet[(random.nextInt() & 0x7FFFFFFF) % charSet.length];
2621      }
2622
2623      return new String JavaDoc(retArray);
2624    }
2625  }
2626
2627
2628
2629  /**
2630   * Generates a globally-unique identifier. Technically speaking, it's not
2631   * guaranteed to be globally unique, but this should be good enough for most
2632   * purposes.
2633   *
2634   * @return The GUID that was generated.
2635   */

2636  public String JavaDoc generateGUID()
2637  {
2638    String JavaDoc timeStr = Long.toHexString(System.currentTimeMillis());
2639    String JavaDoc tmpStr = timeStr +
2640                    generateRandomValue(HEX_CHARS, 20-timeStr.length());
2641    return tmpStr.substring(0, 8) + "-" + tmpStr.substring(8, 12) + "-" +
2642           tmpStr.substring(12,16) + "-" + tmpStr.substring(16) + "-" +
2643           guidBase;
2644  }
2645}
2646
2647
Popular Tags