M3::ServerView::View - Base class for views


M3-ServerView documentation Contained in the M3-ServerView distribution.

Index


Code Index:

NAME

Top

M3::ServerView::View - Base class for views

DESCRIPTION

Top

This class serves as a base class for 'views'. A view is a page in the web-interface which contains a set of data we want to be able to access and search.

INTERFACE

Top

CLASS METHODS

new ( CONNECTION, URL )

Creates a new view which maps to given URL.

INSTANCE METHODS

connection

Returns the M3::ServerView-instance that was used to retrieve the view with.

reload

Reloads the data for the view from the server.

response_time

Returns the number of seconds it took to get the reply from the server.

request_time

The date and time in ISO 8601 format when the request was made.

search ( [QUERY [, OPTIONS ]] )

Performs a search of the data and returns a M3::ServerView::ResultSet-instance.

QUERY FORMAT

If QUERY is specified it must be a hash reference. Each key is a name of an attribute in the entry which must exist or the match will fail. The value should either be a scalar which will be compared with the entries value for the attribute using either == or eq depending on whether it's numerical or not. More complex comparisions can be declared by passing an array reference as value where the first element is an comparison operator and the second element the value to compare with. The following comparisions can be defined

== != < > <= >= - Numerical equals, not equals, less than, greater than, less or equal than, greater or equal than

eq ne lt gt le ge - Same as above but string equivalents

like - Regular expression comparision

Examples

  # Find all entries who's status is down
  $rs = $view->search({ status => 'down' });

  # Find all entries with more than 20 threads and order by threads descending
  $rs = $view->search({ 
    threads => [ '>' => 20] 
  }, { 
    order_by => 'threads', 
    sort_order => 'desc',
  });

  # Find all entries whose type begins with "Sub:A"
  $rs = $view->search({ type => [ 'like' => '^Sub:A' ]});

OPTIONS

Specifiying options the the search is possible by passing a hash reference. The following options valid:

case_sensitive = 1 | 0

Sets if comparision should be case_insensitive or not. Defaults to true.

order_by = attribute

Sets the name of the attribute to sort by.

sort_order = "asc" | "desc"

Sets the order of the search. Defaults to "asc".

sort_as = "text" | "numerical"

Sets the if the sort should be considered numerical or not. In numerical mode (1, 55, 7) sortes as (1, 7, 55) while in text mode it sorts as (1, 55, 7).


M3-ServerView documentation Contained in the M3-ServerView distribution.

package M3::ServerView::View;

use strict;
use warnings;

use Carp qw(croak);
use POSIX qw(strftime);
use Scalar::Util qw(refaddr looks_like_number);

use M3::ServerView::Parser;
use M3::ServerView::ResultSet;

my %Entries;
my %Connection;
my %Response_time;
my %Request_time;
my %Url;

# Constructor
sub new {
    my ($pkg, $connection, $url) = @_;

    my $self = bless \do { my $v; }, $pkg;

    $Url{refaddr $self} = $url;
    $Connection{refaddr $self} = $connection if $connection;

    $self->reload();
    
    return $self;
}

# Deconstructor
sub DESTROY {
    my ($self) = @_;
    delete $Connection{refaddr $self};
    delete $Entries{refaddr $self};
    delete $Response_time{refaddr $self};
    delete $Request_time{refaddr $self};
    delete $Url{refaddr $self};
}

sub connection {
    my ($self) = @_;
    return $Connection{refaddr $self};
}

# Page loading
sub reload {
    my ($self) = @_;
    $self->_load();
}

sub _load {
    my ($self) = @_;

    # Clear entries
    $Entries{refaddr $self} = [];

    return unless $Url{refaddr $self};
    
    # Fetch page
    $Request_time{refaddr $self} = CORE::time;
    my ($content, $response_time) = $self->connection->_get_page_contents($Url{refaddr $self});
    $Response_time{refaddr $self} = sprintf("%.6f", $response_time);
    
    # Parser contents
    $self->_parse($content);
    
    1;
}

# Generic fallback parser that uses M3::ServerView::Parser;
sub _entry_class {
    my ($self) = @_;
    croak "View class '", ref $self, "' doesn't override _entry_class()";
}

sub _entry_columns {
    my ($self) = @_;
    croak "View class '", ref $self, "' doesn't override _entry_columns()";
}

sub _parse {
    my ($self, $content) = @_;
    my $parser = M3::ServerView::Parser->new($self);
    $parser->parse($content);
}


