package LISM::Storage::CSV;

use strict;
use base qw(LISM::Storage);
use Net::LDAP::Constant qw(:all);
use Net::LDAP::Filter;
use Encode;
use Sys::Syslog;
use Sys::Syslog qw(:macros);
use Data::Dumper;

=head1 NAME

LISM::Storage::CSV - CSV storage for LISM

=head1 DESCRIPTION

This class implements the L<LISM::Storage> interface for CSV data.

=head1 METHODS

=head2 init

Initialize the configuration data.

=cut

sub init
{
    my $self = shift;

    return $self->SUPER::init();
}

=pod

=head2 commit

Remove all temporary files updated.

=cut

sub commit
{
    my $self = shift;
    my $conf = $self->{_config};

    foreach my $oname (keys %{$conf->{object}}) {
        if (!defined($conf->{object}{$oname}->{file})) {
            next;
        }

        my $file = $conf->{object}{$oname}->{file}[0];
        if (!open(LOCK, "> $file.lock")) {
            $self->log(level => 'alert', message => "Can't open $file.lock");
            return -1;
        }
        flock(LOCK, 2);
        if (-f "$file.tmp") {
            unlink("$file.tmp");
        }
        close(LOCK);
    }

    return 0;
}

=pod

=head2 rollback

Rename all temporary files to each data files.

=cut

sub rollback
{
    my $self = shift;
    my $conf = $self->{_config};

    foreach my $oname (keys %{$conf->{object}}) {
        my $file = $conf->{object}{$oname}->{file}[0];
        if (!open(LOCK, "> $file.lock")) {
            $self->log(level => 'alert', message => "Can't open $file.lock");
            return -1;
        }
        flock(LOCK, 2);
        if (-f "$file.tmp") {
            rename("$file.tmp", $file);
        }
        close(LOCK);
    }

    return 0;
}


sub _checkConfig
{
    my $self = shift;
    my $conf = $self->{_config};
    my $rc = 0;

    if ($rc = $self->SUPER::_checkConfig()) {
        return $rc;
    }

    if (!defined($conf->{delim})) {
        $conf->{delim}[0] = ',';
    }
    if (!defined($conf->{valdelim})) {
        $conf->{valdelim}[0] = ';';
    }

    return $rc;
}

=pod

=head2 _objSearch($obj, $pkey, $suffix, $sizeLim, $timeLim, $filter)

Search the appropriate records in the object's file.

=cut

sub _objSearch
{
    my $self = shift;
    my ($obj, $pkey, $suffix, $sizeLim, $filter) = @_;
    my $conf = $self->{_config};
    my $oconf = $obj->{conf};
    my $dlm = $conf->{delim}[0];
    my $valdlm = $conf->{valdelim}[0];
    my @match_entries = ();
    my @match_keys = ();
    my $rc = LDAP_SUCCESS;

    DO: {
        my $lock;
        my $file;
        if (!open($lock, "> $oconf->{file}[0].lock")) {
            $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].lock");
            $rc = LDAP_OPERATIONS_ERROR;
            last DO;
        }
        flock($lock, 1);

        if (!open($file, "< $oconf->{file}[0]")) {
            $self->log(level => 'alert', message => "Can't open $oconf->{file}[0]");
            close($lock);
            $rc = LDAP_OPERATIONS_ERROR;
            last DO;
        }

        while (<$file>) {
            chop;
            my @data = split(/$dlm/);
            my $entry;

            # check the number of returned entries
            if ($sizeLim >= 0 && @match_entries == $sizeLim) {
                $rc = LDAP_SIZELIMIT_EXCEEDED;
                last;
            }

            # entries below suffix
            if ($pkey && defined($oconf->{container}) && !defined($oconf->{container}[0]->{rdn})) {
                my $cur_pkey = $data[$oconf->{container}[0]->{idcolumn}[0]];

                if (!($pkey =~ /^$cur_pkey$/i)) {
                    next;
                }
            }

            # get all values of the entry
            foreach my $oc (@{$oconf->{oc}}) {
                $entry = $entry."objectclass: $oc\n";
            }
            foreach my $attr (keys %{$oconf->{attr}}) {
                if (defined($oconf->{attr}{$attr}->{column})) {
                    foreach my $val (split(/$valdlm/, $data[$oconf->{attr}{$attr}->{column}[0]])) {
                        $val =~ s/ *$//;
                        $entry = $entry."$attr: $val\n";
                    }
                } else {
                    my $values = $self->_getAttrValues($oconf, $attr, split(/$valdlm/, $data[$oconf->{attr}{$attr}->{idcolumn}[0]]));
                    if (!defined($values)) {
                        $rc = LDAP_OPERATIONS_ERROR;
                        last;
                    }

                    if ($values) {
                        $entry = $entry.$values;
                    }
                }
            }

            $entry = decode($conf->{mbcode}[0], $entry);

            my ($rdn_val) = ($entry =~ /^$oconf->{rdn}[0]: (.*)$/mi);
            my $rdn = "$oconf->{rdn}[0]=$rdn_val";
            $entry = "dn: $rdn,$suffix\n$entry";

            # parse filter
            if ($self->parseFilter($filter, encode('utf8', $entry))) {
                push(@match_entries, $self->_pwdFormat($entry));
                push(@match_keys, $data[$oconf->{id}[0]->{column}[0]]);
            }
        }
        close($file);
        close($lock);
    }

    return ($rc , \@match_keys, @match_entries);
}

=pod

=head2 _objModify($obj, $pkey, $dn, @list)

Write the modified record to the temporary file.

=cut

sub _objModify
{
    my $self = shift;
    my ($obj, $pkey, $dn, @list) = @_;
    my $conf = $self->{_config};
    my $oconf = $obj->{conf};
    my $dlm = $conf->{delim}[0];
    my $valdlm = $conf->{valdelim}[0];
    my $match = 0;
    my $rc = LDAP_SUCCESS;

    my ($rdn_val) = ($dn =~ /^[^=]+=([^,]+),/);
    $rdn_val =~ s/([.*+?\[\]()|\^\$\\])/\\$1/g;

    # multibyte string
    $rdn_val = encode($conf->{mbcode}[0], $rdn_val);

    my $lock;
    my $file;
    my $tmp;
    if (!open($lock, "> $oconf->{file}[0].lock")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].lock");
        return LDAP_OPERATIONS_ERROR;
    }
    flock($lock, 2);

    if (!rename($oconf->{file}[0], "$oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't rename $oconf->{file}[0] to $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($tmp, "< $oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($file, "> $oconf->{file}[0]")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0]");
        close($tmp);
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    DO: {
        while (<$tmp>) {
            chop;
            my @data = split(/$dlm/);

            # check the data corresponds to the dn
            if (!("$valdlm$data[$oconf->{attr}{$oconf->{rdn}[0]}->{column}[0]]$valdlm" =~ /$valdlm$rdn_val$valdlm/i)) {
                print $file $_."\n";
                next;
            }

            # entries below suffix
            if ($pkey && defined($oconf->{container}) && !defined($oconf->{container}[0]->{rdn})) {
                my $cur_pkey = $data[$oconf->{container}[0]->{idcolumn}[0]];

                if (!($pkey =~ /^$cur_pkey$/i)) {
                    print $file $_."\n";
                    next;
                }
            }
            $match = 1;

            while ( @list > 0 && !$rc) {
                my $action = shift @list;
                my $attr    = lc(shift @list);
                my @values;
                my $coln;

                while (@list > 0 && $list[0] ne "ADD" && $list[0] ne "DELETE" && $list[0] ne "REPLACE") {
                    push(@values, shift @list);
                }

                if (!defined($oconf->{attr}{$attr})) {
                    next;
                }

                # can't modify the attribute for rdn
                if ($attr eq $oconf->{rdn}[0]) {
                    if ($action ne "REPLACE" || join($valdlm, @values) ne $data[$oconf->{attr}{$oconf->{rdn}[0]}{column}[0]]) {
                        $rc =  LDAP_CONSTRAINT_VIOLATION;
                        last;
                    }
                }

                # multibyte value
                for (my $i = 0; $i < @values; $i++) {
                    if ($values[$i]) {
                        $values[$i] = encode($conf->{mbcode}[0], decode('utf8', $values[$i]));
                    }
                }

                if (defined($oconf->{attr}{$attr}->{column})) {
                    $coln = $oconf->{attr}{$attr}->{column}[0];
                } else {
                    my @keys;
                    $coln = $oconf->{attr}{$attr}->{idcolumn}[0];

                    # convert the value to object's id
                    ($rc, @keys) = $self->_getAttrKeys($oconf, $attr, @values);
                    if ($rc) {
                        $self->log(level => 'error', message => "Can't get id of $attr values in the file");
                        $rc = LDAP_OTHER;
                        last DO;
                    }
                    @values = @keys;
                }

                if($action eq "ADD") {
                    # check whether the value already exists
                    for (my $i = 0; $i < @values; $i++) {
                        my $value = $values[0];
                        $value =~ s/([.*+?\[\]()|\^\$\\])/\\$1/g;
                        if ("$valdlm$data[$coln]$valdlm" =~ /$valdlm *$value *$valdlm/i) {
                            $rc = LDAP_TYPE_OR_VALUE_EXISTS;
                            last DO;
                        }
                    }
                    if ($data[$coln] !~ /^ *$/) {
                        $data[$coln] = "$data[$coln]$valdlm".join($valdlm, @values);
                    } else {
                        $data[$coln] = join($valdlm, @values);
                    }
                } elsif($action eq "DELETE") {
                    if (@values && $values[0]) {
                        # check whether the value exists
                        for (my $i = 0;  $i < @values; $i++) {
                            my $value = $values[0];
                            $value =~ s/([.*+?\[\]()|\^\$\\])/\\$1/g;
                            if ("$valdlm$data[$coln]$valdlm" =~ /$valdlm *$value *$valdlm/i) {
                                my $str = "$valdlm$data[$coln]$valdlm";
                                $str =~ s/$valdlm$value$valdlm/$valdlm/i;
                                ($data[$coln]) = ($str =~ /^$valdlm(.*)$valdlm$/);
                            } else {
                                $rc = LDAP_NO_SUCH_ATTRIBUTE;
                                last DO;
                            }
                        }
                    } else {
                        $data[$coln] = '';
		    }
                } elsif($action eq "REPLACE") {
                    $data[$coln] = join($valdlm, @values);
                }
            }

            print $file join($dlm, @data)."\n";
        }
    }
    close($file);
    close($tmp);
    close($lock);

    if (!$rc && !$match) {
        $rc =  LDAP_NO_SUCH_OBJECT;
    }

    if ($rc) {
        $self->rollback();
    }

    return $rc;
}

