File Coverage

blib/lib/Clearquest.pm
Criterion Covered Total %
statement 173 616 28.0
branch 47 360 13.0
condition 9 62 14.5
subroutine 24 53 45.2
pod 30 31 96.7
total 283 1122 25.2


line stmt bran cond sub pod time code
1             package Clearquest;
2              
3 2     2   401797 use strict;
  2         5  
  2         67  
4 2     2   9 use warnings;
  2         2  
  2         146  
5              
6             =pod
7              
8             =head1 NAME
9              
10             Clearquest - Object oriented interface to Clearquest.
11              
12             =head1 VERSION
13              
14             =over
15              
16             =item Author
17              
18             Andrew DeFaria
19              
20             =item Revision
21              
22             $Revision: 1.0 $
23              
24             =item Created
25              
26             Fri Sep 22 09:21:18 CDT 2006
27              
28             =item Modified
29              
30             Fri Feb 06 08:30:00 PST 2026
31              
32             =back
33              
34             =head1 SYNOPSIS
35              
36             Provides access to Clearquest database in an object oriented manner.
37              
38             # Create Clearquest object
39             my $cq = Clearquest->new;
40              
41             # Connect to database (using all the defaults in cq.conf)
42             $cq->connect;
43              
44             # Connect as non standard user;
45              
46             $cq->connect (CQ_USERNAME => 'me', CQ_PASSWORD => 'mypassword');
47              
48             # Get record (Default: all fields)
49             my %record = $cq->get ($recordName, $key);
50              
51             # Get record with specific field list
52             my %record =$cq->get ($recordName, $key, qw(field1 field2))
53              
54             # Modify a record
55             my %update = (
56             Description => 'This is a new description',
57             Active => 1,
58             );
59             $cq->modify ($recordName, $key, 'Modify', \%update);
60              
61             # Change state using modify with an alternate action. Note the use of @ordering
62             my %fieldsToUpdate = (
63             Project => 'Carrier',
64             Category => 'New Functionality',
65             Groups => [ 'Group1', 'Group2' ],
66             );
67              
68             my @ordering qw(Project Category);
69              
70             $cq->modify ($recordName, $key, 'Open', \%fieldsToUpdate, @ordering);
71              
72             if ($cq->error) {
73             error "Unable to update $key to Opened state\n"
74             . $cq->errmsg;
75             } # if
76              
77             =head1 DESCRIPTION
78              
79             This module provides a simple interface to Clearquest in a Perl like fashion.
80             There are three modes of talking to Clearquest using this module - api, rest
81             and client.
82              
83             With module = 'api' you must have Clearquest installed locally and you must use
84             cqperl to execute your script. This mode of operation has the benefit of speed -
85             note that initial connection to the Clearquest database is not very speedy, but
86             all subsequent calls will operate at full speed. The 'api' module is free to
87             use. For the other modules contact Andrew DeFaria .
88              
89             With module = 'rest' you can access Clearquest by using a RESTFull interface.
90             You can use any Perl which has the required CPAN modules (REST, XML::Simple -
91             see Clearquest::REST for a list of required CPAN modules). The REST interface is
92             a slower than the native api and requires the setup of Clearquest Web (cqweb) on
93             your network. To use the REST interface set CQ_MODULE to 'rest'.
94              
95             With module = 'client' you access Clearquest through the companion
96             Clearquest::Server module and the cqd.pl server script. The server process is
97             started on a machine that has Clearquest installed locally. It uses the api
98             interface for speed and can operate in a multithreaded manner, spawning
99             processes which open and handle requests from Clearquest::Client requests. To
100             use the Client interface set CQ_MODULE to 'client'.
101              
102             Other than setting CQ_MODULE to one of the three modes described above, the rest
103             of your script's usage of the Clearquest module should be exactly the same.
104              
105             =head1 CONFIGURATION
106              
107             This module reads configuration data from a file (../etc/cq.conf)
108             which sets default values described below. Or you can export the option name to
109             the env(1) to override the defaults in cq.conf. Finally you can programmatically
110             set the options when you call new by passing in a %parms hash. To specify the
111             %parms hash key remove the CQ_ portion and lc the rest.
112              
113             =for html
114              
115             =over
116              
117             =item CQ_SERVER
118              
119             Clearquest server to talk to. Also used for rest server (Default: From cq.conf)
120              
121             =item CQ_PORT
122              
123             Port to connect to (Default: From cq.conf)
124              
125             =item CQ_WEBHOST
126              
127             The web host to contact with leading http:// (Default: From cq.conf)
128              
129             =item CQ_DATABASE
130              
131             Name of database to connect to (Default: From cq.conf)
132              
133             =item CQ_USERNAME
134              
135             User name to connect as (Default: From cq.conf)
136              
137             =item CQ_PASSWORD
138              
139             Password for CQREST_USERNAME (Default: From cq.conf)
140              
141             =item CQ_DBSET
142              
143             Database Set name (Default: From cq.conf)
144              
145             =item CQ_MODULE
146              
147             One of 'api', 'rest' or 'client' (Default: From cq.conf)
148              
149             =back
150              
151             =head1 METHODS
152              
153             The following methods are available:
154              
155             =cut
156              
157 2     2   9 use File::Basename;
  2         3  
  2         161  
158 2     2   10 use Carp;
  2         16  
  2         104  
159 2     2   1034 use Time::Local;
  2         4196  
  2         162  
160              
161 2     2   14 use File::Spec;
  2         3  
  2         17277  