sub _entries {
    my ($self) = @_;
    return $Entries{refaddr $self};
}

sub _add_entry {
    my ($self, $entry) = @_;
    push @{$Entries{refaddr $self}}, $entry;
}

sub response_time {
    my ($self) = @_;
    return $Response_time{refaddr $self};
}

sub request_time {
    my ($self, $format) = @_;
    my $time = $Request_time{refaddr $self};

    if ($format && $format eq "timestamp") {
        return $time;
    }
    
    return strftime("%Y-%m-%d %H:%M:%S", localtime($time));
}

sub search {
    my ($self, $query, $options) = @_;
    
    $options = {} unless ref $options eq "HASH";
    
    my @matches;
    
    # Build rules
    my $case_sensitive = $options->{case_sensitive};
    $case_sensitive = 0 unless defined $case_sensitive;
    my @matchers = _build_matchers($query, $case_sensitive);
    
    CHECK_ENTRIES: for my $entry (@{$self->_entries}) {
        # Check if entry matches all matchers and 
        # break at the first that doens't
        !($_->($entry)) && next CHECK_ENTRIES for @matchers;
        
        push @matches, $entry;
    }
    
    # Sort results
    if (exists $options->{order_by}) {
        no warnings;
        my $key = $options->{order_by};
        my $sort_as = $options->{sort_as} || "";
        my $sort_order = lc($options->{sort_order}) || "asc";
        croak q{Sort order must be either 'asc' or 'desc'} unless $sort_order =~ /^asc|desc$/;
        if ($sort_order eq "asc") {
            if ($sort_as eq "text") {
                @matches = sort { $a->{$key} cmp $b->{$key} } @matches;
            }
            else {
                @matches = sort { $a->{$key} <=> $b->{$key} } @matches;                
            }
        }
        else {
            if ($sort_as eq "text") {
                @matches = sort { $b->{$key} cmp $a->{$key} } @matches;
            }
            else {
                @matches = sort { $b->{$key} <=> $a->{$key} } @matches;                
            }
        }
    }
    
    my $rs = M3::ServerView::ResultSet->new(\@matches);
    return $rs;
}

my %Op = (
    "=="    => sub { $_[0] == $_[1]; },
    "!="    => sub { $_[0] != $_[1]; },
    "<"     => sub { $_[0] < $_[1]; },
    ">"     => sub { $_[0] > $_[1]; },
    "<="    => sub { $_[0] <= $_[1]; },
    ">="    => sub { $_[0] >= $_[1]; },

    "eq"    => sub { $_[0] eq $_[1]; },
    "ne"    => sub { $_[0] ne $_[1]; },
    "lt"    => sub { $_[0] lt $_[1]; },
    "gt"    => sub { $_[0] gt $_[1]; },
    "le"    => sub { $_[0] le $_[1]; },
    "ge"    => sub { $_[0] ge $_[1]; },
    
    "like"  => sub { $_[0] =~ m{ $_[1] }xi; },
);

my %Op_transform = (
    "=="    => "eq",
    "!="    => "ne",
    ">"     => "gt",
    "<"     => "lt",
    ">="    => "ge",
    "<="    => "le",
);

sub _build_matchers {
    my ($rules, $case_sensitive) = @_;

    $rules = {} unless $rules;
    
    my @matchers;

    while (my ($key, $rule) = each %$rules) {
        my ($op, $value) = ("==", $rule);
        
        if (ref $rule eq "ARRAY") {
            ($op, $value) = @$rule;
        }

        croak "Don't know how to handle op '${op}'" if !exists $Op{$op};
        
        if (!looks_like_number($value) && exists $Op_transform{$op}) {
            $op = $Op_transform{$op};
        }

        my $matcher;
        
        # Basicly the same subroutine except we lowercase the contents if we
        # perform a case-insensitive match
        if ($case_sensitive) {
            $matcher = sub {
                my $entry = shift;
                return 0 unless exists $entry->{$key};
                return 0 unless defined $entry->{$key};
                return 0 unless $Op{$op}->($entry->{$key}, $value);
                return 1;
            };
        }
        else {
            $matcher = sub {
                my $entry = shift;
                return 0 unless exists $entry->{$key};
                return 0 unless defined $entry->{$key};
                return 0 unless $Op{$op}->(lc($entry->{$key}), lc($value));
                return 1;
            };            
        }
        
        push @matchers, $matcher;
    }
    
    return @matchers;
}

1;
__END__