=pod

=head2 _objAdd($obj, $pkey, $dn, $entryStr)

Copy the object's file to the temporary file and add the record to it.

=cut

sub _objAdd
{
    my $self = shift;
    my ($obj, $pkey, $dn,  $entryStr) = @_;
    my $conf = $self->{_config};
    my $oconf = $obj->{conf};
    my $dlm = $conf->{delim}[0];
    my $valdlm = $conf->{valdelim}[0];
    my $rc = LDAP_SUCCESS;

    my ($rdn_val) = ($dn =~ /^[^=]+=([^,]+),/);
    $rdn_val =~ s/([.*+?\[\]()|\^\$\\])/\\$1/g;

    # multibyte string
    $rdn_val = encode($conf->{mbcode}[0], decode('utf8', $rdn_val));
    $entryStr = encode($conf->{mbcode}[0], decode('utf8', $entryStr));

    my $lock;
    my $file;
    my $tmp;
    if (!open($lock, "> $oconf->{file}[0].lock")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].lock");
        return LDAP_OPERATIONS_ERROR;
    }
    flock($lock, 2);

    # check whether the entry already exists
    if (!rename($oconf->{file}[0], "$oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't rename $oconf->{file}[0] to $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($tmp, "< $oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($file, "> $oconf->{file}[0]")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0]");
        close($tmp);
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    while (<$tmp>) {
        print $file $_;

        chop;
        my @data = split(/$dlm/);

        # check the data correspods to the dn
        if ("$valdlm$data[$oconf->{attr}{$oconf->{rdn}[0]}->{column}[0]]$valdlm" !~ /$valdlm *$rdn_val *$valdlm/i) {
            next;
        }

        # entries below suffix
        if ($pkey && defined($oconf->{container}) && !defined($oconf->{container}[0]->{rdn})) {
            my $cur_pkey = $data[$oconf->{container}[0]->{idcolumn}[0]];
            if ($pkey !~ /^$cur_pkey$/i) {
                next;
            }
        }

        $rc = LDAP_ALREADY_EXISTS;
        last;
    }
    close($tmp);

    if (!$rc) {
        my @data;

        DO: {
            foreach my $attr (keys %{$oconf->{attr}}) {
                my $coln;
                my @values = ($entryStr =~ /^$attr:\s(.*)$/gmi);

                if (defined($oconf->{attr}{$attr}->{column})) {
                    $coln = $oconf->{attr}{$attr}->{column}[0];
                } else {
                    my @keys;
                    $coln = $oconf->{attr}{$attr}->{idcolumn}[0];

                    # convert the value to object's id
                    ($rc, @keys) = $self->_getAttrKeys($oconf, $attr, @values);
                    if ($rc) {
                        $self->log(level => 'error', message => "Can't get id of $attr values in the file");
                        $rc = LDAP_OTHER;
                        last DO;
                    }
                    @values = @keys;
                }

                if ($data[$coln] =~ /^ *$/ || @values > split(/$valdlm/, $data[$coln])) {
                    $data[$coln] = join($valdlm, @values);
                }
            }

            # add storage-specific information
            foreach my $strginfo (@{$oconf->{strginfo}}) {
                my $value = $self->_getStaticValue($strginfo, $dn, $entryStr);

                if (defined($strginfo->{column})) {
                    $data[$strginfo->{column}[0]] = $value;
                }
            }

            # add the link with container
            if (defined($oconf->{container}) && defined($oconf->{container}[0]->{idcolumn})) {
                $data[$oconf->{container}[0]->{idcolumn}[0]] = $pkey;
            }

            print $file join($dlm, @data)."\n";
        }
    }
    close($file);
    close($lock);

    if ($rc) {
        $self->rollback();
    }

    return $rc;
}