162              
163             sub _GetConfig($);
164              
165             # Seed options from config file
166             my $config = $ENV{CQ_CONF};
167              
168             unless ($config) {
169             my $dir = dirname (__FILE__);
170             my $local_conf = File::Spec->catfile ($dir, '..', 'etc', 'cq.conf');
171             my $installed_conf = File::Spec->catfile ($dir, 'Clearquest', 'cq.conf');
172              
173             if (-r $local_conf) {
174             $config = $local_conf;
175             } elsif (-r $installed_conf) {
176             $config = $installed_conf;
177             } elsif (-r '/etc/clearquest/cq.conf') {
178             $config = '/etc/clearquest/cq.conf';
179             } # if
180             } # unless
181              
182             croak "Unable to find config file" unless $config and -r $config;
183              
184             our %OPTS = _GetConfig ($config);
185              
186             my $DEFAULT_DBSET = $OPTS{CQ_DBSET};
187              
188             our $VERSION = '1.02';
189              
190             # Override options if in the environment
191             $OPTS{CQ_DATABASE} = $ENV{CQ_DATABASE} if $ENV{CQ_DATABASE};
192             $OPTS{CQ_DBSET} = $ENV{CQ_DBSET} if $ENV{CQ_DBSET};
193             $OPTS{CQ_MODULE} = $ENV{CQ_MODULE} if $ENV{CQ_MODULE};
194             $OPTS{CQ_PASSWORD} = $ENV{CQ_PASSWORD} if $ENV{CQ_PASSWORD};
195             $OPTS{CQ_PORT} = $ENV{CQ_PORT} if $ENV{CQ_PORT};
196             $OPTS{CQ_SERVER} = $ENV{CQ_SERVER} if $ENV{CQ_SERVER};
197             $OPTS{CQ_USERNAME} = $ENV{CQ_USERNAME} if $ENV{CQ_USERNAME};
198              
199             # FieldTypes ENUM
200             our $UNKNOWN = -1;
201             our $STRING = 1;
202             our $MULTILINE_STRING = 2;
203             our $INT = 3;
204             our $DATE_TIME = 4;
205             our $REFERENCE = 5;
206             our $REFERENCE_LIST = 6;
207             our $ATTACHMENT_LIST = 7;
208             our $ID = 8;
209             our $STATE = 9;
210             our $JOURNAL = 10;
211             our $DBID = 11;
212             our $STATETYPE = 12;
213             our $RECORD_TYPE = 13;
214              
215             my %FIELDS;
216              
217             my @objects;
218              
219             my $SECS_IN_MIN = 60;
220             my $SECS_IN_HOUR = $SECS_IN_MIN * 60;
221             my $SECS_IN_DAY = $SECS_IN_HOUR * 24;
222              
223             my $operatorRE = qr/
224             (\w+) # field name
225             \s* # whitespace
226             ( # operators
227             == # double equals
228             |= # single equals
229             |!= # not equal
230             |<> # the other not equal
231             |<= # less than or equal
232             |>= # greater than or equal
233             |< # less than
234             |> # greater than
235             |like # like
236             |not\s+like # not like
237             |between # between
238             |not\s*between # not between
239             |is\s+null # is null
240             |is\s+not\s+null # is not null
241             |in # in
242             |not\s+in # not in
243             )
244             \s* # whitespace
245             (.*) # value
246             /ix;
247              
248             END {
249             # Insure all instaniated objects have been destroyed
250 2     2   241825 $_->DESTROY for (@objects);
251             } # END
252              
253             # Internal methods
254             sub _commitRecord($) {
255 1     1   3 my ($self, $entity) = @_;
256              
257 1         3 $self->{errmsg} = $entity->Validate;
258              
259 1 50       6 if ($self->{errmsg} eq '') {
260 1         3 $self->{errmsg} = $entity->Commit;
261 1 50       8 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
262              
263 1         4 return $self->{errmsg};
264             } else {
265 0         0 $self->{error} = 1;
266              
267 0         0 $entity->Revert;
268              
269 0         0 return $self->{errmsg};
270             } # if
271             } # _commitRecord
272              
273             sub _is_leap_year($) {
274 0     0   0 my ($year) = @_;
275              
276 0 0       0 return 0 if $year % 4;
277 0 0       0 return 1 if $year % 100;
278 0 0       0 return 0 if $year % 400;
279              
280 0         0 return 1;
281             } # _is_leap_year
282              
283             sub _dateToEpoch($) {
284 0     0   0 my ($date) = @_;
285              
286 0         0 my $year = substr $date, 0, 4;
287 0         0 my $month = substr $date, 5, 2;
288 0         0 my $day = substr $date, 8, 2;
289 0         0 my $hour = substr $date, 11, 2;
290 0         0 my $minute = substr $date, 14, 2;
291 0         0 my $seconds = substr $date, 17, 2;
292              
293 0         0 my $days;
294              
295 0         0 for (my $i = 1970; $i < $year; $i++) {
296 0 0       0 $days += _is_leap_year ($i) ? 366 : 365;
297             } # for
298              
299 0         0 my @monthDays = (0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334,);
300              
301 0         0 $days += $monthDays[$month - 1];
302              
303 0 0 0     0 $days++
304             if _is_leap_year ($year)
305             and $month > 2;
306              
307 0         0 $days += $day - 1;
308              
309 0         0 return ($days * $SECS_IN_DAY) +
310             ($hour * $SECS_IN_HOUR) +
311             ($minute * $SECS_IN_MIN) +
312             $seconds;
313             } # _dateToEpoch
314              
315             sub _epochToDate($) {
316 0     0   0 my ($epoch) = @_;
317              
318 0         0 my $year = 1970;
319 0         0 my ($month, $day, $hour, $minute, $seconds);
320 0         0 my $leapYearSecs = 366 * $SECS_IN_DAY;
321 0         0 my $yearSecs = $leapYearSecs - $SECS_IN_DAY;
322              
323 0         0 while () {
324 0 0       0 my $amount = _is_leap_year ($year) ? $leapYearSecs : $yearSecs;
325              
326             last
327 0 0       0 if $amount > $epoch;
328              
329 0         0 $epoch -= $amount;
330 0         0 $year++;
331             } # while
332              
333 0 0       0 my $leapYearAdjustment = _is_leap_year ($year) ? 1 : 0;
334              
335 0 0       0 if ($epoch >= (334 + $leapYearAdjustment) * $SECS_IN_DAY) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
336 0         0 $month = '12';
337 0         0 $epoch -= (334 + $leapYearAdjustment) * $SECS_IN_DAY;
338             } elsif ($epoch >= (304 + $leapYearAdjustment) * $SECS_IN_DAY) {
339 0         0 $month = '11';
340 0         0 $epoch -= (304 + $leapYearAdjustment) * $SECS_IN_DAY;
341             } elsif ($epoch >= (273 + $leapYearAdjustment) * $SECS_IN_DAY) {
342 0         0 $month = '10';
343 0         0 $epoch -= (273 + $leapYearAdjustment) * $SECS_IN_DAY;
344             } elsif ($epoch >= (243 + $leapYearAdjustment) * $SECS_IN_DAY) {
345 0         0 $month = '09';
346 0         0 $epoch -= (243 + $leapYearAdjustment) * $SECS_IN_DAY;
347             } elsif ($epoch >= (212 + $leapYearAdjustment) * $SECS_IN_DAY) {
348 0         0 $month = '08';
349 0         0 $epoch -= (212 + $leapYearAdjustment) * $SECS_IN_DAY;
350             } elsif ($epoch >= (181 + $leapYearAdjustment) * $SECS_IN_DAY) {
351 0         0 $month = '07';
352 0         0 $epoch -= (181 + $leapYearAdjustment) * $SECS_IN_DAY;
353             } elsif ($epoch >= (151 + $leapYearAdjustment) * $SECS_IN_DAY) {
354 0         0 $month = '06';
355 0         0 $epoch -= (151 + $leapYearAdjustment) * $SECS_IN_DAY;
356             } elsif ($epoch >= (120 + $leapYearAdjustment) * $SECS_IN_DAY) {
357 0         0 $month = '05';
358 0         0 $epoch -= (120 + $leapYearAdjustment) * $SECS_IN_DAY;
359             } elsif ($epoch >= (90 + $leapYearAdjustment) * $SECS_IN_DAY) {
360 0         0 $month = '04';
361 0         0 $epoch -= (90 + $leapYearAdjustment) * $SECS_IN_DAY;
362             } elsif ($epoch >= (59 + $leapYearAdjustment) * $SECS_IN_DAY) {
363 0         0 $month = '03';
364 0         0 $epoch -= (59 + $leapYearAdjustment) * $SECS_IN_DAY;
365             } elsif ($epoch >= 31 * $SECS_IN_DAY) {
366 0         0 $month = '02';
367 0         0 $epoch -= 31 * $SECS_IN_DAY;
368             } else {
369 0         0 $month = '01';
370             } # if
371              
372 0         0 $day = int (($epoch / $SECS_IN_DAY) + 1);
373 0         0 $epoch = $epoch % $SECS_IN_DAY;
374 0         0 $hour = int ($epoch / $SECS_IN_HOUR);
375 0         0 $epoch = $epoch % $SECS_IN_HOUR;
376 0         0 $minute = int ($epoch / $SECS_IN_MIN);
377 0         0 $seconds = $epoch % $SECS_IN_MIN;
378              
379 0 0       0 $day = "0$day" if $day < 10;
380 0 0       0 $hour = "0$hour" if $hour < 10;
381 0 0       0 $minute = "0$minute" if $minute < 10;
382 0 0       0 $seconds = "0$seconds" if $seconds < 10;
383              
384 0         0 return "$year-$month-$day $hour:$minute:$seconds";
385             } # _epochToDate
386              
387             sub _parseCondition($) {
388 0     0   0 my ($self, $condition) = @_;
389              
390             # Parse simple conditions only
391 0         0 my ($field, $operator, $value);
392              
393 0 0       0 if ($condition =~ $operatorRE) {
394 0         0 $field = $1;
395 0         0 $operator = $2;
396 0         0 $value = $3;
397              
398 0 0 0     0 if ($operator eq '==' or $operator eq '=') {
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
399 0 0       0 if ($value !~ /^null$/i) {
400 0         0 $operator = $CQPerlExt::CQ_COMP_OP_EQ;
401             } else {
402 0         0 $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL;
403             } # if
404             } elsif ($operator eq '!=' or $operator eq '<>') {
405 0 0       0 if ($value !~ /^null$/i) {
406 0         0 $operator = $CQPerlExt::CQ_COMP_OP_NEQ;
407             } else {
408 0         0 $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL;
409             } # if
410             } elsif ($operator eq '<') {
411 0         0 $operator = $CQPerlExt::CQ_COMP_OP_LT;
412             } elsif ($operator eq '>') {
413 0         0 $operator = $CQPerlExt::CQ_COMP_OP_GT;
414             } elsif ($operator eq '<=') {
415 0         0 $operator = $CQPerlExt::CQ_COMP_OP_LTE;
416             } elsif ($operator eq '>=') {
417 0         0 $operator = $CQPerlExt::CQ_COMP_OP_GTE;
418             } elsif ($operator =~ /^like$/i) {
419 0         0 $operator = $CQPerlExt::CQ_COMP_OP_LIKE;
420             } elsif ($operator =~ /^not\s+like$/i) {
421 0         0 $operator = $CQPerlExt::CQ_COMP_OP_NOT_LIKE;
422             } elsif ($operator =~ /^between$/i) {
423 0         0 $operator = $CQPerlExt::CQ_COMP_OP_BETWEEN;
424             } elsif ($operator =~ /^not\s+between$/i) {
425 0         0 $operator = $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN;
426             } elsif ($operator =~ /^is\s+null$/i) {
427 0         0 $operator = $CQPerlExt::CQ_COMP_OP_IS_NULL;
428             } elsif ($operator =~ /^is\s+not\s+null$/i) {
429 0         0 $operator = $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL;
430             } elsif ($operator =~ /^in$/i) {
431 0         0 $operator = $CQPerlExt::CQ_COMP_OP_IN;
432             } elsif ($operator =~ /^not\s+in$/) {
433 0         0 $operator = $CQPerlExt::CQ_COMP_OP_NOT_IN;
434             } else {
435 0         0 $self->_setError ("I can't understand the operator $operator");
436              
437 0         0 $operator = undef;
438              
439 0         0 return 1;
440             } # if
441             } else {
442              
443             # TODO: How to handle more complicated $condition....
444 0         0 $self->_setError (
445             "I can't understand the conditional expression " . $condition);
446              
447 0         0 $operator = undef;
448              
449 0         0 return 1;
450             } # if
451              
452             # Trim quotes if any:
453 0 0       0 if ($value =~ /^\s*\'/) {
    0          
454 0         0 $value =~ s/^\s*\'//;
455 0         0 $value =~ s/\'\s*$//;
456             } elsif ($value =~ /^\s*\"/) {
457 0         0 $value =~ s/^\s*\"//;
458 0         0 $value =~ s/\"\s*$//;
459             } # if
460              
461             # Trim leading and trailing whitespace
462 0         0 $value =~ s/^\s+//;
463 0         0 $value =~ s/\s+$//;
464              
465 0         0 return ($field, $operator, $value);
466             } # _parseCondition
467              
468             sub _parseConditional($$;$);
469              
470             sub _parseConditional($$;$) {
471 0     0   0 my ($self, $query, $condition, $filterOperator) = @_;
472              
473 0 0       0 return if $condition eq '';
474              
475 0         0 my ($field, $operator, $value);
476              
477 0 0       0 if ($condition =~ /(.+?)\s+(and|or)\s+(.+)/i) {
478 0         0 my $leftSide = $1;
479 0         0 my $conjunction = lc $2;
480 0         0 my $rightSide = $3;
481              
482 0 0       0 if ($conjunction eq 'and') {
    0          
483 0 0       0 unless ($filterOperator) {
484 0         0 $filterOperator =
485             $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND);
486             } else {
487 0         0 $filterOperator =
488             $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND);
489             } # unless
490             } elsif ($conjunction eq 'or') {
491 0 0       0 unless ($filterOperator) {
492 0         0 $filterOperator =
493             $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR);
494             } else {
495 0         0 $filterOperator =
496             $filterOperator->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_OR);
497             } # unless
498             } # if
499              
500 0         0 $self->_setCondition ($self->_parseCondition ($leftSide), $filterOperator);
501              
502 0         0 $self->_parseConditional ($query, $rightSide, $filterOperator);
503             } else {
504 0 0       0 unless ($condition =~ $operatorRE) {
505 0         0 $self->_setError ("Unable to parse condition \"$condition\"");
506              
507 0         0 return;
508             } # unless
509              
510 0 0       0 $filterOperator = $query->BuildFilterOperator ($CQPerlExt::CQ_BOOL_OP_AND)
511             unless $filterOperator;
512              
513 0         0 $self->_setCondition ($self->_parseCondition ($condition), $filterOperator);
514             } # if
515              
516             # Actually clear error...
517 0         0 $self->_setError;
518              
519 0         0 return;
520             } # _parseConditional
521              
522             sub _setCondition($$$) {
523 0     0   0 my ($self, $field, $operator, $value, $filterOperator) = @_;
524              
525 0 0       0 return unless $operator;
526              
527 0 0 0     0 if ( $operator == $CQPerlExt::CQ_COMP_OP_IS_NULL
528             or $operator == $CQPerlExt::CQ_COMP_OP_IS_NOT_NULL)
529             {
530 0         0 eval {$filterOperator->BuildFilter ($field, $operator, [()])};
  0         0  
531              
532 0 0       0 if ($@) {
533 0         0 $self->_setError ($@);
534              
535 0         0 carp $@;
536             } # if
537             } else {
538              
539             # If the operator is one of the operators that have mulitple values then we
540             # need to make an array of $value
541 0 0 0     0 if ( $operator == $CQPerlExt::CQ_COMP_OP_BETWEEN
      0        
      0        
542             or $operator == $CQPerlExt::CQ_COMP_OP_NOT_BETWEEN
543             or $operator == $CQPerlExt::CQ_COMP_OP_IN
544             or $operator == $CQPerlExt::CQ_COMP_OP_NOT_IN)
545             {
546 0         0 my @values = split /,\s*/, $value;
547              
548 0         0 eval {$filterOperator->BuildFilter ($field, $operator, \@values)};
  0         0  
549              
550 0 0       0 if ($@) {
551 0         0 $self->_setError ($@);
552              
553 0         0 carp $@;
554             } # if
555             } else {
556 0         0 eval {$filterOperator->BuildFilter ($field, $operator, [$value])};
  0         0  
557              
558 0 0       0 if ($@) {
559 0         0 $self->_setError ($@);
560              
561 0         0 carp $@;
562             } # if
563             } # if
564             } # if
565              
566 0         0 return;
567             } # _setCondition
568              
569             sub _setFields($@) {
570 2     2   4 my ($self, $table, @fields) = @_;
571              
572 2         4 my $entityDef;
573              
574 2         2 eval {$entityDef = $self->{session}->GetEntityDef ($table)};
  2         7  
575              
576 2 50       14 if ($@) {
577 0         0 $self->_setError ($@, -1);
578              
579 0         0 return;
580             } # if
581              
582 2 100       5 unless (@fields) {
583 1         2 for (@{$entityDef->GetFieldDefNames}) {
  1         3  
584 5 50       15 unless ($self->{returnSystemFields}) {
585 5 50       8 next if $entityDef->IsSystemOwnedFieldDefName ($_);
586             } # unless
587              
588 5         21 push @fields, $_;
589             } # for
590             } # unless
591              
592             # Always return dbid
593 2 100       4 push @fields, 'dbid' unless grep {$_ eq 'dbid'} @fields;
  6         14  
594              
595 2         6 return @fields;
596             } # _setFields
597              
598             sub _setError(;$$) {
599 5     5   9 my ($self, $errmsg, $error) = @_;
600              
601 5   50     23 $error ||= 0;
602              
603 5 100 66     13 if ($errmsg and $errmsg ne '') {
604 1         1 $error = 1;
605              
606 1         2 $self->{errmsg} = $errmsg;
607             } else {
608 4         8 $self->{errmsg} = '';
609             } # if
610              
611 5         12 $self->error ($error);
612              
613 5         6 return;
614             } # _setError
615              
616             sub _setFieldValue($$$$) {
617 3     3   5 my ($self, $entity, $table, $fieldName, $fieldValue) = @_;
618              
619 3         4 my $errmsg = '';
620              
621 3         9 my $entityDef = $self->{session}->GetEntityDef ($table);
622              
623 3 50       19 return $errmsg if $entityDef->IsSystemOwnedFieldDefName ($fieldName);
624              
625 3 50       15 unless (ref $fieldValue eq 'ARRAY') {
626              
627             # This is one of those rare instances where it is important to surround a
628             # bare variable with double quotes otherwise the CQ API will wrongly
629             # evaluate $fieldValue if $fieldValue is a simple number (e.g. 0, 1, etc.)
630 3 50       25 $errmsg = $entity->SetFieldValue ($fieldName, "$fieldValue") if $fieldValue;
631             } else {
632 0         0 for (@$fieldValue) {
633 0         0 $errmsg = $entity->AddFieldValue ($fieldName, $_);
634              
635 0 0       0 return $errmsg unless $errmsg eq '';
636             } # for
637             } # unless
638              
639 3         80 return $errmsg;
640             } # _setFieldValues
641              
642             sub _UTC2Localtime($) {
643 0     0   0 my ($utcdatetime) = @_;
644              
645 0 0       0 return unless $utcdatetime;
646              
647             # If the field does not look like a UTC time then just return it.
648 0 0       0 return $utcdatetime
649             unless $utcdatetime =~ /\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z/;
650              
651 0         0 $utcdatetime =~ s/T/ /;
652 0         0 $utcdatetime =~ s/Z//;
653              
654 0         0 my @localtime = localtime;
655              
656 0         0 return _epochToDate (_dateToEpoch ($utcdatetime) +
657             (timegm (@localtime) - timelocal (@localtime)));
658             } # _UTC2Localtime
659              
660             sub add($$;@) {
661 1     1 1 3 my ($self, $table, $values, @ordering) = @_;
662              
663             =pod
664              
665             =head2 add ($$;@)
666              
667             Insert a new record into the database
668              
669             Parameters:
670              
671             =for html
672              
673             =over
674              
675             =item $table
676              
677             The name of the table to insert into
678              
679             =item $values
680              
681             Hash reference of name/value pairs for the insertion
682              
683             =item @ordering
684              
685             Array containing field names that need to be processed in order. Not all fields
686             mentioned in the $values hash need be mentioned here. If you have fields that
687             must be set in a particular order you can mention them here. So, if you're
688             adding the Defect record, but you need Project set before Platform, you need
689             only pass in an @ordering of qw(Project Platform). They will be done first, then
690             all of the rest of the fields in the $values hash. If you have no ordering
691             dependencies then you can simply omit @ordering.
692              
693             Note that the best way to determine if you have an ordering dependency try using
694             a Clearquest client and note the order that you set fields in. If at anytime
695             setting one field negates another field via action hook code then you have just
696             figured out that this field needs to be set before the file that just got
697             negated.
698              
699             =back
700              
701             =for html
702              
703             Returns:
704              
705             =for html
706              
707             =over
708              
709             =item $dbid
710              
711             The DBID of the newly added record or undef if error.
712              
713             =back
714              
715             =for html
716              
717             =cut
718              
719 1         2 $self->{errmsg} = '';
720              
721 1 50       3 unless ($self->connected) {
722 0         0 $self->_setError ('You must connect to Clearquest before you can call add');
723              
724 0         0 return;
725             } # unless
726              
727 1         4 my %values = %$values;
728 1         2 my $entity;
729              
730 1         2 eval {$entity = $self->{session}->BuildEntity ($table)};
  1         4  
731              
732 1 50       11 if ($@) {
733 0         0 $self->_setError ("Unable to create new $table record:\n$@");
734              
735 0         0 return;
736             } # if
737              
738             # First process all fields in @ordering, if specified
739 1         2 for (@ordering) {
740 0 0       0 if ($values{$_}) {
741             $self->{errmsg} =
742 0         0 $self->_setFieldValue ($entity, $table, $_, $values{$_});
743             } else {
744 0         0 $self->_setError (
745             "$_ from the ordering array is not present in the value hash", -1);
746             } # if
747              
748 0 0       0 last unless $self->{errmsg} eq '';
749             } # for
750              
751 1 50       4 return unless $self->{errmsg} eq '';
752              
753             # Now process the rest of the values
754 1         3 for my $fieldName (keys %values) {
755 3 50       8 next if grep {$fieldName eq $_} @ordering;
  0         0  
756              
757             $self->{errmsg} =
758 3         7 $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName});
759              
760 3 50       9 last unless $self->{errmsg} eq '';
761             } # for
762              
763 1         4 $self->_setError ($self->{errmsg});
764              
765 1 50       3 return unless $self->{errmsg} eq '';
766              
767 1         2 $self->{errmsg} = $self->_commitRecord ($entity);
768 1 50       3 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
769              
770 1         3 my $dbid = $entity->GetFieldValue ('dbid')->GetValue;
771              
772 1         35 return $dbid;
773             } # add
774              
775             sub connect(;$$$$) {
776 1     1 1 413 my ($self, $username, $password, $database, $dbset) = @_;
777              
778             =pod
779              
780             =head2 connect (;$$$$)
781              
782             Connect to the Clearquest database. You can supply parameters such as username,
783             password, etc and they will override any passed to Clearquest::new (or those
784             coming from ../etc/cq.conf)
785              
786             Parameters:
787              
788             =for html
789              
790             =over
791              
792             =item $username
793              
794             Username to use to connect to the database
795              
796             =item $password
797              
798             Password to use to connect to the database
799              
800             =item $database
801              
802             Clearquest database to connect to
803              
804             =item $dbset
805              
806             Database set to connect to (Default: Connect to the default dbset)
807              
808             =back
809              
810             =for html
811              
812             Returns:
813              
814             =for html
815              
816             =over
817              
818             =item 1
819              
820             =back
821              
822             =for html
823              
824             =cut
825              
826 1 50       5 return unless $self->{module} eq 'api';
827              
828 1         2 eval {require CQPerlExt};
  1         7  
829              
830 1 50       3 croak "Unable to use Rational's CQPerlExt library - "
831             . "You must use cqperl to use the Clearquest API back end\n$@"
832             if $@;
833              
834 1 50       3 $self->{username} = $username if $username;
835 1 50       2 $self->{password} = $password if $password;
836 1 50       2 $self->{database} = $database if $database;
837 1 50       3 $self->{dbset} = $dbset if $dbset;
838              
839 1         4 $self->{session} = CQSession::Build ();
840              
841 1         7 $self->{loggedin} = 0;
842              
843 1         1 eval {
844             $self->{session}->UserLogon (
845             $self->{username}, $self->{password},
846             $self->{database}, $self->{dbset}
847 1         5 );
848             };
849              
850 1 50       8 if ($@) {
851 0         0 chomp ($@);
852              
853 0         0 $self->_setError ($@, 1);
854             } else {
855 1         2 $self->{loggedin} = 1;
856              
857 1         3 $self->_setError ($_, 0);
858             } # if
859              
860 1         5 return $self->{loggedin};
861             } # connect
862              
863             sub connected() {
864 6     6 1 12 my ($self) = @_;
865              
866             =pod
867              
868             =head2 connected ()
869              
870             Returns 1 if we are currently connected to Clearquest
871              
872             Parameters:
873              
874             =for html
875              
876             =over
877              
878             =item none
879              
880             =back
881              
882             =for html
883              
884             Returns:
885              
886             =for html
887              
888             =over
889              
890             =item 1 if logged in - 0 if not
891              
892             =back
893              
894             =for html
895              
896             =cut
897              
898 6         23 return $self->{loggedin};
899             } # connected
900              
901             sub connection($) {
902 0     0 1 0 my ($self, $fullyQualify) = @_;
903              
904             =pod
905              
906             =head2 connection ()
907              
908             Returns a connection string that describes the current connection
909              
910             Parameters:
911              
912             =for html
913              
914             =over
915              
916             =item $fullyQualify
917              
918             If true the connection string will be fully qualified
919              
920             =back
921              
922             =for html
923              
924             Returns:
925              
926             =for html
927              
928             =over
929              
930             =item $connectionStr
931              
932             A string describing the current connection. Generally
933             @[/]. Note that is only displayed if it is
934             not the default DBSet as defined in cq.conf.
935              
936             =back
937              
938             =for html
939              
940             =cut
941              
942 0         0 my $connectionStr = $self->username () . '@' . $self->database ();
943              
944 0 0       0 if ($fullyQualify) {
945 0         0 $connectionStr .= '/' . $self->dbset;
946             } else {
947 0 0       0 $connectionStr .= '/' . $self->dbset ()
948             unless $self->dbset eq $DEFAULT_DBSET;
949             } # if
950              
951 0         0 return $connectionStr;
952             } # connection
953              
954             sub checkErr(;$$$) {
955 0     0 1 0 my ($self, $msg, $die, $log) = @_;
956              
957             =pod
958              
959             =head2 checkErr (;$$)
960              
961             Checks for error in the last Clearquest method call and prints error to STDERR.
962             Optionally prints a user message if $msg is specified. Dies if $die is true
963              
964             Parameters:
965              
966             =for html
967              
968             =over
969              
970             =item $msg
971              
972             User error message
973              
974             =item $die
975              
976             Causes caller to croak if set to true
977              
978             =back
979              
980             =for html
981              
982             Returns:
983              
984             =for html
985              
986             =over
987              
988             =item $error
989              
990             Returns 0 for no error, non-zero if error.
991              
992             =back
993              
994             =for html
995              
996             =cut
997              
998 0   0     0 $die ||= 0;
999              
1000 0 0       0 if ($self->{error}) {
1001 0 0       0 if ($msg) {
1002 0         0 $msg .= "\n" . $self->errmsg . "\n";
1003             } else {
1004 0         0 $msg = $self->errmsg . "\n";
1005             } # if
1006              
1007 0 0       0 if ($die) {
1008 0 0       0 $log->err ($msg) if $log;
1009 0         0 croak $msg;
1010             } else {
1011 0 0       0 if ($log) {
1012 0         0 $log->err ($msg);
1013             } else {
1014 0         0 print STDERR "$msg\n";
1015             } # if
1016              
1017 0         0 return $self->{error};
1018             } # if
1019             } # if
1020              
1021 0         0 return 0;
1022             } # checkErr
1023              
1024             sub database() {
1025 0     0 1 0 my ($self) = @_;
1026              
1027             =pod
1028              
1029             =head2 database
1030              
1031             Returns the current database (or the database that would be used)
1032              
1033             Parameters:
1034              
1035             =for html
1036              
1037             =over
1038              
1039             =item none
1040              
1041             =back
1042              
1043             =for html
1044              
1045             Returns:
1046              
1047             =for html
1048              
1049             =over
1050              
1051             =item database
1052              
1053             =back
1054              
1055             =for html
1056              
1057             =cut
1058              
1059 0         0 return $self->{database};
1060             } # database
1061              
1062             sub dbset() {
1063 0     0 1 0 my ($self) = @_;
1064              
1065             =pod
1066              
1067             =head2 dbset
1068              
1069             Returns the current dbset (or the dbset that would be used)
1070              
1071             Parameters:
1072              
1073             =for html
1074              
1075             =over
1076              
1077             =item none
1078              
1079             =back
1080              
1081             =for html
1082              
1083             Returns:
1084              
1085             =for html
1086              
1087             =over
1088              
1089             =item dbset
1090              
1091             =back
1092              
1093             =for html
1094              
1095             =cut
1096              
1097 0         0 return $self->{dbset};
1098             } # dbset
1099              
1100             sub dbsets() {
1101 0     0 1 0 my ($self) = @_;
1102              
1103             =pod
1104              
1105             =head2 dbsets ()
1106              
1107             Return the installed DBSets for this schema
1108              
1109             Parameters:
1110              
1111             =for html
1112              
1113             =over
1114              
1115             =item none
1116              
1117             =back
1118              
1119             =for html
1120              
1121             Returns:
1122              
1123             =for html
1124              
1125             =over
1126              
1127             =item @dbsets
1128              
1129             An array of dbsets
1130              
1131             =back
1132              
1133             =for html
1134              
1135             =cut
1136              
1137 0 0       0 unless ($self->connected) {
1138 0         0 $self->_setError (
1139             'You must connect to Clearquest before you can call DBSets', '-1');
1140              
1141 0         0 return;
1142             } # unless
1143              
1144 0         0 return @{$self->{session}->GetInstalledDbSets};
  0         0  
1145             } # dbsets
1146              
1147             sub delete($;$) {
1148 0     0 1 0 my ($self, $table, $key) = @_;
1149              
1150             =pod
1151              
1152             =head2 delete ($;$)
1153              
1154             Deletes records from the database
1155              
1156             Parameters:
1157              
1158             =for html
1159              
1160             =over
1161              
1162             =item $table
1163              
1164             Table to delete records from
1165              
1166             =item $key
1167              
1168             Key of the record to delete
1169              
1170             =back
1171              
1172             =for html
1173              
1174             Returns:
1175              
1176             =for html
1177              
1178             =over
1179              
1180             =item $errmsg
1181              
1182             Error message or blank if no error
1183              
1184             =back
1185              
1186             =for html
1187              
1188             =cut
1189              
1190 0         0 my $entity;
1191              
1192 0         0 eval {$entity = $self->{session}->GetEntity ($table, $key)};
  0         0  
1193              
1194 0 0       0 if ($@) {
1195 0         0 $self->_setError ($@, 1);
1196              
1197 0         0 return $@;
1198             } # if
1199              
1200 0         0 eval {$self->{session}->DeleteEntity ($entity, 'Delete')};
  0         0  
1201              
1202 0 0       0 if ($@) {
1203 0         0 $self->_setError ($@, 1);
1204              
1205 0         0 return $@;
1206             } # if
1207              
1208 0         0 return '';
1209             } # delete
1210              
1211             sub DESTROY() {
1212 1     1   3 my ($self) = @_;
1213              
1214 1 50       4 CQSession::Unbuild ($self->{session}) if $self->{session};
1215              
1216 1         30 return;
1217             } # DESTROY
1218              
1219             sub disconnect() {
1220 1     1 1 785 my ($self) = @_;
1221              
1222             =pod
1223              
1224             =head2 disconnect ()
1225              
1226             Disconnect from Clearquest
1227              
1228             Parameters:
1229              
1230             =for html
1231              
1232             =over
1233              
1234             =item none
1235              
1236             =back
1237              
1238             =for html
1239              
1240             Returns:
1241              
1242             =for html
1243              
1244             =over
1245              
1246             =item nothing
1247              
1248             =back
1249              
1250             =for html
1251              
1252             =cut
1253              
1254 1         5 CQSession::Unbuild ($self->{session});
1255              
1256 1         9 undef $self->{session};
1257              
1258 1         2 $self->{loggedin} = 0;
1259              
1260 1         3 return;
1261             } # disconnect
1262              
1263             sub errmsg(;$) {
1264 0     0 1 0 my ($self, $errmsg) = @_;
1265              
1266             =pod
1267              
1268             =head2 errmsg ()
1269              
1270             Returns the last error message. Optionally sets the error message if specified.
1271              
1272             Parameters:
1273              
1274             =for html
1275              
1276             =over
1277              
1278             =item $errmsg
1279              
1280             =back
1281              
1282             =for html
1283              
1284             Returns:
1285              
1286             =for html
1287              
1288             =over
1289              
1290             =item $errmsg
1291              
1292             Last $errmsg
1293              
1294             =back
1295              
1296             =for html
1297              
1298             =cut
1299              
1300 0 0       0 $self->{errmsg} = $errmsg if $errmsg;
1301              
1302 0         0 return $self->{errmsg};
1303             } # errmsg
1304              
1305             sub error(;$) {
1306 5     5 1 8 my ($self, $error) = @_;
1307              
1308             =pod
1309              
1310             =head2 error ($error)
1311              
1312             Returns the last error number. Optional set the error number if specified
1313              
1314             Parameters:
1315              
1316             =for html
1317              
1318             =over
1319              
1320             =item $error
1321              
1322             Error number to set
1323              
1324             =back
1325              
1326             =for html
1327              
1328             Returns:
1329              
1330             =for html
1331              
1332             =over
1333              
1334             =item $error
1335              
1336             Last error
1337              
1338             =back
1339              
1340             =for html
1341              
1342             =cut
1343              
1344             # Watch here as $error can very well be 0 which "if $error" would evaluate
1345             # to false leaving $self->{error} undefined
1346 5 50       12 $self->{error} = $error if defined $error;
1347              
1348 5         9 return $self->{error};
1349             } # error
1350              
1351             sub fieldType($$) {
1352 0     0 1 0 my ($self, $table, $fieldName) = @_;
1353              
1354             =pod
1355              
1356             =head2 fieldType ($table, $fieldname)
1357              
1358             Returns the field type for the $table, $fieldname combination.
1359              
1360             Parameters:
1361              
1362             =for html
1363              
1364             =over
1365              
1366             =item $table
1367              
1368             Table to return field type from.
1369              
1370             =item $fieldname
1371              
1372             Fieldname to return the field type from.
1373              
1374             =back
1375              
1376             =for html
1377              
1378             Returns:
1379              
1380             =for html
1381              
1382             =over
1383              
1384             =item $fieldType
1385              
1386             Fieldtype enum
1387              
1388             =back
1389              
1390             =for html
1391              
1392             =cut
1393              
1394 0 0       0 return $UNKNOWN unless $self->{loggedin};
1395              
1396             # If we've already computed the fieldTypes for the fields in this table then
1397             # return the value
1398 0 0       0 if ($FIELDS{$table}) {
1399              
1400             # If we already have this fieldType just return it
1401 0 0       0 if (defined $FIELDS{$table}{$fieldName}) {
1402 0         0 return $FIELDS{$table}{$fieldName};
1403             } else {
1404 0         0 return $UNKNOWN;
1405             } # if
1406             } # if
1407              
1408 0         0 my $entityDef = $self->{session}->GetEntityDef ($table);
1409              
1410 0         0 for (@{$entityDef->GetFieldDefNames}) {
  0         0  
1411 0         0 $FIELDS{$table}{$_} = $entityDef->GetFieldDefType ($_);
1412             } # for
1413              
1414 0 0       0 if (defined $FIELDS{$table}{$fieldName}) {
1415 0         0 return $FIELDS{$table}{$fieldName};
1416             } else {
1417 0         0 return $UNKNOWN;
1418             } # if
1419             } # fieldType
1420              
1421             sub fieldTypeName($$) {
1422 0     0 1 0 my ($self, $table, $fieldName) = @_;
1423              
1424             =pod
1425              
1426             =head2 fieldTypeName ($table, $fieldname)
1427              
1428             Returns the field type name for the $table, $fieldname combination.
1429              
1430             Parameters:
1431              
1432             =for html
1433              
1434             =over
1435              
1436             =item $table
1437              
1438             Table to return field type from.
1439              
1440             =item $fieldname
1441              
1442             Fieldname to return the field type from.
1443              
1444             =back
1445              
1446             =for html
1447              
1448             Returns:
1449              
1450             =for html
1451              
1452             =over
1453              
1454             =item $fieldTypeName
1455              
1456             Fieldtype name
1457              
1458             =back
1459              
1460             =for html
1461              
1462             =cut
1463              
1464 0         0 my $fieldType = $self->fieldType ($table, $fieldName);
1465              
1466 0 0       0 return $UNKNOWN unless $fieldType;
1467              
1468 0 0       0 if ($fieldType == $STRING) {
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
1469 0         0 return "STRING";
1470             } elsif ($fieldType == $MULTILINE_STRING) {
1471 0         0 return "MULTILINE_STRING";
1472             } elsif ($fieldType == $INT) {
1473 0         0 return "INT";
1474             } elsif ($fieldType == $DATE_TIME) {
1475 0         0 return "DATE_TIME";
1476             } elsif ($fieldType == $REFERENCE) {
1477 0         0 return "REFERENCE";
1478             } elsif ($fieldType == $REFERENCE_LIST) {
1479 0         0 return "REFERENCE_LIST";
1480             } elsif ($fieldType == $ATTACHMENT_LIST) {
1481 0         0 return "ATTACHMENT_LIST";
1482             } elsif ($fieldType == $ID) {
1483 0         0 return "ID";
1484             } elsif ($fieldType == $STATE) {
1485 0         0 return "STATE";
1486             } elsif ($fieldType == $JOURNAL) {
1487 0         0 return "JOURNAL";
1488             } elsif ($fieldType == $DBID) {
1489 0         0 return "DBID";
1490             } elsif ($fieldType == $STATETYPE) {
1491 0         0 return "STATETYPE";
1492             } elsif ($fieldType == $RECORD_TYPE) {
1493 0         0 return "RECORD_TYPE";
1494             } elsif ($fieldType == $UNKNOWN) {
1495 0         0 return "UNKNOWN";
1496             } # if
1497             } # fieldTypeName
1498              
1499             sub find($;$@) {
1500 0     0 1 0 my ($self, $table, $condition, @fields) = @_;
1501              
1502             =pod
1503              
1504             =head2 find ($;$@)
1505              
1506             Find records in $table. You can specify a $condition and which fields you wish
1507             to retrieve. Specifying a smaller set of fields means less data transfered and
1508             quicker retrieval so only retrieve the fields you really need.
1509              
1510             Parameters:
1511              
1512             =for html
1513              
1514             =over
1515              
1516             =item $table
1517              
1518             Name of the table to search
1519              
1520             =item $condition
1521              
1522             Condition to use. If you want all records then pass in undef. Only simple
1523             conditions are supported. You can specify compound conditions (e.g. field1 ==
1524             'foo' and field1 == 'bar' or field2 is not null). No parenthesizing is
1525             supported (yet).
1526              
1527             The following conditionals are supported
1528              
1529             =over
1530              
1531             =item Equal (==|=)
1532              
1533             =item Not Equal (!=|<>)
1534              
1535             =item Less than (<)
1536              
1537             =item Greater than (>)
1538              
1539             =item Less than or equal (<=)
1540              
1541             =item Greater than or equal (>=)
1542              
1543             =item Like
1544              
1545             =item Is null
1546              
1547             =item Is not null
1548              
1549             =item In
1550              
1551             =back
1552              
1553             Note that "is not null" is currently not working in the REST module (it works
1554             in the api and thus also in the client/server model). This because the
1555             OLSC spec V1.0 does not support it.
1556              
1557             As for "Like"", you'll need to specify " like '%var%'" for the
1558             condition.
1559              
1560             "In" is only available in the REST interface as that's what OLSC supports. It's
1561             syntax would be " In 'value1', 'value2', 'value3'..."
1562              
1563             Also conditions can be combined with (and|or) so in the api you could do "in"
1564             as " = 'value1 or = 'value2" or = 'value3'".
1565              
1566             Complicated expressions with parenthesis like "(Project = 'Athena' or Project =
1567             'Hawaii') and Category = 'Aspen'" are not supported.
1568              
1569             =item @fields
1570              
1571             An array of fieldnames to retrieve
1572              
1573             =back
1574              
1575             =for html
1576              
1577             Returns:
1578              
1579             =for html
1580              
1581             =over
1582              
1583             =item $result or ($result, $nbrRecs)
1584              
1585             Internal structure to be used with getNext. If in an array context then $nbrRecs
1586             is also returned.
1587              
1588             =back
1589              
1590             =for html
1591              
1592             =cut
1593              
1594 0   0     0 $condition ||= '';
1595              
1596 0 0       0 unless ($self->connected) {
1597 0         0 $self->_setError ('You must connect to Clearquest before you can call find',
1598             '-1');
1599              
1600 0         0 return;
1601             } # unless
1602              
1603 0         0 my $entityDef;
1604              
1605 0         0 eval {$entityDef = $self->{session}->GetEntityDef ($table)};
  0         0  
