Net-Z3950-FOLIO
view release on metacpan or search on metacpan
lib/Net/Z3950/FOLIO/Config.pm view on Meta::CPAN
package Net::Z3950::FOLIO::Config;
use 5.008000;
use strict;
use warnings;
use IO::File;
use Cpanel::JSON::XS qw(decode_json);
# Possible values of $missingAction
sub MISSING_ERROR { 0 }
sub MISSING_TENANT { 1 }
sub MISSING_FILTER { 2 }
sub new {
my $class = shift();
my($cfgbase, @extras) = @_;
my $cfg = _compileConfig($cfgbase, @extras);
return bless $cfg, $class;
}
sub _compileConfig {
my($cfgbase, @extras) = @_;
my $cfg = _compileConfigFile($cfgbase, undef, MISSING_ERROR);
my $isFirst = 1;
while (@extras) {
my $extra = shift @extras;
my $overlay = _compileConfigFile($cfgbase, $extra, $isFirst ? MISSING_TENANT : MISSING_FILTER);
$isFirst = 0;
_mergeConfig($cfg, $overlay);
}
my $path = $cfgbase;
if ($path =~ /\//) {
$path =~ s/(.*)?\/.*/$1/;
} else {
$path = '.';
}
my $gqlfile = $cfg->{graphqlQuery}
or die "$0: no GraphQL query file defined";
my $fh = new IO::File("<$path/$gqlfile")
or die "$0: can't open GraphQL query file '$path/$gqlfile': $!";
{ local $/; $cfg->{graphql} = <$fh> };
$fh->close();
my $esets = $cfg->{xmlElementSets};
if ($esets) {
foreach my $setname (keys %$esets) {
my $filename = $esets->{$setname};
my $fh = new IO::File("<$path/$filename")
or Net::Z3950::FOLIO::_throw(1, "can't open XSLT stylesheet file '$path/$filename': $!");
{ local $/; $esets->{$setname} = <$fh> };
$fh->close();
}
}
return $cfg;
}
sub _compileConfigFile {
my($cfgbase, $cfgsub, $missingAction) = @_;
my $cfgname = $cfgbase . ($cfgsub ? ".$cfgsub" : '') . '.json';
my $fh = new IO::File("<$cfgname");
if (!$fh) {
if ($! == 2 && $missingAction == MISSING_TENANT) {
Net::Z3950::FOLIO::_throw(109, "$cfgsub");
} elsif ($! == 2 && $missingAction == MISSING_FILTER) {
Net::Z3950::FOLIO::_throw(1, "filter not configured: $cfgsub");
}
die "$0: can't open config file '$cfgbase.json': $!"
}
my $json; { local $/; $json = <$fh> };
$fh->close();
my $cfg = decode_json($json);
_expandVariableReferences($cfg);
return $cfg;
}
sub _expandVariableReferences {
my($obj) = @_;
foreach my $key (sort keys %$obj) {
$obj->{$key} = _expandSingleVariableReference($key, $obj->{$key});
}
return $obj;
}
sub _expandSingleVariableReference {
my($key, $val) = @_;
if (ref($val) eq 'HASH') {
return _expandVariableReferences($val);
} elsif (ref($val) eq 'ARRAY') {
return [ map { _expandSingleVariableReference($key, $_) } @$val ];
} elsif (ref($val) eq 'JSON::PP::Boolean') {
return $val;
} elsif (!ref($val)) {
return _expandScalarVariableReference($key, $val);
} else {
use Data::Dumper;
die "non-hash, non-array, non-boolean, non-scalar configuration key '$key' = ", Dumper($val);
}
}
sub _expandScalarVariableReference {
my ($key, $val) = @_;
my $orig = $val;
while ($val =~ /(.*?)\$\{(.*?)}(.*)/) {
my($pre, $inclusion, $post) = ($1, $2, $3);
my($name, $default);
if ($inclusion =~ /(.*?)-(.*)/) {
$name = $1;
$default = $2;
} else {
$name = $inclusion;
$default = undef;
}
my $env = $ENV{$name} || $default;
if (!defined $env) {
warn "environment variable '$2' not defined for '$key'";
$env = '';
}
$val = "$pre$env$post";
}
return $val;
}
sub _mergeConfig {
my($base, $overlay) = @_;
my %complex_keys = (
okapi => 1,
login => 1,
indexMap => 1,
marcHoldings => 1,
fieldDefinitions => {
holding => 1,
circulation => 1,
},
);
foreach my $key (keys (%complex_keys)) {
if (defined $overlay->{$key}) {
if (ref $base->{$key} eq 'HASH') {
_mergeHash($base->{$key}, $overlay->{$key}, $complex_keys{$key});
} else {
$base->{$key} = $overlay->{$key};
}
}
}
foreach my $key (sort keys %$overlay) {
if (!$complex_keys{$key}) {
$base->{$key} = $overlay->{$key};
}
}
}
sub _mergeHash {
my($base, $overlay, $keysOfsubStructures) = @_;
foreach my $key (sort keys %$overlay) {
if (ref $keysOfsubStructures eq 'HASH' && $keysOfsubStructures->{$key}) {
_mergeHash($base->{$key}, $overlay->{$key}, $keysOfsubStructures->{$key});
} else {
$base->{$key} = $overlay->{$key};
}
}
}
=encoding utf-8
=head1 NAME
Net::Z3950::FOLIO::Config - configuration file for the FOLIO Z39.50 gateway
=head1 SYNOPSIS
{
"okapi": {
"url": "https://folio-snapshot-okapi.dev.folio.org",
"tenant": "${OKAPI_TENANT-indexdata}"
},
"login": {
"username": "diku_admin",
"password": "${OKAPI_PASSWORD}"
},
"indexMap": {
"1": "author",
"7": "identifiers/@value/@identifierTypeId=\"8261054f-be78-422d-bd51-4ed9f33c3422\"",
"4": "title",
"12": {
"cql": "hrid",
"relation": "==",
"omitSortIndexModifiers": [ "missing", "case" ]
},
"21": "subject",
"1016": "author,title,hrid,subject"
},
"queryFilter": "source=marc",
"graphqlQuery": "instances.graphql-query",
"chunkSize": 5,
"fieldDefinitions": {
"circulation": {
"availableThru": "permanentLoanType"
}
},
"marcHoldings": {
"restrictToItem": 0,
"field": "952",
"indicators": [" ", " "],
"holdingsElements": {
"t": "copyNumber"
},
"itemElements": {
"b": "itemId",
"k": "_callNumberPrefix",
"h": "_callNumber",
"m": "_callNumberSuffix",
"v": "_volume",
"e": "_enumeration",
"y": "_yearCaption",
"c": "_chronology"
}
},
"postProcessing": {
( run in 2.476 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )