9 |
use Tracer; |
use Tracer; |
10 |
use ERDBLoad; |
use ERDBLoad; |
11 |
use Stats; |
use Stats; |
12 |
|
use Time::HiRes qw(time); |
13 |
|
|
14 |
=head1 Custom SEED Attribute Manager |
=head1 Custom SEED Attribute Manager |
15 |
|
|
345 |
-labels => \%labelMap, |
-labels => \%labelMap, |
346 |
-default => 'string'); |
-default => 'string'); |
347 |
# Allow the user to specify a new field name. This is required if the |
# Allow the user to specify a new field name. This is required if the |
348 |
# user has selected the "(new)" marker. We put a little scriptlet in here that |
# user has selected the "(new)" marker. |
|
# selects the (new) marker when the user enters the field. |
|
|
push @retVal, "<script language=\"javaScript\">"; |
|
349 |
my $fieldField = "document.$name.fieldName"; |
my $fieldField = "document.$name.fieldName"; |
350 |
my $newName = "\"" . NewName() . "\""; |
my $newName = "\"" . NewName() . "\""; |
351 |
push @retVal, $cgi->Tr($cgi->th("New Field Name"), |
push @retVal, $cgi->Tr($cgi->th("New Field Name"), |
386 |
=head3 LoadAttributesFrom |
=head3 LoadAttributesFrom |
387 |
|
|
388 |
C<< my $stats = $attrDB->LoadAttributesFrom($fileName, %options); >> |
C<< my $stats = $attrDB->LoadAttributesFrom($fileName, %options); >> |
389 |
|
s |
390 |
Load attributes from the specified tab-delimited file. Each line of the file must |
Load attributes from the specified tab-delimited file. Each line of the file must |
391 |
contain an object ID in the first column, an attribute key name in the second |
contain an object ID in the first column, an attribute key name in the second |
392 |
column, and attribute values in the remaining columns. The attribute values will |
column, and attribute values in the remaining columns. The attribute values will |
429 |
|
|
430 |
If specified, the specified object type will be prefixed to each object ID. |
If specified, the specified object type will be prefixed to each object ID. |
431 |
|
|
432 |
|
=item resume |
433 |
|
|
434 |
|
If specified, key-value pairs already in the database will not be reinserted. |
435 |
|
|
436 |
=back |
=back |
437 |
|
|
438 |
=cut |
=cut |
442 |
my ($self, $fileName, %options) = @_; |
my ($self, $fileName, %options) = @_; |
443 |
# Declare the return variable. |
# Declare the return variable. |
444 |
my $retVal = Stats->new('keys', 'values'); |
my $retVal = Stats->new('keys', 'values'); |
445 |
|
# Initialize the timers. |
446 |
|
my ($insertTime, $eraseTime, $archiveTime, $checkTime) = (0, 0, 0, 0); |
447 |
# Check for append mode. |
# Check for append mode. |
448 |
my $append = ($options{append} ? 1 : 0); |
my $append = ($options{append} ? 1 : 0); |
449 |
|
# Check for resume mode. |
450 |
|
my $resume = ($options{resume} ? 1 : 0); |
451 |
# Create a hash of key names found. |
# Create a hash of key names found. |
452 |
my %keyHash = (); |
my %keyHash = (); |
453 |
# Open the file for input. Note we must anticipate the possibility of an |
# Open the file for input. Note we must anticipate the possibility of an |
466 |
$ah = Open(undef, ">$options{archive}"); |
$ah = Open(undef, ">$options{archive}"); |
467 |
Trace("Load file will be archived to $options{archive}.") if T(3); |
Trace("Load file will be archived to $options{archive}.") if T(3); |
468 |
} |
} |
469 |
# Finally, open a database transaction. |
# Insure we recover from errors. |
|
$self->BeginTran(); |
|
|
# Insure we recover from errors. If an error occurs, we will delete the archive file and |
|
|
# roll back the updates. |
|
470 |
eval { |
eval { |
471 |
# Loop through the file. |
# Loop through the file. |
472 |
while (! eof $fh) { |
while (! eof $fh) { |
479 |
} |
} |
480 |
# Archive the line (if necessary). |
# Archive the line (if necessary). |
481 |
if (defined $ah) { |
if (defined $ah) { |
482 |
|
my $startTime = time(); |
483 |
Tracer::PutLine($ah, [$id, $key, @values]); |
Tracer::PutLine($ah, [$id, $key, @values]); |
484 |
|
$archiveTime += time() - $startTime; |
485 |
} |
} |
486 |
# Do some validation. |
# Do some validation. |
487 |
if (! $id) { |
if (! $id) { |
494 |
# An ID without a key is a serious error. |
# An ID without a key is a serious error. |
495 |
my $lines = $retVal->Ask('linesIn'); |
my $lines = $retVal->Ask('linesIn'); |
496 |
Confess("Line $lines in $fileName has no attribute key."); |
Confess("Line $lines in $fileName has no attribute key."); |
497 |
|
} elsif (! @values) { |
498 |
|
# A line with no values is not allowed. |
499 |
|
my $lines = $retVal->Ask('linesIn'); |
500 |
|
Trace("Line $lines for key $key has no attribute values.") if T(1); |
501 |
|
$retVal->Add(skipped => 1); |
502 |
} else { |
} else { |
503 |
# The key contains a real part and an optional sub-part. We need the real part. |
# The key contains a real part and an optional sub-part. We need the real part. |
504 |
my ($realKey, $subKey) = $self->SplitKey($key); |
my ($realKey, $subKey) = $self->SplitKey($key); |
513 |
$retVal->Add(keys => 1); |
$retVal->Add(keys => 1); |
514 |
# If this is NOT append mode, erase the key. |
# If this is NOT append mode, erase the key. |
515 |
if (! $append) { |
if (! $append) { |
516 |
|
my $startTime = time(); |
517 |
$self->EraseAttribute($realKey); |
$self->EraseAttribute($realKey); |
518 |
|
$eraseTime += time() - $startTime; |
519 |
|
Trace("Attribute $realKey erased.") if T(3); |
520 |
} |
} |
521 |
} |
} |
522 |
Trace("Key $realKey found.") if T(3); |
Trace("Key $realKey found.") if T(3); |
523 |
} |
} |
524 |
|
# If we're in resume mode, check to see if this insert is redundant. |
525 |
|
my $ok = 1; |
526 |
|
if ($resume) { |
527 |
|
my $startTime = time(); |
528 |
|
my $count = $self->GetAttributes($id, $key, @values); |
529 |
|
$ok = ! $count; |
530 |
|
$checkTime += time() - $startTime; |
531 |
|
} |
532 |
|
if ($ok) { |
533 |
# Everything is all set up, so add the value. |
# Everything is all set up, so add the value. |
534 |
|
my $startTime = time(); |
535 |
$self->AddAttribute($id, $key, @values); |
$self->AddAttribute($id, $key, @values); |
536 |
|
$insertTime += time() - $startTime; |
537 |
|
} else { |
538 |
|
# Here we skipped because of resume mode. |
539 |
|
$retVal->Add(resumeSkip => 1); |
540 |
|
} |
541 |
|
|
542 |
my $progress = $retVal->Add(values => 1); |
my $progress = $retVal->Add(values => 1); |
543 |
Trace("$progress values loaded.") if T(3) && ($progress % 1000 == 0); |
Trace("$progress values loaded.") if T(3) && ($progress % 1000 == 0); |
544 |
} |
} |
545 |
} |
} |
546 |
|
$retVal->Add(eraseTime => $eraseTime); |
547 |
|
$retVal->Add(insertTime => $insertTime); |
548 |
|
$retVal->Add(archiveTime => $archiveTime); |
549 |
|
$retVal->Add(checkTime => $checkTime); |
550 |
}; |
}; |
551 |
# Check for an error. |
# Check for an error. |
552 |
if ($@) { |
if ($@) { |
553 |
# Here we have an error. Roll back the transaction and delete the archive file. |
# Here we have an error. Display the error message. |
554 |
my $message = $@; |
my $message = $@; |
555 |
Trace("Rolling back attribute updates due to error.") if T(1); |
Trace("Error during attribute load: $message") if T(0); |
556 |
$self->RollbackTran(); |
$retVal->AddMessage($message); |
|
if (defined $ah) { |
|
|
Trace("Deleting archive file $options{archive}.") if T(1); |
|
|
close $ah; |
|
|
unlink $options{archive}; |
|
557 |
} |
} |
558 |
Confess("Error during attribute load: $message"); |
# Close the archive file, if any. |
|
} else { |
|
|
# Here the load worked. Commit the transaction and close the archive file. |
|
|
Trace("Committing attribute upload.") if T(2); |
|
|
$self->CommitTran(); |
|
559 |
if (defined $ah) { |
if (defined $ah) { |
560 |
Trace("Closing archive file $options{archive}.") if T(2); |
Trace("Closing archive file $options{archive}.") if T(2); |
561 |
close $ah; |
close $ah; |
562 |
} |
} |
|
} |
|
563 |
# Return the result. |
# Return the result. |
564 |
return $retVal; |
return $retVal; |
565 |
} |
} |
1747 |
return sort @groups; |
return sort @groups; |
1748 |
} |
} |
1749 |
|
|
1750 |
|
=head3 QueryAttributes |
1751 |
|
|
1752 |
|
C<< my @attributeData = $ca->QueryAttributes($filter, $filterParms); >> |
1753 |
|
|
1754 |
|
Return the attribute data based on an SQL filter clause. In the filter clause, |
1755 |
|
the name C<$object> should be used for the object ID, C<$key> should be used for |
1756 |
|
the key name, C<$subkey> for the subkey value, and C<$value> for the value field. |
1757 |
|
|
1758 |
|
=over 4 |
1759 |
|
|
1760 |
|
=item filter |
1761 |
|
|
1762 |
|
Filter clause in the standard ERDB format, except that the field names are C<$object> for |
1763 |
|
the object ID field, C<$key> for the key name field, C<$subkey> for the subkey field, |
1764 |
|
and C<$value> for the value field. This abstraction enables us to hide the details of |
1765 |
|
the database construction from the user. |
1766 |
|
|
1767 |
|
=item filterParms |
1768 |
|
|
1769 |
|
Parameters for the filter clause. |
1770 |
|
|
1771 |
|
=item RETURN |
1772 |
|
|
1773 |
|
Returns a list of tuples. Each tuple consists of an object ID, a key (with optional subkey), and |
1774 |
|
one or more attribute values. |
1775 |
|
|
1776 |
|
=back |
1777 |
|
|
1778 |
|
=cut |
1779 |
|
|
1780 |
|
# This hash is used to drive the substitution process. |
1781 |
|
my %AttributeParms = (object => 'HasValueFor(to-link)', |
1782 |
|
key => 'HasValueFor(from-link)', |
1783 |
|
subkey => 'HasValueFor(subkey)', |
1784 |
|
value => 'HasValueFor(value)'); |
1785 |
|
|
1786 |
|
sub QueryAttributes { |
1787 |
|
# Get the parameters. |
1788 |
|
my ($self, $filter, $filterParms) = @_; |
1789 |
|
# Declare the return variable. |
1790 |
|
my @retVal = (); |
1791 |
|
# Make sue we have filter parameters. |
1792 |
|
my $realParms = (defined($filterParms) ? $filterParms : []); |
1793 |
|
# Create the query by converting the filter. |
1794 |
|
my $realFilter = $filter; |
1795 |
|
for my $name (keys %AttributeParms) { |
1796 |
|
$realFilter =~ s/\$$name/$AttributeParms{$name}/g; |
1797 |
|
} |
1798 |
|
my $query = $self->Get(['HasValueFor'], $realFilter, $realParms); |
1799 |
|
# Loop through the results, forming the output attribute tuples. |
1800 |
|
while (my $result = $query->Fetch()) { |
1801 |
|
# Get the four values from this query result row. |
1802 |
|
my ($objectID, $key, $subkey, $value) = $result->Values([$AttributeParms{object}, |
1803 |
|
$AttributeParms{key}, |
1804 |
|
$AttributeParms{subkey}, |
1805 |
|
$AttributeParms{value}]); |
1806 |
|
# Combine the key and the subkey. |
1807 |
|
my $realKey = ($subkey ? $key . $self->{splitter} . $subkey : $key); |
1808 |
|
# Split the value. |
1809 |
|
my @values = split $self->{splitter}, $value; |
1810 |
|
# Output the result. |
1811 |
|
push @retVal, [$objectID, $realKey, @values]; |
1812 |
|
} |
1813 |
|
# Return the result. |
1814 |
|
return @retVal; |
1815 |
|
} |
1816 |
|
|
1817 |
=head2 Key and ID Manipulation Methods |
=head2 Key and ID Manipulation Methods |
1818 |
|
|
1819 |
=head3 ParseID |
=head3 ParseID |
1857 |
if ($idValue =~ /^([A-Za-z]+):(.+)/) { |
if ($idValue =~ /^([A-Za-z]+):(.+)/) { |
1858 |
# Here we have a typed ID. |
# Here we have a typed ID. |
1859 |
($type, $id) = ($1, $2); |
($type, $id) = ($1, $2); |
1860 |
|
# Fix the case sensitivity on PDB IDs. |
1861 |
|
if ($type eq 'PDB') { $id = lc $id; } |
1862 |
} elsif ($idValue =~ /fig\|/) { |
} elsif ($idValue =~ /fig\|/) { |
1863 |
# Here we have a feature ID. |
# Here we have a feature ID. |
1864 |
($type, $id) = (Feature => $idValue); |
($type, $id) = (Feature => $idValue); |
1934 |
|
|
1935 |
=item RETURN |
=item RETURN |
1936 |
|
|
1937 |
Returns a B<DBObject> for the attribute value's target object. |
Returns a B<ERDBObject> for the attribute value's target object. |
1938 |
|
|
1939 |
=back |
=back |
1940 |
|
|
2033 |
return $retVal; |
return $retVal; |
2034 |
} |
} |
2035 |
|
|
2036 |
|
|
2037 |
|
=head3 AttributeTable |
2038 |
|
|
2039 |
|
C<< my $tableHtml = CustomAttributes::AttributeTable($cgi, @attrList); >> |
2040 |
|
|
2041 |
|
Format the attribute data into an HTML table. |
2042 |
|
|
2043 |
|
=over 4 |
2044 |
|
|
2045 |
|
=item cgi |
2046 |
|
|
2047 |
|
CGI query object used to generate the HTML |
2048 |
|
|
2049 |
|
=item attrList |
2050 |
|
|
2051 |
|
List of attribute results, in the format returned by the L</GetAttributes> or |
2052 |
|
L</QueryAttributes> methods. |
2053 |
|
|
2054 |
|
=item RETURN |
2055 |
|
|
2056 |
|
Returns an HTML table displaying the attribute keys and values. |
2057 |
|
|
2058 |
|
=back |
2059 |
|
|
2060 |
|
=cut |
2061 |
|
|
2062 |
|
sub AttributeTable { |
2063 |
|
# Get the parameters. |
2064 |
|
my ($cgi, @attrList) = @_; |
2065 |
|
# Accumulate the table rows. |
2066 |
|
my @html = (); |
2067 |
|
for my $attrData (@attrList) { |
2068 |
|
# Format the object ID and key. |
2069 |
|
my @columns = map { CGI::escapeHTML($_) } @{$attrData}[0,1]; |
2070 |
|
# Now we format the values. These remain unchanged unless one of them is a URL. |
2071 |
|
my $lastValue = scalar(@{$attrData}) - 1; |
2072 |
|
push @columns, map { $_ =~ /^http:/ ? $cgi->a({ href => $_ }, $_) : $_ } @{$attrData}[2 .. $lastValue]; |
2073 |
|
# Assemble the values into a table row. |
2074 |
|
push @html, $cgi->Tr($cgi->td(\@columns)); |
2075 |
|
} |
2076 |
|
# Format the table in the return variable. |
2077 |
|
my $retVal = $cgi->table({ border => 2 }, $cgi->Tr($cgi->th(['Object', 'Key', 'Values'])), @html); |
2078 |
|
# Return it. |
2079 |
|
return $retVal; |
2080 |
|
} |
2081 |
1; |
1; |