=pod

=head2 _objDelete($dn)

Copy the object's file from which the appropriate record is deleted to the temporary file.

=cut

sub _objDelete
{
    my $self = shift;
    my ($obj, $pkey, $dn) = @_;
    my $conf = $self->{_config};
    my $oconf = $obj->{conf};
    my $dlm = $conf->{delim}[0];
    my $valdlm = $conf->{valdelim}[0];
    my $rc = LDAP_NO_SUCH_OBJECT;

    my ($rdn_val) = ($dn =~ /^[^=]+=([^,]+),/);
    $rdn_val =~ s/([.*+?\[\]()|\^\$\\])/\\$1/g;

    # multibyte string
    $rdn_val = encode($conf->{mbcode}[0], decode('utf8', $rdn_val));

    my $lock;
    my $file;
    my $tmp;
    if (!open($lock, "> $oconf->{file}[0].lock")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].lock");
        return LDAP_OPERATIONS_ERROR;
    }
    flock($lock, 2);

    if (!rename($oconf->{file}[0], "$oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't rename $oconf->{file}[0] to $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($tmp, "< $oconf->{file}[0].tmp")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].tmp");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    if (!open($file, "> $oconf->{file}[0]")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0]");
        close($tmp);
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }

    while (<$tmp>) {
        chop;
        my @data = split(/$dlm/);

        # check the data corresponds to the dn
        if (!("$valdlm$data[$oconf->{attr}{$oconf->{rdn}[0]}{column}[0]]$valdlm" =~ /$valdlm$rdn_val$valdlm/i)) {
            print $file $_."\n";
            next;
        }

        # entries below suffix
        if ($pkey && defined($oconf->{container}) && !defined($oconf->{container}[0]->{rdn})) {
            my $cur_pkey = $data[$oconf->{container}[0]->{idcolumn}[0]];

            if (!($pkey =~ /^$cur_pkey$/i)) {
                print $file $_."\n";
                next;
            }
        }

        $rc = LDAP_SUCCESS;
    }
    close($file);
    close($tmp);
    close($lock);

    if ($rc) {
        $self->rollback();
    }

    return $rc;
}