1606              
1607 0 0       0 if ($@) {
1608 0         0 $self->_setError ($@, -1);
1609              
1610 0         0 return ($@, -1);
1611             } # if
1612              
1613 0         0 @fields = $self->_setFields ($table, @fields);
1614              
1615 0 0       0 return unless @fields;
1616              
1617 0         0 my $query = $self->{session}->BuildQuery ($table);
1618              
1619 0         0 for (@fields) {
1620 0         0 eval {$query->BuildField ($_)};
  0         0  
1621              
1622 0 0       0 if ($@) {
1623 0         0 $self->_setError ($@);
1624              
1625 0         0 carp $@;
1626             } # if
1627             } # for
1628              
1629 0         0 $self->_parseConditional ($query, $condition);
1630              
1631 0 0       0 return if $self->error;
1632              
1633 0         0 my $result = $self->{session}->BuildResultSet ($query);
1634 0         0 my $nbrRecs = $result->ExecuteAndCountRecords;
1635              
1636 0         0 $self->_setError;
1637              
1638 0         0 my %resultSet = (result => $result);
1639              
1640 0 0       0 if (wantarray) {
1641 0         0 return (\%resultSet, $nbrRecs);
1642             } else {
1643 0         0 return \%resultSet;
1644             } # if
1645             } # find
1646              
1647             sub findIDs($) {
1648 0     0 1 0 my ($str) = @_;
1649              
1650             =pod
1651              
1652             =head2 findIDs ($)
1653              
1654             Given a $str or a reference to an array of strings, this function returns a list
1655             of Clearquest IDs found in the $str. If called in a scalar context this function
1656             returns a comma separated string of IDs found. Note that duplicate IDs are
1657             eliminated. Also, the lists of IDs may refer to different Clearquest databases.
1658              
1659             Parameters:
1660              
1661             =for html
1662              
1663             =over
1664              
1665             =item $str
1666              
1667             String or reference to an array of strings to search
1668              
1669             =back
1670              
1671             =for html
1672              
1673             Returns:
1674              
1675             =for html
1676              
1677             =over
1678              
1679             =item @IDs or $strIDs
1680              
1681             Either an array of CQ IDs or a comma separated list of CQ IDs.
1682              
1683             =back
1684              
1685             =for html
1686              
1687             =cut
1688              
1689 0 0       0 $str = join ' ', @$str if ref $str eq 'ARRAY';
1690              
1691 0         0 my @IDs = $str =~ /([A-Za-z]\w{1,4}\d{8})/gs;
1692              
1693 0         0 my %IDs;
1694              
1695 0         0 map {$IDs{$_} = 1;} @IDs;
  0         0  
1696              
1697 0 0       0 if (wantarray) {
1698 0         0 return keys %IDs;
1699             } else {
1700 0         0 return join ',', keys %IDs;
1701             } # if
1702             } # findIDs
1703              
1704             sub get($$;@) {
1705 2     2 1 318 my ($self, $table, $id, @fields) = @_;
1706              
1707             =pod
1708              
1709             =head2 get ($$)
1710              
1711             Return a record that you have the id or key of.
1712              
1713             Parameters:
1714              
1715             =for html
1716              
1717             =over
1718              
1719             =item $table
1720              
1721             The $table to get the record from
1722              
1723             =item $id
1724              
1725             The $id or key to use to retrieve the record
1726              
1727             =back
1728              
1729             =for html
1730              
1731             Returns:
1732              
1733             =for html
1734              
1735             =over
1736              
1737             =item %record
1738              
1739             Hash of name/value pairs for all the fields in $table
1740              
1741             =back
1742              
1743             =for html
1744              
1745             =cut
1746              
1747 2 50       6 unless ($self->connected) {
1748 0         0 $self->_setError ('You must connect to Clearquest before you can call get',
1749             '-1');
1750              
1751 0         0 return;
1752             } # unless
1753              
1754 2         7 @fields = $self->_setFields ($table, @fields);
1755              
1756 2 50       14 return unless @fields;
1757              
1758 2         3 my $entity;
1759              
1760 2         2 eval {$entity = $self->{session}->GetEntity ($table, $id)};
  2         7  
1761              
1762 2 50       20 if ($@) {
1763 0         0 $self->_setError ($@);
1764              
1765 0         0 return;
1766             } # if
1767              
1768 2         4 my %record;
1769              
1770 2         15 for (@fields) {
1771 7         15 my $fieldType = $entity->GetFieldValue ($_)->GetType;
1772              
1773 7 50       52 if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) {
1774 0         0 $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList;
1775             } else {
1776 7         12 $record{$_} = $entity->GetFieldValue ($_)->GetValue;
1777 7 50 0     62 $record{$_} ||= '' if $self->{emptyStringForUndef};
1778              
1779             # Fix any UTC dates
1780 7 50       14 if ($fieldType == $CQPerlExt::CQ_DATE_TIME) {
1781 0         0 $record{$_} = _UTC2Localtime ($record{$_});
1782             } # if
1783             } # if
1784             } # for
1785              
1786 2         6 $self->_setError;
1787              
1788 2         9 return %record;
1789             } # get
1790              
1791             sub getDBID($$;@) {
1792 0     0 1 0 my ($self, $table, $dbid, @fields) = @_;
1793              
1794             =pod
1795              
1796             =head2 getDBID ($$;@)
1797              
1798             Return a record that you have the dbid
1799              
1800             Parameters:
1801              
1802             =for html
1803              
1804             =over
1805              
1806             =item $table
1807              
1808             The $table to get the record from
1809              
1810             =item $dbid
1811              
1812             The $dbid to use to retrieve the record
1813              
1814             =item @fields
1815              
1816             Array of field names to retrieve (Default: All fields)
1817              
1818             Note: Avoid getting all fields for large records. It will be slow and bloat your
1819             script's memory usage.
1820              
1821             =back
1822              
1823             =for html
1824              
1825             Returns:
1826              
1827             =for html
1828              
1829             =over
1830              
1831             =item %record
1832              
1833             Hash of name/value pairs for all the fields in $table
1834              
1835             =back
1836              
1837             =for html
1838              
1839             =cut
1840              
1841 0 0       0 unless ($self->connected) {
1842 0         0 $self->_setError (
1843             'You must connect to Clearquest before you can call getDBID', '-1');
1844              
1845 0         0 return;
1846             } # unless
1847              
1848 0         0 @fields = $self->_setFields ($table, @fields);
1849              
1850 0         0 my $entity;
1851              
1852 0         0 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
  0         0  
1853              
1854 0 0       0 if ($@) {
1855 0         0 $self->_setError ($@);
1856              
1857 0         0 return;
1858             } # if
1859              
1860 0         0 my %record;
1861              
1862 0         0 for (@fields) {
1863 0         0 my $fieldType = $entity->GetFieldValue ($_)->GetType;
1864              
1865 0 0       0 if ($fieldType == $CQPerlExt::CQ_REFERENCE_LIST) {
1866 0         0 $record{$_} = $entity->GetFieldValue ($_)->GetValueAsList;
1867             } else {
1868 0         0 $record{$_} = $entity->GetFieldValue ($_)->GetValue;
1869 0 0 0     0 $record{$_} ||= '' if $self->{emptyStringForUndef};
1870              
1871             # Fix any UTC dates
1872 0 0       0 if ($fieldType == $CQPerlExt::CQ_DATE_TIME) {
1873 0         0 $record{$_} = _UTC2Localtime ($record{$_});
1874             } # if
1875             } # if
1876             } # for
1877              
1878 0         0 $self->_setError;
1879              
1880 0         0 return %record;
1881             } # getDBID
1882              
1883             sub getDynamicList($) {
1884 0     0 1 0 my ($self, $list) = @_;
1885              
1886             =pod
1887              
1888             =head2 getDynamicList ($)
1889              
1890             Return the entries of a dynamic list
1891              
1892             Parameters:
1893              
1894             =for html
1895              
1896             =over
1897              
1898             =item $list
1899              
1900             The name of the dynamic list
1901              
1902             =back
1903              
1904             =for html
1905              
1906             Returns:
1907              
1908             =for html
1909              
1910             =over
1911              
1912             =item @entries
1913              
1914             An array of entries from the dynamic list
1915              
1916             =back
1917              
1918             =for html
1919              
1920             =cut
1921              
1922 0 0       0 return () unless $self->connected;
1923              
1924 0         0 return @{$self->{session}->GetListMembers ($list)};
  0         0  
1925             } # getDynamicList
1926              
1927             sub getNext($) {
1928 0     0 1 0 my ($self, $result) = @_;
1929              
1930             =pod
1931              
1932             =head2 getNext ($)
1933              
1934             Return the next record that qualifies from a preceeding call to the find method.
1935              
1936             Parameters:
1937              
1938             =for html
1939              
1940             =over
1941              
1942             =item $result
1943              
1944             The $result returned from find.
1945              
1946             =back
1947              
1948             =for html
1949              
1950             Returns:
1951              
1952             =for html
1953              
1954             =over
1955              
1956             =item %record
1957              
1958             Hash of name/value pairs for the @fields specified to find.
1959              
1960             =back
1961              
1962             =for html
1963              
1964             =cut
1965              
1966 0 0       0 unless ($self->connected) {
1967 0         0 $self->_setError (
1968             'You must connect to Clearquest before you can call getNext', '-1');
1969              
1970 0         0 return;
1971             } # unless
1972              
1973             # Here we need to do special processing to gather up reference list fields, if
1974             # any. If we have a reference list field in the field list then Clearquest
1975             # returns multiple records - one for each entry in the reference list. Thus if
1976             # you were getting say the key field of a record and a reference list field like
1977             # say Projects, you might see:
1978             #
1979             # Key Value Projects
1980             # --------- --------
1981             # key1 Athena
1982             # key1 Apollo
1983             # key1 Gemini
1984             #
1985             # Things get combinatoric when multiple reference list fields are involved. Our
1986             # strategy here is to keep gathering all fields that change into arrays assuming
1987             # they are reference fields as long as the dbid field has not changed.
1988 0         0 my %record;
1989              
1990 0         0 while () {
1991 0 0       0 unless ($result->{lastDBID}) {
    0          
1992              
1993             # Move to the first record
1994 0 0       0 last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS;
1995 0         0 } elsif ($result->{lastDBID} == $result->{thisDBID}) {
1996              
1997             # If the dbid is the same then we have at least one reference list field
1998             # in the request so we need to move to the next record
1999 0 0       0 last unless $result->{result}->MoveNext == $CQPerlExt::CQ_SUCCESS;
2000             } else {
2001              
2002             # If lastDBID != thisDBID then set lastDBID to thisDBID so we can process
2003             # this group
2004 0         0 $result->{lastDBID} = $result->{thisDBID};
2005              
2006 0         0 delete $result->{lastRecord};
2007             } # unless
2008              
2009 0         0 my $nbrColumns = $result->{result}->GetNumberOfColumns;
2010              
2011 0         0 my $column = 1;
2012              
2013             # Format %record
2014 0         0 while ($column <= $nbrColumns) {
2015 0         0 my $name = $result->{result}->GetColumnLabel ($column);
2016 0         0 my $value = $result->{result}->GetColumnValue ($column++);
2017              
2018             # Fix any UTC dates - _UTC2Localtime will only modify data if the data
2019             # matches a UTC datetime.
2020 0 0       0 $value = _UTC2Localtime ($value) if $value;
2021              
2022 0 0 0     0 $value ||= '' if $self->{emptyStringForUndef};
2023              
2024 0         0 $record{$name} = $value;
2025             } # while
2026              
2027 0 0       0 %{$result->{lastRecord}} = %record unless $result->{lastRecord};
  0         0  
2028              
2029             # Store this record's DBID
2030 0         0 $result->{thisDBID} = $record{dbid};
2031              
2032 0 0       0 if ($result->{lastDBID}) {
2033 0 0       0 if ($result->{thisDBID} == $result->{lastDBID}) {
2034              
2035             # Since the dbid's are the same, we have at least one reference list field
2036             # and we need to compare all fields
2037 0         0 for my $field (keys %record) {
2038              
2039             # If the field is blank then skip it
2040 0 0       0 next if $record{$field} eq '';
2041              
2042             # Here we check the field in %lastRecord to see if it was a reference
2043             # list with more than one entry.
2044 0 0       0 if (ref \$result->{lastRecord}{$field} eq 'ARRAY') {
2045              
2046             # Check to see if this entry is already in the list of current entries
2047 0 0       0 next if grep {/^$record{$field}$/} @{$result->{lastRecord}{$field}};
  0         0  
  0         0  
2048             } # if
2049              
2050             # This checks to see if the current field is a scalar and we have a new
2051             # value, then the scalar needs to be changed to an array
2052 0 0       0 if (ref \$result->{lastRecord}{$field} eq 'SCALAR') {
2053              
2054             # If the field is the same value then no change, no array. We do next
2055             # to start processing the next field
2056 0 0       0 next if $result->{lastRecord}{$field} eq $record{$field};
2057              
2058             # Changed $lastRecord{$_} to a reference to an ARRAY
2059             $result->{lastRecord}{$field} =
2060 0         0 [$result->{lastRecord}{$field}, $record{$field}];
2061             } else {
2062              
2063             # Push the value only if it does not already exists in the array
2064 0         0 push @{$result->{lastRecord}{$field}}, $record{$field}
2065 0         0 unless grep {/^$record{$field}$/}
2066 0 0       0 @{$result->{lastRecord}{$field}};
  0         0  
2067             } # if
2068             } # for
2069              
2070             # Transfer %lastRecord -> %record
2071 0         0 %record = %{$result->{lastRecord}};
  0         0  
2072             } else {
2073 0         0 %record = %{$result->{lastRecord}};
  0         0  
2074              
2075 0         0 last;
2076             } # if
2077             } # if
2078              
2079             # The $lastDBID is now $thisDBID
2080 0         0 $result->{lastDBID} = $result->{thisDBID};
2081              
2082             # Update %lastRecord
2083 0         0 %{$result->{lastRecord}} = %record;
  0         0  
2084             } # while
2085              
2086 0         0 $self->_setError;
2087              
2088             # Never return dbid...
2089 0         0 delete $record{dbid};
2090              
2091 0         0 return %record;
2092             } # getNext
2093              
2094             sub id2db($) {
2095 0     0 1 0 my ($ID) = @_;
2096              
2097             =pod
2098              
2099             =head2 id2db ($)
2100              
2101             This function returns the database name given an ID.
2102              
2103             Parameters:
2104              
2105             =for html
2106              
2107             =over
2108              
2109             =item $ID
2110              
2111             The ID to extract the database name from
2112              
2113             =back
2114              
2115             =for html
2116              
2117             Returns:
2118              
2119             =for html
2120              
2121             =over
2122              
2123             =item $database
2124              
2125             Returns the name of the database the ID is part of or undef if not found.
2126              
2127             =back
2128              
2129             =for html
2130              
2131             =cut
2132              
2133 0 0       0 if ($ID =~ /([A-Za-z]\w{1,4})\d{8}/) {
2134 0         0 return $1;
2135             } else {
2136 0         0 return;
2137             } # if
2138             } # id2db
2139              
2140             sub key($$) {
2141 0     0 1 0 my ($self, $table, $dbid) = @_;
2142              
2143             =pod
2144              
2145             =head2 key ($$)
2146              
2147             Return the key of the record given a $dbid
2148              
2149             Parameters:
2150              
2151             =for html
2152              
2153             =over
2154              
2155             =item $table
2156              
2157             Name of the table to lookup
2158              
2159             =item $dbid
2160              
2161             Database ID of the record to retrieve
2162              
2163             =back
2164              
2165             =for html
2166              
2167             Returns:
2168              
2169             =for html
2170              
2171             =over
2172              
2173             =item key
2174              
2175             =back
2176              
2177             =for html
2178              
2179             =cut
2180              
2181 0 0       0 unless ($self->connected) {
2182 0         0 $self->_setError ('You must connect to Clearquest before you can call key',
2183             '-1');
2184              
2185 0         0 return;
2186             } # unless
2187              
2188 0         0 my $entity;
2189              
2190 0         0 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
  0         0  
2191              
2192 0         0 return $entity->GetDisplayName;
2193             } # key
2194              
2195             sub modify($$$$;@) {
2196 1     1 1 952 my ($self, $table, $key, $action, $values, @ordering) = @_;
2197              
2198             =pod
2199              
2200             =head2 modify ($$$$;@)
2201              
2202             Update record(s)
2203              
2204             Parameters:
2205              
2206             =for html
2207              
2208             =over
2209              
2210             =item $table
2211              
2212             The $table to get the record from
2213              
2214             =item $key
2215              
2216             The $key identifying the record to modify
2217              
2218             =item $action
2219              
2220             Action to perform the modification under. Default is 'Modify'.
2221              
2222             =item $values
2223              
2224             Hash reference containing name/value that have the new values for the fields
2225              
2226             =item @ordering
2227              
2228             Array containing field names that need to be processed in order. Not all fields
2229             mentioned in the $values hash need be mentioned here. If you have fields that
2230             must be set in a particular order you can mention them here. So, if you're
2231             modifying the Defect record, but you need Project set before Platform, you need
2232             only pass in an @ordering of qw(Project Platform). They will be done first, then
2233             all of the rest of the fields in the $values hash. If you have no ordering
2234             dependencies then you can simply omit @ordering.
2235              
2236             Note that the best way to determine if you have an ordering dependency try using
2237             a Clearquest client and note the order that you set fields in. If at anytime
2238             setting one field negates another field via action hook code then you have just
2239             figured out that this field needs to be set before the file that just got
2240             negated.
2241              
2242             =back
2243              
2244             =for html
2245              
2246             Returns:
2247              
2248             =for html
2249              
2250             =over
2251              
2252             =item $errmsg
2253              
2254             The $errmsg, if any, when performing the update (empty string for success)
2255              
2256             =back
2257              
2258             =for html
2259              
2260             =cut
2261              
2262 1 50       3 unless ($self->connected) {
2263 0         0 $self->_setError (
2264             'You must connect to Clearquest before you can call modify', '-1');
2265              
2266 0         0 return $self->{errmsg};
2267             } # unless
2268              
2269 1         3 my %record = $self->get ($table, $key, qw(dbid));
2270              
2271 1         4 return $self->modifyDBID ($table, $record{dbid}, $action, $values, @ordering);
2272             } # modify
2273              
2274             sub modifyDBID($$$$;@) {
2275 1     1 1 2 my ($self, $table, $dbid, $action, $values, @ordering) = @_;
2276              
2277             =pod
2278              
2279             =head2 modifyDBID ($$$%)
2280              
2281             Update a unique record (by DBID)
2282              
2283             Parameters:
2284              
2285             =for html
2286              
2287             =over
2288              
2289             =item $table
2290              
2291             The $table to get the record from
2292              
2293             =item $dbid
2294              
2295             The $dbid of the record to update. Note that the find method always includes the
2296             dbid of a record in the hash that it returns.
2297              
2298             =item $action
2299              
2300             Action to perform the modification under. Default is 'Modify'.
2301              
2302             =item %update
2303              
2304             Hash containing name/value that have the new values for the fields
2305              
2306             =back
2307              
2308             =for html
2309              
2310             Returns:
2311              
2312             =for html
2313              
2314             =over
2315              
2316             =item $errmsg
2317              
2318             The $errmsg, if any, when performing the update (empty string for success)
2319              
2320             =back
2321              
2322             =for html
2323              
2324             =cut
2325              
2326 1   50     2 $action ||= 'Modify';
2327              
2328 1         2 my %values = ();
2329              
2330 1 50       4 %values = %$values if $values;
2331              
2332 1         2 my $entity;
2333              
2334 1         1 eval {$entity = $self->{session}->GetEntityByDbId ($table, $dbid)};
  1         4  
2335              
2336 1 50       8 if ($@) {
2337 0         0 $self->_setError ($@);
2338              
2339 0         0 return;
2340             } # if
2341              
2342 1         8 eval {$entity->EditEntity ($action)};
  1         22  
2343              
2344 1 50       3 if ($@) {
2345 1         3 $self->_setError ($@);
2346              
2347 1         5 return $@;
2348             } # if
2349              
2350             # First process all fields in @ordering, if specified
2351 0         0 for (@ordering) {
2352 0 0       0 if ($values{$_}) {
2353 0         0 $self->{errmsg} = $self->_setFieldValue ($table, $_, $values{$_});
2354             } else {
2355 0         0 $self->_setError (
2356             "$_ from the ordering array is not present in the value hash", -1);
2357             } # if
2358              
2359 0 0       0 last unless $self->{errmsg} eq '';
2360             } # for
2361              
2362 0 0       0 return $self->{errmsg} unless $self->{errmsg} eq '';
2363              
2364             # Now process the rest of the values
2365 0         0 for my $fieldName (keys %values) {
2366 0 0       0 next if grep {$fieldName eq $_} @ordering;
  0         0  
2367              
2368             $self->{errmsg} =
2369 0         0 $self->_setFieldValue ($entity, $table, $fieldName, $values{$fieldName});
2370              
2371 0 0       0 last unless $self->{errmsg} eq '';
2372             } # for
2373              
2374 0         0 $self->_setError ($self->{errmsg});
2375              
2376 0 0       0 return $self->{errmsg} unless $self->{errmsg} eq '';
2377              
2378 0         0 $self->{errmsg} = $self->_commitRecord ($entity);
2379 0 0       0 $self->{error} = $self->{errmsg} eq '' ? 0 : 1;
2380              
2381 0         0 return $self->{errmsg};
2382             } # modifyDBID
2383              
2384             sub module() {
2385 0     0 1 0 my ($self) = @_;
2386              
2387             =pod
2388              
2389             =head2 module
2390              
2391             Returns the current back end module we are using
2392              
2393             Parameters:
2394              
2395             =for html
2396              
2397             =over
2398              
2399             =item none
2400              
2401             =back
2402              
2403             =for html
2404              
2405             Returns:
2406              
2407             =for html
2408              
2409             =over
2410              
2411             =item module
2412              
2413             =back
2414              
2415             =for html
2416              
2417             =cut
2418              
2419 0         0 return $self->{module};
2420             } # module
2421              
2422             sub new(;%) {
2423 1     1 1 554 my ($class, %parms) = @_;
2424              
2425             =pod
2426              
2427             =head2 new ()
2428              
2429             Construct a new Clearquest object.
2430              
2431             Parameters:
2432              
2433             Below are the key values for the %parms hash.
2434              
2435             =for html
2436              
2437             =over
2438              
2439             =item CQ_SERVER
2440              
2441             Webhost for REST module
2442              
2443             =item CQ_USERNAME
2444              
2445             Username to use to connect to the database
2446              
2447             =item CQ_PASSWORD
2448              
2449             Password to use to connect to the database
2450              
2451             =item CQ_DATABASE
2452              
2453             Clearquest database to connect to
2454              
2455             =item CQ_DBSET
2456              
2457             Database set to connect to
2458              
2459             =item CQ_MODULE
2460              
2461             One of 'rest', 'api' or 'client' (Default: From cq.conf). This determines which
2462             backend module will be used.
2463              
2464             =back
2465              
2466             =for html
2467              
2468             Returns:
2469              
2470             =for html
2471              
2472             =over
2473              
2474             =item Clearquest object
2475              
2476             =back
2477              
2478             =for html
2479              
2480             =cut
2481              
2482 1   33     8 $parms{CQ_DATABASE} ||= $OPTS{CQ_DATABASE};
2483 1   33     2 $parms{CQ_USERNAME} ||= $OPTS{CQ_USERNAME};
2484 1   33     4 $parms{CQ_PASSWORD} ||= $OPTS{CQ_PASSWORD};
2485 1   33     6 $parms{CQ_DBSET} ||= $OPTS{CQ_DBSET};
2486              
2487             my $self = bless {
2488             server => $parms{CQ_SERVER},
2489             port => $parms{CQ_PORT},
2490             database => $parms{CQ_DATABASE},
2491             dbset => $parms{CQ_DBSET},
2492             username => $parms{CQ_USERNAME},
2493             password => $parms{CQ_PASSWORD},
2494 1         6 emptyStringForUndef => 0,
2495             returnSystemFields => 0,
2496             }, $class;
2497              
2498 1         3 my $module = delete $parms{CQ_MODULE};
2499              
2500 1   33     2 $module ||= $OPTS{CQ_MODULE};
2501              
2502 1         3 $module = lc $module;
2503              
2504 1 50       5 if ($module eq 'rest') {
    50          
    50          
2505 0         0 require Clearquest::REST;
2506              
2507 0   0     0 $self->{webhost} = $parms{CQ_WEBHOST} || $OPTS{CQ_WEBHOST};
2508              
2509 0         0 $self = Clearquest::REST->new ($self);
2510             } elsif ($module eq 'client') {
2511 0         0 require Clearquest::Client;
2512              
2513 0   0     0 $self->{server} = $parms{CQ_SERVER} || $OPTS{CQ_SERVER};
2514 0   0     0 $self->{port} = $parms{CQ_PORT} || $OPTS{CQ_PORT};
2515              
2516 0         0 $self = Clearquest::Client->new ($self);
2517             } elsif ($module ne 'api') {
2518 0         0 croak "Unknown interface requested - $module";
2519             } # if
2520              
2521 1         5 $self->{module} = $module;
2522              
2523             # Save reference to instaniated instance of this object to insure that global
2524             # variables are properly disposed of
2525 1         2 push @objects, $self;
2526              
2527 1         4 return $self;
2528             } # new
2529              
2530             sub server() {
2531 0     0 1 0 my ($self) = @_;
2532              
2533             =pod
2534              
2535             =head2 server
2536              
2537             Returns the current server if applicable
2538              
2539             Parameters:
2540              
2541             =for html
2542              
2543             =over
2544              
2545             =item none
2546              
2547             =back
2548              
2549             =for html
2550              
2551             Returns:
2552              
2553             =for html
2554              
2555             =over
2556              
2557             =item $server
2558              
2559             For api this will return ''. For REST and client/server this will return the
2560             server name that we are talking to.
2561              
2562             =back
2563              
2564             =for html
2565              
2566             =cut
2567              
2568 0         0 return $self->{server};
2569             } # server
2570              
2571             sub setOpts(%) {
2572 0     0 1 0 my ($self, %opts) = @_;
2573              
2574             =pod
2575              
2576             =head2 setOpts
2577              
2578             Set options for operating
2579              
2580             Parameters:
2581              
2582             =for html
2583              
2584             =over
2585              
2586             =item %opts
2587              
2588             =back
2589              
2590             Options to set. The only options currently supported are emptyStringForUndef
2591             and returnSystemFields. If set emptyStringForUndef will return empty strings for
2592             empty fields instead of undef. Default: Empty fields are represented with undef.
2593              
2594             System-owned fields are used internally by IBM Rational ClearQuest to maintain
2595             information about the database. You should never modify system fields directly
2596             as it could corrupt the database. If returnSystemFields is set then system
2597             fields will be returned. Default: System fields will not be returned unless
2598             explicitly stated in the @fields parameter. This means that if you do not
2599             specify any fields in @fields, all fields will be returned except system fields,
2600             unless you set returnSystemFields via this method or you explicitly mention the
2601             system field in your @fields parameter.
2602              
2603             =for html
2604              
2605             Returns:
2606              
2607             =for html
2608              
2609             =over
2610              
2611             =item Nothing
2612              
2613             =back
2614              
2615             =for html
2616              
2617             =cut
2618              
2619             $self->{emptyStringForUndef} = $opts{emptyStringForUndef}
2620 0 0       0 if $opts{emptyStringForUndef};
2621             $self->{returnSystemFields} = $opts{returnSystemFields}
2622 0 0       0 if $opts{returnSystemFields};
2623              
2624 0         0 return;
2625             } # setOpts
2626              
2627             sub getOpt($) {
2628 0     0 1 0 my ($self, $option) = @_;
2629              
2630             =pod
2631              
2632             =head2 getOpt
2633              
2634             Get option
2635              
2636             Parameters:
2637              
2638             =for html
2639              
2640             =over
2641              
2642             =item $option
2643              
2644             =back
2645              
2646             Option to retrieve. If non-existant then undef is returned.
2647              
2648             =for html
2649              
2650             Returns:
2651              
2652             =for html
2653              
2654             =over
2655              
2656             =item $option or undef if option doesn't exist
2657              
2658             =back
2659              
2660             =for html
2661              
2662             =cut
2663              
2664 0         0 my @validOpts = qw (emptyStringForUndef returnSystemFields);
2665              
2666 0 0       0 if (grep {$option eq $_} @validOpts) {
  0         0  
2667 0         0 return $self->{$option};
2668             } else {
2669 0         0 return;
2670             } # if
2671             } # getOpt
2672              
2673             sub username() {
2674 0     0 1 0 my ($self) = @_;
2675              
2676             =pod
2677              
2678             =head2 username
2679              
2680             Returns the current username (or the username that would be used)
2681              
2682             Parameters:
2683              
2684             =for html
2685              
2686             =over
2687              
2688             =item none
2689              
2690             =back
2691              
2692             =for html
2693              
2694             Returns:
2695              
2696             =for html
2697              
2698             =over
2699              
2700             =item username
2701              
2702             =back
2703              
2704             =for html
2705              
2706             =cut
2707              
2708 0         0 return $self->{username};
2709             } # username
2710              
2711             sub webhost() {
2712 0     0 0 0 my ($self) = @_;
2713              
2714 0         0 return $self->{webhost};
2715             } # webhost
2716              
2717             # Internal GetConfig replacement
2718             sub _GC_interpolate($%) {
2719 12     12   34 my ($str, %opts) = @_;
2720              
2721 12         17 my $copyStr = $str;
2722              
2723 12         66 while ($copyStr =~ /\$(\w+)/) {
2724 0         0 my $var = $1;
2725              
2726 0 0       0 if (exists $opts{$var}) {
    0          
2727 0         0 $str =~ s/\$$var/$opts{$var}/;
2728 0         0 $copyStr =~ s/\$$var/$opts{$var}/;
2729             } elsif (exists $ENV{$var}) {
2730 0         0 $str =~ s/\$$var/$ENV{$var}/;
2731 0         0 $copyStr =~ s/\$$var/$ENV{$var}/;
2732             } else {
2733 0         0 $copyStr =~ s/\$$var//;
2734             } # if
2735             } # while
2736              
2737 12         30 return $str;
2738             } # _GC_interpolate
2739              
2740             sub _GC_processFile($%) {
2741 2     2   6 my ($configFile, %opts) = @_;
2742              
2743 2         53 while (<$configFile>) {
2744 12         23 chomp;
2745              
2746 12 50       35 next if /^\s*[\#|\!]/; # Skip comments
2747              
2748 12 50       56 if (/\s*(.*?)\s*[:=]\s*(.*)\s*/) {
2749 12         22 my $key = $1;
2750 12         23 my $value = $2;
2751              
2752             # Strip trailing spaces
2753 12         21 $value =~ s/\s+$//;
2754              
2755             # Interpolate
2756 12         28 $value = _GC_interpolate $value, %opts;
2757              
2758 12 50       27 if ($opts{$key}) {
2759              
2760             # If the key exists already then we have a case of multiple values for
2761             # the same key. Since we support this we need to replace the scalar
2762             # value with an array of values...
2763 0 0       0 if (ref $opts{$key} eq "ARRAY") {
2764              
2765             # It's already an array, just add to it!
2766 0         0 push @{$opts{$key}}, $value;
  0         0  
2767             } else {
2768              
2769             # It's not an array so make it one
2770 0         0 my @a;
2771              
2772 0         0 push @a, $opts{$key};
2773 0         0 push @a, $value;
2774 0         0 $opts{$key} = \@a;
2775             } # if
2776             } else {
2777              
2778             # It's a simple value
2779 12         49 $opts{$key} = $value;
2780             } # if
2781             } # if
2782             } # while
2783              
2784 2         20 return %opts;
2785             } # _GC_processFile
2786              
2787             sub _GetConfig($) {
2788 2     2   6 my ($filename) = @_;
2789              
2790 2         5 my %opts;
2791              
2792 2 50       85 open my $configFile, '<', $filename
2793             or carp "Unable to open config file $filename";
2794              
2795 2         6 %opts = _GC_processFile $configFile;
2796              
2797 2         22 close $configFile;
2798              
2799 2         20 return %opts;
2800             } # _GetConfig
2801              
2802             1;
2803              
2804             =pod
2805              
2806             =head1 DEPENDENCIES
2807              
2808             =head2 Perl Modules
2809              
2810             L
2811              
2812             =head1 BUGS AND LIMITATIONS
2813              
2814             There are no known bugs in this module
2815              
2816             Multithreading causes the database to be reopened for each thread. This can
2817             cause performance issues if the database is large or if the database is
2818             accessed by multiple threads.
2819              
2820             Please report problems to Andrew DeFaria .
2821              
2822             =head1 LICENSE AND COPYRIGHT
2823              
2824             Copyright (C) 2007-2026 Andrew DeFaria
2825              
2826             This program is free software; you can redistribute it and/or modify it
2827             under the terms of the the Artistic License (2.0). You may obtain a
2828             copy of the full license at:
2829              
2830             L
2831              
2832             Any use, modification, and distribution of the Standard or Modified
2833             Versions is governed by this Artistic License. By using, modifying or
2834             distributing the Package, you accept this license. Do not use, modify,
2835             or distribute the Package, if you do not accept this license.
2836              
2837             If your Modified Version has been derived from a Modified Version made
2838             by someone else, you are strictly prohibited from removing any
2839             copyright notice from that Modified Version.
2840              
2841             Copyright Holder makes no, and expressly disclaims any, representation
2842             or warranty, should the Package be used for any purpose. The liability
2843             of the Copyright Holder is limited to the maximum extent permitted by
2844             law.
2845              
2846             =cut