1 # ---------------------------------------------------------------
2 # Copyright © 2014 Jason Stephenson <jason@sigio.com>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 # ---------------------------------------------------------------
14 package NCIP::ILS::Evergreen;
17 use Object::Tiny qw/name/;
20 use OpenSRF::Utils::SettingsClient;
21 use Digest::MD5 qw/md5_hex/;
22 use OpenILS::Utils::Fieldmapper;
23 use OpenILS::Utils::CStoreEditor qw/:funcs/;
24 use OpenILS::Application::AppUtils;
25 use OpenILS::Const qw/:const/;
30 # Default values we define for things that might be missing in our
31 # runtime environment or configuration file that absolutely must have
34 # OILS_NCIP_CONFIG_DEFAULT is the default location to find our
35 # driver's configuration file. This location can be overridden by
36 # setting the path in the OILS_NCIP_CONFIG environment variable.
38 # BIB_SOURCE_DEFAULT is the config.bib_source.id to use when creating
39 # "short" bibs. It is used only if no entry is supplied in the
40 # configuration file. The provided default is 2, the id of the
41 # "System Local" source that comes with a default Evergreen
44 OILS_NCIP_CONFIG_DEFAULT => '/openils/conf/oils_ncip.xml',
45 BIB_SOURCE_DEFAULT => 2
48 # A common Evergreen code shortcut to use AppUtils:
49 my $U = 'OpenILS::Application::AppUtils';
51 # The usual constructor:
54 $class = ref $class or $class;
56 # Instantiate our Object::Tiny parent with the rest of the
57 # arguments. It creates a blessed hashref.
58 my $self = $class->SUPER::new(@_);
60 # Look for our configuration file, load, and parse it:
63 # Bootstrap OpenSRF and prepare some OpenILS components.
66 # Initialize the rest of our internal state.
72 # Subroutines required by the NCIPServer interface:
89 # Implementation functions that might be useful to a subclass.
95 # If we have an editor, check the validity of the auth session, then
96 # invalidate the editor if the session is not valid.
97 if ($self->{editor}) {
98 undef($self->{editor}) unless ($self->checkauth());
101 # If we don't have an editor, make a new one.
102 unless (defined($self->{editor})) {
103 $self->login() unless ($self->checkauth());
104 $self->{editor} = new_editor(authtoken=>$self->{session}->{authtoken});
107 return $self->{editor};
110 # Login via OpenSRF to Evergreen.
114 # Get the authentication seed.
115 my $seed = $U->simple_req(
117 'open-ils.auth.authenticate.init',
118 $self->{config}->{username}
123 my $response = $U->simple_req(
125 'open-ils.auth.authenticate.complete',
127 username => $self->{config}->{username},
129 $seed . md5_hex($self->{config}->{password})
132 workstation => $self->{config}->{workstation}
136 $self->{session}->{authtoken} = $response->{payload}->{authtoken};
137 $self->{session}->{authtime} = $response->{payload}->{authtime};
142 # Return 1 if we have a 'valid' authtoken, 0 if not.
146 # We implement our own version of this function, rather than rely
147 # on CStoreEditor, because we may want to check this at times that
148 # we don't have a CStoreEditor.
150 # We use AppUtils to do the heavy lifting.
151 if (defined($self->{session})) {
152 if ($U->check_user_session($self->{session}->{authtoken})) {
159 # If we reach here, we don't have a session, so we are definitely
164 # private subroutines not meant to be used directly by subclasses.
165 # Most have to do with setup and/or state checking of implementation
168 # Find, load, and parse our configuration file:
172 # Find the configuration file via variables:
173 my $file = OILS_NCIP_CONFIG_DEFAULT;
174 $file = $ENV{OILS_NCIP_CONFIG} if ($ENV{OILS_NCIP_CONFIG});
176 # Load our configuration with XML::XPath.
177 my $xpath = XML::XPath->new(filename => $file);
178 # Load configuration into $self:
179 $self->{config}->{bootstrap} =
180 _strip($xpath->findvalue("/ncip/bootstrap")->value());
181 $self->{config}->{username} =
182 _strip($xpath->findvalue("/ncip/credentials/username")->value());
183 $self->{config}->{password} =
184 _strip($xpath->findvalue("/ncip/credentials/password")->value());
185 $self->{config}->{work_ou} =
186 _strip($xpath->findvalue("/ncip/credentials/work_ou")->value());
187 $self->{config}->{workstation} =
188 _strip($xpath->findvalue("/ncip/credentials/workstation")->value());
189 # Look for a list of patron profiles to treat as blocked. This is
190 # useful if you have a patron group or groups that are not
191 # permitted to do ILL.
192 $self->{config}->{barred_groups} = [];
193 my $nodes = $xpath->findnodes('/ncip/patrons/block_profile');
195 foreach my $node ($nodes->get_nodelist()) {
196 my $data = {id => 0, name => ""};
197 my $attr = $xpath->findvalue('@pgt', $node);
201 $data->{name} = _strip($node->string_value());
202 push(@{$self->{config}->{barred_groups}}, $data)
203 if ($data->{id} || $data->{name});
206 # Check for the use_precats setting for acceptitem. This should
207 # only be set if you are using 2.7.0-alpha or later of Evergreen.
208 $self->{config}->{use_precats} = 0;
210 $nodes = $xpath->find('/ncip/items/use_precats');
211 $self->{config}->{use_precats} = 1 if ($nodes);
212 # If we're not using precats, we will be making "short" bibs. We
213 # need to look up and see if a special bib source has been
214 # configured for these.
216 $nodes = $xpath->findnodes('/ncip/items/bib_source');
218 my $node = $nodes->get_node(1);
219 my $attr = $xpath->findvalue('@cbs', $node);
221 $self->{config}->{cbs}->{id} = $attr;
223 $self->{config}->{cbs}->{name} = _strip($node->string_value());
225 # Look for any required asset.copy.stat_cat_entry entries.
226 $self->{config}->{asces} = [];
228 $nodes = $xpath->findnodes('/ncip/items/stat_cat_entry');
230 foreach my $node ($nodes->get_nodelist()) {
231 my $data = {asc => 0, id => 0, name => ''};
232 my $asc = $xpath->findvalue('@asc', $node);
233 $data->{asc} = $asc if ($asc);
234 my $asce = $xpath->findvalue('@asce', $node);
235 $data->{id} = $asce if ($asce);
236 $data->{name} = _strip($node->string_value());
237 push(@{$self->{config}->{asces}}, $data)
238 if ($data->{id} || ($data->{name} && $data->{asc}));
243 # Bootstrap OpenSRF::System, load the IDL, and initialize the
244 # CStoreEditor module.
248 my $bootstrap_config = $self->{config}->{bootstrap};
249 OpenSRF::System->bootstrap_client(config_file => $bootstrap_config);
251 my $idl = OpenSRF::Utils::SettingsClient->new->config_value("IDL");
252 Fieldmapper->import(IDL => $idl);
254 OpenILS::Utils::CStoreEditor->init;
257 # Login and then initialize some object data based on the
262 # Login to Evergreen.
266 my $e = $self->editor();
268 # Load the barred groups as pgt objects into a blocked_profiles
270 $self->{blocked_profiles} = [];
271 foreach (@{$self->{config}->{barred_groups}}) {
273 my $pgt = $e->retrieve_permission_grp_tree($_->{id});
274 push(@{$self->{blocked_profiles}}, $pgt) if ($pgt);
276 my $result = $e->search_permission_grp_tree(
279 if ($result && @$result) {
280 map {push(@{$self->{blocked_profiles}}, $_)} @$result;
285 # Load the bib source if we're not using precats.
286 unless ($self->{config}->{use_precats}) {
287 # Retrieve the default
288 my $cbs = $e->retrieve_config_bib_source(BIB_SOURCE_DEFAULT);
289 my $data = $self->{config}->{cbs};
292 my $result = $e->retrieve_config_bib_source($data->{id});
293 $cbs = $result if ($result);
295 my $result = $e->search_config_bib_source(
296 {source => $data->{name}}
298 if ($result && @$result) {
299 $cbs = $result->[0]; # Use the first one.
303 $self->{bib_source} = $cbs;
306 # Load the required asset.stat_cat_entries:
308 foreach (@{$self->{config}->{asces}}) {
310 my $asce = $e->retrieve_asset_stat_cat_entry($_->{id});
311 push(@{$self->{asces}}, $asce) if ($asce);
312 } elsif ($_->{asc} && $_->{name}) {
313 # We may actually want to retrieve the ancestor tree
314 # beginning with $self->{config}->{work_ou} and limit the
315 # next search where the owner is one of those org units.
316 my $result = $e->search_asset_stat_cat_entry(
318 stat_cat => $_->{asc},
322 if ($result && @$result) {
323 map {push(@{$self->{asces}}, $_)} @$result;
329 # Standalone, "helper" functions. These do not take an object or
332 # Strip leading and trailing whitespace (incl. newlines) from a string