sub _getParentRdn
{
    my $self = shift;
    my ($obj, $key) = @_;
    my $conf = $self->{_config};
    my $oconf = $obj->{conf};
    my $dlm = $conf->{delim}[0];
    my $prdn = undef;
    my $pkey = undef;

    if (defined($oconf->{container}[0]->{rdn})) {
        return $oconf->{container}[0]->{rdn}[0];
    }
    if (!defined($oconf->{container}[0]->{oname})) {
        return undef;
    }

    my $lock;
    my $file;
    if (!open($lock, "> $oconf->{file}[0].lock")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0].lock");
        return LDAP_OPERATIONS_ERROR;
    }
    flock($lock, 1);

    if (!open($file, "< $oconf->{file}[0]")) {
        $self->log(level => 'alert', message => "Can't open $oconf->{file}[0]");
        close($lock);
        return LDAP_OPERATIONS_ERROR;
    }
    while (<$file>) {
        chop;
        my @data = split(/$dlm/);

        # check the data corresponds to the object's id
        if ($data[$oconf->{id}[0]->{column}[0]] =~ /^$key$/i) {
            $pkey = $data[$oconf->{container}[0]->{idcolumn}[0]];
            last;
        }
    }

    my $poconf = $self->{object}{$obj->{parent}}->{conf};
    if (!open($lock, "> $poconf->{file}[0].lock")) {
        $self->log(level => 'alert', message => "Can't open $poconf->{file}[0].lock");
        return undef;
    }
    flock($lock, 1);

    if (!open($file, "< $poconf->{file}[0]")) {
        $self->log(level => 'alert', message => "Can't open $poconf->{file}[0]");
        close($lock);
        return undef;
    }

    while (<$file>) {
        chop;
        my @data = split(/$dlm/);

        # check the data corresponds to the object's id
        if ($data[$poconf->{id}[0]->{column}[0]] =~ /^$pkey$/i) {
            my $rdn_val = $data[$poconf->{attr}{$poconf->{rdn}[0]}->{column}[0]];
            $rdn_val =~ s/ *$//;
            $prdn = "$poconf->{rdn}[0]=$rdn_val";
            last;
        }
    }
    close($lock);
    close($file);

    return ($prdn, $pkey);
}

sub _getAttrValues
{
    my $self = shift;
    my ($oconf, $attr, @keys) = @_;
    my $conf = $self->{_config};
    my $dlm = $conf->{delim}[0];
    my $aobj = undef;
    my $aoconf = undef;
    my $attrStr = '';
    my @colnums;
    my $filename;

    if (defined($oconf->{attr}{$attr}->{oname})) {
        $aobj = $self->{object}{$oconf->{attr}{$attr}->{oname}[0]};
        $aoconf = $aobj->{conf};
        $filename = $aoconf->{file}[0];
    } elsif (defined($oconf->{attr}{$attr}->{file})) {
        @colnums = ($oconf->{attr}{$attr}->{value}[0] =~ /%([0-9]+)/g);
        $filename = $oconf->{attr}{$attr}->{file}[0];
    } else {
        return undef;
    }

    my $lock;
    my $file;
    if (!open($lock, "> $filename.lock")) {
        $self->log(level => 'alert', message => "Can't open $filename.lock");
        return undef;
    }
    flock($lock, 1);

    if (!open($file, "< $filename")) {
        $self->log(level => 'alert', message => "Can't open $filename");
        close($lock);
        return undef;
    }

    while (<$file>) {
        chop;
        my @data = split(/$dlm/);

        # check the data corresponds to the object's id
        for (my $i = 0; $i < @keys; $i++) {
            if (defined($aoconf)) {
                if ($data[$aoconf->{id}[0]->{column}[0]] =~ /^$keys[$i]$/i) {
                    my $rdn_val = $data[$aoconf->{attr}{$aoconf->{rdn}[0]}->{column}[0]];
                    $rdn_val =~ s/ *$//;
                    $attrStr = $attrStr."$attr: $aoconf->{rdn}[0]=$rdn_val,".$self->_getParentDn($aobj, $data[$aoconf->{id}[0]->{column}[0]])."\n";
                    splice(@keys, $i, 1);
                    last;
                }
            } else {
                if ($data[$oconf->{attr}{$attr}->{id}[0]->{column}[0]] =~ /^$keys[$i]/i) {
                    my $value = $oconf->{attr}{$attr}->{value}[0];
                    foreach my $coln (@colnums) {
                        $value =~ s/%$coln/$data[$coln]/g;
                    }
                    $value =~ s/ *$//;

                    if ($value) {
                        $attrStr = $attrStr."$attr: $value\n";
                        splice(@keys, $i, 1);
                    }
       	            last;
                }
	    }
        }

        if (!@keys) {
            last;
        }
    }
    close($lock);
    close($file);

    # Values not got exist
    if (@keys) {
        return undef;
    }

    return $attrStr;
}

