-
Notifications
You must be signed in to change notification settings - Fork 0
/
ASImport.lua
378 lines (306 loc) · 12.2 KB
/
ASImport.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
-- Aeon ArchivesSpace Import
-- This addon will utilize the ArchivesSpace API to look up the collection/item information matching a request in Aeon.
-- Please see README for workflow and configuration explanation.
luanet.load_assembly("System");
luanet.load_assembly("log4net");
require("JsonParser");
require("AtlasHelpers");
local Types = {};
Types["System.Net.WebClient"] = luanet.import_type("System.Net.WebClient");
Types["System.Text.Encoding"] = luanet.import_type("System.Text.Encoding");
Types["log4net.LogManager"] = luanet.import_type("log4net.LogManager");
local Settings = {};
Settings.RequestMonitorQueue = GetSetting("RequestMonitorQueue");
Settings.SuccessRouteQueue = GetSetting("SuccessRouteQueue");
Settings.ErrorRouteQueue = GetSetting("ErrorRouteQueue");
Settings.ApiBaseURL = GetSetting("ArchivesSpaceApiUrl");
Settings.ArchivesSpaceUsername = GetSetting("ArchivesSpaceUsername");
Settings.ArchivesSpacePassword = GetSetting("ArchivesSpacePassword");
Settings.TopContainerUriField = GetSetting("TopContainerUriField");
Settings.RepoCodeField = GetSetting("RepoCodeField");
Settings.RepoIdMapping = AtlasHelpers.StringSplit(",", GetSetting("RepoIdMapping"));
Settings.BarcodeField = GetSetting("BarcodeField");
Settings.LocationDestinationField = GetSetting("LocationDestinationField");
local rootLogger = "AtlasSystems.Addons.Aeon-ArchivesSpace-Import";
local log = Types["log4net.LogManager"].GetLogger(rootLogger);
local sessionId;
local sessionTimeStamp;
function Init()
RegisterSystemEventHandler("SystemTimerElapsed","InitiateASpaceImport");
end
function InitiateASpaceImport()
log:Debug("Initiate ArchivesSpace Import");
ProcessDataContexts("transactionstatus", Settings.RequestMonitorQueue, "ImportASpaceInfo");
end
function ImportASpaceInfo()
sessionId = GetSessionId();
local transactionNumber = GetFieldValue("Transaction", "TransactionNumber");
if (sessionId == nil or sessionId == "") then
log:Info("Unable to retrieve auth session token from ArchiveSpace API. Skipping this processing interval. The addon will try again on the next interval.")
return;
end
local barcode = TagProcessor.ReplaceTags(Settings.BarcodeField);
if Settings.TopContainerUriField ~= "" then
if GetFieldValue("Transaction.CustomFields", Settings.TopContainerUriField) == "" then
log:Info("Top container URI is missing from " .. Settings.TopContainerUriField .. " for Transaction " .. transactionNumber);
return;
end
local topContainerUri = GetFieldValue("Transaction.CustomFields", Settings.TopContainerUriField) .. "?resolve%5B%5D=container_locations";
local success, location = pcall(ImportByTopContainerUri, topContainerUri);
if success and NotNilOrBlank(location) then
SetLocationAndRoute(location, transactionNumber);
else
HandleLocationError(location, transactionNumber, barcode);
end
elseif Settings.RepoCodeField ~= "" then
local repoCode = TagProcessor.ReplaceTags(Settings.RepoCodeField);
if repoCode == "" then
log:Info("repo_code is missing from " .. Settings.RepoCodeField:match("%.(.+)}") .. " for Transaction " .. transactionNumber);
return;
end
local success, repoId = pcall(GetRepoId, repoCode);
if success and NotNilOrBlank(repoId) then
local success, location = pcall(ImportByRepoIdAndBarcode, repoId, barcode);
if success and NotNilOrBlank(location) then
SetLocationAndRoute(location, transactionNumber);
else
HandleLocationError(location, transactionNumber, barcode);
end
else
if not NotNilOrBlank(repoId) then
log:Info("Repo ID not found for Transaction " .. transactionNumber .. " with repo_code " .. repoCode);
else
OnError(repoId);
end
ExecuteCommand("Route", {transactionNumber, Settings.ErrorRouteQueue});
end
elseif #Settings.RepoIdMapping > 0 then
local siteCode = GetFieldValue("Transaction", "Site");
local repoId = nil;
for i = 1, #Settings.RepoIdMapping do
if Settings.RepoIdMapping[i]:lower():find(siteCode:lower()) then
repoId = Settings.RepoIdMapping[i]:match("^(%d+)=");
break;
end
end
if not repoId then
log:Info("Unable to determine repo ID from RepoIdMapping.");
return;
end
local success, location = pcall(ImportByRepoIdAndBarcode, repoId, barcode);
if success and NotNilOrBlank(location) then
SetLocationAndRoute(location, transactionNumber);
else
HandleLocationError(location, transactionNumber, barcode);
end
else
log:Info("Invalid configuration. TopContainerUri, RepoCodeField, or RepoIdMapping must contain a value.");
return;
end
end
function ImportByTopContainerUri(topContainerUri)
log:Debug("Retrieving ASpace info for top level container " .. topContainerUri);
local response = SendApiRequest(topContainerUri, "GET", nil, sessionId);
log:Debug("API request completed");
local parsedResponse = JsonParser:ParseJSON(response);
local location = GetCurrentContainerLocation(parsedResponse);
if location ~= nil then
log:Debug("Location: " .. location);
else
log:Debug("No location found for container " .. topContainerUri);
end
return location;
end
function ImportByRepoIdAndBarcode(repoId, barcode)
log:Debug("Retrieving ASpace info for barcode " .. barcode);
local apiPath = "/repositories/" .. repoId .. "/top_containers/search?q=" .. barcode .. "&fields[]=json&page=1";
local response = SendApiRequest(apiPath, "GET", nil, sessionId);
log:Debug("API Request completed");
local parsedResponse = JsonParser:ParseJSON(response);
local location = GetCurrentContainerLocation(parsedResponse);
if NotNilOrBlank(location) then
log:Debug("Location: " .. location);
else
log:Debug("No location found for barcode " .. barcode .. " in repo " .. repoId);
end
return location;
end
function GetRepoId(repoCode)
log:Debug("Retrieving list of ASpace repos");
local apiPath = "/repositories";
local response = SendApiRequest(apiPath, "GET", nil, sessionId);
log:Debug("API Request completed");
local parsedResponse = JsonParser:ParseJSON(response);
local repoId = nil;
for i = 1, #parsedResponse do
if parsedResponse[i].repo_code == repoCode then
repoId = parsedResponse[i].uri:match("%d+$");
break;
end
end
if NotNilOrBlank(repoId) then
log:Debug("Repo ID: " .. repoId);
else
log:Debug("No repo ID found for repo_code " .. repoCode);
end
return repoId;
end
function GetCurrentContainerLocation(parsedResponse)
-- The response from the /repositories/[repoId]/top_containers endpoint has the JSON we need in a "json" field
log:Debug("Response first JSON field: " .. tostring(parsedResponse["response"]["docs"][1]["json"]));
local containerLocations = parsedResponse.container_locations or JsonParser:ParseJSON(parsedResponse["response"]["docs"][1]["json"]).container_locations;
local currentContainerLocationTitle;
for i = 1, #containerLocations do
if containerLocations[i].status == "current" then
currentContainerLocationTitle = containerLocations[i]._resolved.title;
break;
end
end
return currentContainerLocationTitle;
end
function SetLocationAndRoute(location, transactionNumber)
SetFieldValue("Transaction" , Settings.LocationDestinationField, location);
SaveDataSource("Transaction");
ExecuteCommand("Route", {transactionNumber, Settings.SuccessRouteQueue});
end
function GetAuthenticationToken()
local authenticationToken = JsonParser:ParseJSON(SendApiRequest('/users/' .. Settings.ArchivesSpaceUsername .. '/login', 'POST', "password=" .. Settings.ArchivesSpacePassword));
if (authenticationToken == nil or authenticationToken == JsonParser.NIL) then
log:Error("Unable to get valid authentication token.");
return;
end
return authenticationToken;
end
function GetSessionId()
if (sessionId == nil or sessionId == "" or (sessionTimeStamp + 60 * 60) < os.time()) then
log:Debug("Renewing ArchivesSpace authentication token.");
local authentication = GetAuthenticationToken();
sessionId = ExtractProperty(authentication, "session");
if (sessionId == nil or sessionId == JsonParser.NIL) then
log:Error("Unable to get valid session ID token.");
return;
end
sessionTimeStamp = os.time();
end
return sessionId;
end
function SendApiRequest(apiPath, method, parameters, sessionId)
local webClient = Types["System.Net.WebClient"]();
webClient.Headers:Clear();
if (sessionId ~= nil and sessionId ~= "") then
webClient.Headers:Add("X-ArchivesSpace-Session", sessionId);
end
local success, result;
if (method == 'POST') then
success, result = pcall(WebClientPost, webClient, apiPath, method, parameters);
else
success, result = pcall(WebClientGet, webClient, apiPath);
end
webClient:Dispose();
if (success) then
log:Debug("API call successful");
log:Debug("Response: " .. result);
return result;
else
log:Debug("API call error");
OnError(result);
return "";
end
end
function HandleLocationError(location, transactionNumber, barcode);
if not NotNilOrBlank(location) then
log:Info("No location found for Transaction " .. transactionNumber .. " with barcode " .. barcode);
else
OnError(location);
end
ExecuteCommand("Route", {transactionNumber, Settings.ErrorRouteQueue});
end
function ObjectToString(o)
if type(o) == 'table' then
local s = '{ '
for k,v in pairs(o) do
if type(k) ~= 'number' then k = '"'..k..'"' end
s = s .. '['..k..'] = ' .. ObjectToString(v) .. ','
end
return s .. '} '
else
return tostring(o)
end
end
function ExtractProperty(object, property)
if object then
return EmptyStringIfNil(object[property]);
end
end
function EmptyStringIfNil(value)
if (value == nil or value == JsonParser.NIL) then
return "";
else
return value;
end
end
function WebClientPost(webClient, apiPath, method, postParameters)
return webClient:UploadString(PathCombine(Settings.ApiBaseURL, apiPath), method, postParameters);
end
function WebClientGet(webClient, apiPath)
return webClient:DownloadString(PathCombine(Settings.ApiBaseURL, apiPath));
end
function OnError(e)
if e == nil then
log:Error("OnError supplied a nil error");
return;
end
if not e.GetType then
-- Not a .NET type
-- Attempt to log value
pcall(function ()
log:Error(e);
end);
return;
else
if not e.Message then
log:Error(e:ToString());
return;
end
end
local message = TraverseError(e);
if message == nil then
message = "Unspecified Error";
end
log:Error("An error occurred while processing the ArchivesSpace API request:\r\n" .. message);
end
function TraverseError(e)
if not e.GetType then
-- Not a .NET type
return nil;
else
if not e.Message then
-- Not a .NET exception
log:Debug(e:ToString());
return nil;
end
end
log:Debug(e.Message);
if e.InnerException then
return TraverseError(e.InnerException);
else
return e.Message;
end
end
function PathCombine(path1, path2)
local trailingSlashPattern = '/$';
local leadingSlashPattern = '^/';
if (path1 and path2) then
local result = path1:gsub(trailingSlashPattern, '') .. '/' .. path2:gsub(leadingSlashPattern, '');
return result;
else
return "";
end
end
function NotNilOrBlank(value)
if value == nil or value == JsonParser.NIL or value == "" then
return false;
else
return true;
end
end