From c4bcc18856b53e55d52e56cc6575c1a98fa74c5b Mon Sep 17 00:00:00 2001 From: Jason Stephenson Date: Sun, 13 Jul 2014 19:57:53 -0400 Subject: [PATCH] Add setup and initialization code to the Evergreen driver. It can parse a XML configuration file. It can use that configuration to bootstrap itself and login to OpenSRF via Evergreen. It can initialize its state based on that configuration. Signed-off-by: Jason Stephenson --- lib/NCIP/ILS/Evergreen.pm | 309 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 308 insertions(+), 1 deletion(-) diff --git a/lib/NCIP/ILS/Evergreen.pm b/lib/NCIP/ILS/Evergreen.pm index 4948a20..41ac7fc 100644 --- a/lib/NCIP/ILS/Evergreen.pm +++ b/lib/NCIP/ILS/Evergreen.pm @@ -14,9 +14,62 @@ package NCIP::ILS::Evergreen; use Modern::Perl; - use Object::Tiny qw/name/; +use XML::XPath; +use OpenSRF::System; +use OpenSRF::Utils::SettingsClient; +use Digest::MD5 qw/md5_hex/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Application::AppUtils; +use OpenILS::Const qw/:const/; +use MARC::Record; +use MARC::Field; +use MARC::File::XML; + +# Default values we define for things that might be missing in our +# runtime environment or configuration file that absolutely must have +# values. +# +# OILS_NCIP_CONFIG_DEFAULT is the default location to find our +# driver's configuration file. This location can be overridden by +# setting the path in the OILS_NCIP_CONFIG environment variable. +# +# BIB_SOURCE_DEFAULT is the config.bib_source.id to use when creating +# "short" bibs. It is used only if no entry is supplied in the +# configuration file. The provided default is 2, the id of the +# "System Local" source that comes with a default Evergreen +# installation. +use constant { + OILS_NCIP_CONFIG_DEFAULT => '/openils/conf/oils_ncip.xml', + BIB_SOURCE_DEFAULT => 2 +}; + +# A common Evergreen code shortcut to use AppUtils: +my $U = 'OpenILS::Application::AppUtils'; + +# The usual constructor: +sub new { + my $class = shift; + $class = ref $class or $class; + + # Instantiate our Object::Tiny parent with the rest of the + # arguments. It creates a blessed hashref. + my $self = $class->SUPER::new(@_); + + # Look for our configuration file, load, and parse it: + $self->_configure(); + + # Bootstrap OpenSRF and prepare some OpenILS components. + $self->_bootstrap(); + + # Initialize the rest of our internal state. + $self->_init(); + return $self; +} + +# Subroutines required by the NCIPServer interface: sub itemdata {} sub userdata {} @@ -33,4 +86,258 @@ sub cancelrequest {} sub acceptitem {} +# Implementation functions that might be useful to a subclass. + +# Get a CStoreEditor: +sub editor { + my $self = shift; + + # If we have an editor, check the validity of the auth session, then + # invalidate the editor if the session is not valid. + if ($self->{editor}) { + undef($self->{editor}) unless ($self->checkauth()); + } + + # If we don't have an editor, make a new one. + unless (defined($self->{editor})) { + $self->login() unless ($self->checkauth()); + $self->{editor} = new_editor(authtoken=>$self->{session}->{authtoken}); + } + + return $self->{editor}; +} + +# Login via OpenSRF to Evergreen. +sub login { + my $self = shift; + + # Get the authentication seed. + my $seed = $U->simple_req( + 'open-ils.auth', + 'open-ils.auth.authenticate.init', + $self->{config}->{username} + ); + + # Actually login. + if ($seed) { + my $response = $U->simple_req( + 'open-ils.auth', + 'open-ils.auth.authenticate.complete', + { + username => $self->{config}->{username}, + password => md5_hex( + $seed . md5_hex($self->{config}->{password}) + ), + type => 'staff', + workstation => $self->{config}->{workstation} + } + ); + if ($response) { + $self->{session}->{authtoken} = $response->{payload}->{authtoken}; + $self->{session}->{authtime} = $response->{payload}->{authtime}; + } + } +} + +# Return 1 if we have a 'valid' authtoken, 0 if not. +sub checkauth { + my $self = shift; + + # We implement our own version of this function, rather than rely + # on CStoreEditor, because we may want to check this at times that + # we don't have a CStoreEditor. + + # We use AppUtils to do the heavy lifting. + if (defined($self->{session})) { + if ($U->check_user_session($self->{session}->{authtoken})) { + return 1; + } else { + return 0; + } + } + + # If we reach here, we don't have a session, so we are definitely + # not logged in. + return 0; +} + +# private subroutines not meant to be used directly by subclasses. +# Most have to do with setup and/or state checking of implementation +# components. + +# Find, load, and parse our configuration file: +sub _configure { + my $self = shift; + + # Find the configuration file via variables: + my $file = OILS_NCIP_CONFIG_DEFAULT; + $file = $ENV{OILS_NCIP_CONFIG} if ($ENV{OILS_NCIP_CONFIG}); + + # Load our configuration with XML::XPath. + my $xpath = XML::XPath->new(filename => $file); + # Load configuration into $self: + $self->{config}->{bootstrap} = + _strip($xpath->findvalue("/ncip/bootstrap")->value()); + $self->{config}->{username} = + _strip($xpath->findvalue("/ncip/credentials/username")->value()); + $self->{config}->{password} = + _strip($xpath->findvalue("/ncip/credentials/password")->value()); + $self->{config}->{work_ou} = + _strip($xpath->findvalue("/ncip/credentials/work_ou")->value()); + $self->{config}->{workstation} = + _strip($xpath->findvalue("/ncip/credentials/workstation")->value()); + # Look for a list of patron profiles to treat as blocked. This is + # useful if you have a patron group or groups that are not + # permitted to do ILL. + $self->{config}->{barred_groups} = []; + my $nodes = $xpath->findnodes('/ncip/patrons/block_profile'); + if ($nodes) { + foreach my $node ($nodes->get_nodelist()) { + my $data = {id => 0, name => ""}; + my $attr = $xpath->findvalue('@pgt', $node); + if ($attr) { + $data->{id} = $attr; + } + $data->{name} = _strip($node->string_value()); + push(@{$self->{config}->{barred_groups}}, $data) + if ($data->{id} || $data->{name}); + } + } + # Check for the use_precats setting for acceptitem. This should + # only be set if you are using 2.7.0-alpha or later of Evergreen. + $self->{config}->{use_precats} = 0; + undef($nodes); + $nodes = $xpath->find('/ncip/items/use_precats'); + $self->{config}->{use_precats} = 1 if ($nodes); + # If we're not using precats, we will be making "short" bibs. We + # need to look up and see if a special bib source has been + # configured for these. + undef($nodes); + $nodes = $xpath->findnodes('/ncip/items/bib_source'); + if ($nodes) { + my $node = $nodes->get_node(1); + my $attr = $xpath->findvalue('@cbs', $node); + if ($attr) { + $self->{config}->{cbs}->{id} = $attr; + } + $self->{config}->{cbs}->{name} = _strip($node->string_value()); + } + # Look for any required asset.copy.stat_cat_entry entries. + $self->{config}->{asces} = []; + undef($nodes); + $nodes = $xpath->findnodes('/ncip/items/stat_cat_entry'); + if ($nodes) { + foreach my $node ($nodes->get_nodelist()) { + my $data = {asc => 0, id => 0, name => ''}; + my $asc = $xpath->findvalue('@asc', $node); + $data->{asc} = $asc if ($asc); + my $asce = $xpath->findvalue('@asce', $node); + $data->{id} = $asce if ($asce); + $data->{name} = _strip($node->string_value()); + push(@{$self->{config}->{asces}}, $data) + if ($data->{id} || ($data->{name} && $data->{asc})); + } + } +} + +# Bootstrap OpenSRF::System, load the IDL, and initialize the +# CStoreEditor module. +sub _bootstrap { + my $self = shift; + + my $bootstrap_config = $self->{config}->{bootstrap}; + OpenSRF::System->bootstrap_client(config_file => $bootstrap_config); + + my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL"); + Fieldmapper->import(IDL => $idl); + + OpenILS::Utils::CStoreEditor->init; +} + +# Login and then initialize some object data based on the +# configuration. +sub _init { + my $self = shift; + + # Login to Evergreen. + $self->login(); + + # Create an editor. + my $e = $self->editor(); + + # Load the barred groups as pgt objects into a blocked_profiles + # list. + $self->{blocked_profiles} = []; + foreach (@{$self->{config}->{barred_groups}}) { + if ($_->{id}) { + my $pgt = $e->retrieve_permission_grp_tree($_->{id}); + push(@{$self->{blocked_profiles}}, $pgt) if ($pgt); + } else { + my $result = $e->search_permission_grp_tree( + {name => $_->{name}} + ); + if ($result && @$result) { + map {push(@{$self->{blocked_profiles}}, $_)} @$result; + } + } + } + + # Load the bib source if we're not using precats. + unless ($self->{config}->{use_precats}) { + # Retrieve the default + my $cbs = $e->retrieve_config_bib_source(BIB_SOURCE_DEFAULT); + my $data = $self->{config}->{cbs}; + if ($data) { + if ($data->{id}) { + my $result = $e->retrieve_config_bib_source($data->{id}); + $cbs = $result if ($result); + } else { + my $result = $e->search_config_bib_source( + {source => $data->{name}} + ); + if ($result && @$result) { + $cbs = $result->[0]; # Use the first one. + } + } + } + $self->{bib_source} = $cbs; + } + + # Load the required asset.stat_cat_entries: + $self->{asces} = []; + foreach (@{$self->{config}->{asces}}) { + if ($_->{id}) { + my $asce = $e->retrieve_asset_stat_cat_entry($_->{id}); + push(@{$self->{asces}}, $asce) if ($asce); + } elsif ($_->{asc} && $_->{name}) { + # We may actually want to retrieve the ancestor tree + # beginning with $self->{config}->{work_ou} and limit the + # next search where the owner is one of those org units. + my $result = $e->search_asset_stat_cat_entry( + { + stat_cat => $_->{asc}, + value => $_->{name} + } + ); + if ($result && @$result) { + map {push(@{$self->{asces}}, $_)} @$result; + } + } + } +} + +# Standalone, "helper" functions. These do not take an object or +# class reference. + +# Strip leading and trailing whitespace (incl. newlines) from a string +# value. +sub _strip { + my $string = shift; + if ($string) { + $string =~ s/^\s+//; + $string =~ s/\s+$//; + } + return $string; +} + 1; -- 2.11.0