sub _getAttrKeys
{
    my $self = shift;
    my ($oconf, $attr, @values) = @_;
    my $conf = $self->{_config};
    my $dlm = $conf->{delim}[0];
    my @attrkeys = ();
    my $rc = 0;

    if (defined($oconf->{attr}{$attr}->{oname})) {
        for (my $i = 0; $i < @values && $values[$i]; $i++) {
            my $aobj;
            my $attrkey;
            my $attrpkey;

            ($rc, $aobj, $attrpkey) = $self->_getObject($values[$i]);
            if ($rc) {
                return (-1, ());
            }

            ($rc, $attrkey) =$self->_baseSearch($aobj, $attrpkey, $values[$i], 0, 0, 1, 0, undef, 0, ('dn'));
            if ($rc || !$attrkey) {
                return (-1, ());
            }

            push(@attrkeys, $attrkey);
        }
    } elsif (defined($oconf->{attr}{$attr}->{file})) {
        my $lock;
        my $file;

        if (!open($lock, "> $oconf->{attr}{$attr}->{file}[0].lock")) {
            $self->log(level => 'alert', message => "Can't open $oconf->{attr}{$attr}->{file}[0].lock");
            return (-1, ());
        }
        flock($lock, 1);

        if (!open($file, "< $oconf->{attr}{$attr}->{file}[0]")) {
            $self->log(level => 'alert', message => "Can't open $oconf->{attr}{$attr}->{file}[0]");
            close($lock);
            return (-1, ());
        }

        my @colnums = ($oconf->{attr}{$attr}->{value}[0] =~ /%([0-9]+)/g);
        (my $replace = $oconf->{attr}{$attr}->{value}[0]) =~ s/([*+\/\.^$()\[\]])/\\$1/g;
        $replace =~ s/%[0-9]+/(.+)/ig;

        my @avals;
        for (my $i = 0; $i < @values && $values[$i]; $i++) {
            $avals[$i] = join(';', ($values[$i] =~ /^$replace$/));
        }

        while (<$file>) {
            if (!@avals) {
                last;
            }

            chop;
            my @data = split(/$dlm/);
            my $dvals;
            foreach my $coln (@colnums) {
                $data[$coln] =~ s/ *$//;
                if ($dvals) {
                    $dvals = "$dvals;$data[$coln]";
                } else {
                    $dvals = $data[$coln];
                }
            }

            # check the data corresponds to the object's id
            for (my $i = 0; $i < @avals; $i++) {
                if ($dvals =~ /^$avals[$i]$/i) {
                    push(@attrkeys, $data[$oconf->{attr}{$attr}->{id}[0]->{column}[0]]);
                    splice(@avals, $i, 1);
                    last;
		}
	    }
        }
        close($lock);
        close($file);

        # Values not added exist
        if (@avals) {
            return (-1, ());
        }
    } else {
        return (-1, ());
    }

    return ($rc, @attrkeys);
}

=head1 SEE ALSO

L<LISM>,
L<LISM::Storage>

=head1 AUTHOR

Kaoru Sekiguchi, <sekiguchi.kaoru@secioss.co.jp>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006 by Kaoru Sekiguchi

This library is free software; you can redistribute it and/or modify
it under the GNU LGPL.

=cut

1;
