Mercurial > hg > orthanc
comparison OrthancServer/OrthancRestApi/OrthancRestModalities.cpp @ 3761:3b5feb2bbd4b transcoding
integration mainline->transcoding
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Mon, 16 Mar 2020 12:17:43 +0100 |
parents | e69c556f1913 |
children | 7f083dfae62b |
comparison
equal
deleted
inserted
replaced
3748:ca36e3f1112c | 3761:3b5feb2bbd4b |
---|---|
44 #include "../ServerContext.h" | 44 #include "../ServerContext.h" |
45 #include "../ServerJobs/DicomModalityStoreJob.h" | 45 #include "../ServerJobs/DicomModalityStoreJob.h" |
46 #include "../ServerJobs/DicomMoveScuJob.h" | 46 #include "../ServerJobs/DicomMoveScuJob.h" |
47 #include "../ServerJobs/OrthancPeerStoreJob.h" | 47 #include "../ServerJobs/OrthancPeerStoreJob.h" |
48 #include "../ServerToolbox.h" | 48 #include "../ServerToolbox.h" |
49 #include "../StorageCommitmentReports.h" | |
49 | 50 |
50 | 51 |
51 namespace Orthanc | 52 namespace Orthanc |
52 { | 53 { |
53 static const char* const KEY_LEVEL = "Level"; | 54 static const char* const KEY_LEVEL = "Level"; |
961 if (moveOriginatorID != 0) | 962 if (moveOriginatorID != 0) |
962 { | 963 { |
963 job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); | 964 job->SetMoveOriginator(moveOriginatorAET, moveOriginatorID); |
964 } | 965 } |
965 | 966 |
967 // New in Orthanc 1.6.0 | |
968 if (Toolbox::GetJsonBooleanField(request, "StorageCommitment", false)) | |
969 { | |
970 job->EnableStorageCommitment(true); | |
971 } | |
972 | |
966 OrthancRestApi::GetApi(call).SubmitCommandsJob | 973 OrthancRestApi::GetApi(call).SubmitCommandsJob |
967 (call, job.release(), true /* synchronous by default */, request); | 974 (call, job.release(), true /* synchronous by default */, request); |
968 } | 975 } |
969 | 976 |
970 | 977 |
1271 | 1278 |
1272 Json::Value json; | 1279 Json::Value json; |
1273 if (call.ParseJsonRequest(json)) | 1280 if (call.ParseJsonRequest(json)) |
1274 { | 1281 { |
1275 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); | 1282 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); |
1276 RemoteModalityParameters remote = | 1283 const RemoteModalityParameters remote = |
1277 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); | 1284 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); |
1278 | 1285 |
1279 std::unique_ptr<ParsedDicomFile> query | 1286 std::unique_ptr<ParsedDicomFile> query |
1280 (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0), | 1287 (ParsedDicomFile::CreateFromJson(json, static_cast<DicomFromJsonFlags>(0), |
1281 "" /* no private creator */)); | 1288 "" /* no private creator */)); |
1296 { | 1303 { |
1297 throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object"); | 1304 throw OrthancException(ErrorCode_BadFileFormat, "Must provide a JSON object"); |
1298 } | 1305 } |
1299 } | 1306 } |
1300 | 1307 |
1308 | |
1309 // Storage commitment SCU --------------------------------------------------- | |
1310 | |
1311 static void StorageCommitmentScu(RestApiPostCall& call) | |
1312 { | |
1313 static const char* const ORTHANC_RESOURCES = "Resources"; | |
1314 static const char* const DICOM_INSTANCES = "DicomInstances"; | |
1315 static const char* const SOP_CLASS_UID = "SOPClassUID"; | |
1316 static const char* const SOP_INSTANCE_UID = "SOPInstanceUID"; | |
1317 | |
1318 ServerContext& context = OrthancRestApi::GetContext(call); | |
1319 | |
1320 Json::Value json; | |
1321 if (!call.ParseJsonRequest(json) || | |
1322 json.type() != Json::objectValue) | |
1323 { | |
1324 throw OrthancException(ErrorCode_BadFileFormat, | |
1325 "Must provide a JSON object with a list of resources"); | |
1326 } | |
1327 else if (!json.isMember(ORTHANC_RESOURCES) && | |
1328 !json.isMember(DICOM_INSTANCES)) | |
1329 { | |
1330 throw OrthancException(ErrorCode_BadFileFormat, | |
1331 "Empty storage commitment request, one of these fields is mandatory: \"" + | |
1332 std::string(ORTHANC_RESOURCES) + "\" or \"" + std::string(DICOM_INSTANCES) + "\""); | |
1333 } | |
1334 else | |
1335 { | |
1336 std::list<std::string> sopClassUids, sopInstanceUids; | |
1337 | |
1338 if (json.isMember(ORTHANC_RESOURCES)) | |
1339 { | |
1340 const Json::Value& resources = json[ORTHANC_RESOURCES]; | |
1341 | |
1342 if (resources.type() != Json::arrayValue) | |
1343 { | |
1344 throw OrthancException(ErrorCode_BadFileFormat, | |
1345 "The \"" + std::string(ORTHANC_RESOURCES) + | |
1346 "\" field must provide an array of Orthanc resources"); | |
1347 } | |
1348 else | |
1349 { | |
1350 for (Json::Value::ArrayIndex i = 0; i < resources.size(); i++) | |
1351 { | |
1352 if (resources[i].type() != Json::stringValue) | |
1353 { | |
1354 throw OrthancException(ErrorCode_BadFileFormat, | |
1355 "The \"" + std::string(ORTHANC_RESOURCES) + | |
1356 "\" field must provide an array of strings, found: " + resources[i].toStyledString()); | |
1357 } | |
1358 | |
1359 std::list<std::string> instances; | |
1360 context.GetIndex().GetChildInstances(instances, resources[i].asString()); | |
1361 | |
1362 for (std::list<std::string>::const_iterator | |
1363 it = instances.begin(); it != instances.end(); ++it) | |
1364 { | |
1365 std::string sopClassUid, sopInstanceUid; | |
1366 DicomMap tags; | |
1367 if (context.LookupOrReconstructMetadata(sopClassUid, *it, MetadataType_Instance_SopClassUid) && | |
1368 context.GetIndex().GetAllMainDicomTags(tags, *it) && | |
1369 tags.LookupStringValue(sopInstanceUid, DICOM_TAG_SOP_INSTANCE_UID, false)) | |
1370 { | |
1371 sopClassUids.push_back(sopClassUid); | |
1372 sopInstanceUids.push_back(sopInstanceUid); | |
1373 } | |
1374 else | |
1375 { | |
1376 throw OrthancException(ErrorCode_InternalError, | |
1377 "Cannot retrieve SOP Class/Instance UID of Orthanc instance: " + *it); | |
1378 } | |
1379 } | |
1380 } | |
1381 } | |
1382 } | |
1383 | |
1384 if (json.isMember(DICOM_INSTANCES)) | |
1385 { | |
1386 const Json::Value& instances = json[DICOM_INSTANCES]; | |
1387 | |
1388 if (instances.type() != Json::arrayValue) | |
1389 { | |
1390 throw OrthancException(ErrorCode_BadFileFormat, | |
1391 "The \"" + std::string(DICOM_INSTANCES) + | |
1392 "\" field must provide an array of DICOM instances"); | |
1393 } | |
1394 else | |
1395 { | |
1396 for (Json::Value::ArrayIndex i = 0; i < instances.size(); i++) | |
1397 { | |
1398 if (instances[i].type() == Json::arrayValue) | |
1399 { | |
1400 if (instances[i].size() != 2 || | |
1401 instances[i][0].type() != Json::stringValue || | |
1402 instances[i][1].type() != Json::stringValue) | |
1403 { | |
1404 throw OrthancException(ErrorCode_BadFileFormat, | |
1405 "An instance entry must provide an array with 2 strings: " | |
1406 "SOP Class UID and SOP Instance UID"); | |
1407 } | |
1408 else | |
1409 { | |
1410 sopClassUids.push_back(instances[i][0].asString()); | |
1411 sopInstanceUids.push_back(instances[i][1].asString()); | |
1412 } | |
1413 } | |
1414 else if (instances[i].type() == Json::objectValue) | |
1415 { | |
1416 if (!instances[i].isMember(SOP_CLASS_UID) || | |
1417 !instances[i].isMember(SOP_INSTANCE_UID) || | |
1418 instances[i][SOP_CLASS_UID].type() != Json::stringValue || | |
1419 instances[i][SOP_INSTANCE_UID].type() != Json::stringValue) | |
1420 { | |
1421 throw OrthancException(ErrorCode_BadFileFormat, | |
1422 "An instance entry must provide an object with 2 string fiels: " | |
1423 "\"" + std::string(SOP_CLASS_UID) + "\" and \"" + | |
1424 std::string(SOP_INSTANCE_UID)); | |
1425 } | |
1426 else | |
1427 { | |
1428 sopClassUids.push_back(instances[i][SOP_CLASS_UID].asString()); | |
1429 sopInstanceUids.push_back(instances[i][SOP_INSTANCE_UID].asString()); | |
1430 } | |
1431 } | |
1432 else | |
1433 { | |
1434 throw OrthancException(ErrorCode_BadFileFormat, | |
1435 "JSON array or object is expected to specify one " | |
1436 "instance to be queried, found: " + instances[i].toStyledString()); | |
1437 } | |
1438 } | |
1439 } | |
1440 } | |
1441 | |
1442 if (sopClassUids.size() != sopInstanceUids.size()) | |
1443 { | |
1444 throw OrthancException(ErrorCode_InternalError); | |
1445 } | |
1446 | |
1447 const std::string transactionUid = Toolbox::GenerateDicomPrivateUniqueIdentifier(); | |
1448 | |
1449 if (sopClassUids.empty()) | |
1450 { | |
1451 LOG(WARNING) << "Issuing an outgoing storage commitment request that is empty: " << transactionUid; | |
1452 } | |
1453 | |
1454 { | |
1455 const RemoteModalityParameters remote = | |
1456 MyGetModalityUsingSymbolicName(call.GetUriComponent("id", "")); | |
1457 | |
1458 const std::string& remoteAet = remote.GetApplicationEntityTitle(); | |
1459 const std::string& localAet = context.GetDefaultLocalApplicationEntityTitle(); | |
1460 | |
1461 // Create a "pending" storage commitment report BEFORE the | |
1462 // actual SCU call in order to avoid race conditions | |
1463 context.GetStorageCommitmentReports().Store( | |
1464 transactionUid, new StorageCommitmentReports::Report(remoteAet)); | |
1465 | |
1466 DicomUserConnection scu(localAet, remote); | |
1467 | |
1468 std::vector<std::string> a(sopClassUids.begin(), sopClassUids.end()); | |
1469 std::vector<std::string> b(sopInstanceUids.begin(), sopInstanceUids.end()); | |
1470 scu.RequestStorageCommitment(transactionUid, a, b); | |
1471 } | |
1472 | |
1473 Json::Value result = Json::objectValue; | |
1474 result["ID"] = transactionUid; | |
1475 result["Path"] = "/storage-commitment/" + transactionUid; | |
1476 call.GetOutput().AnswerJson(result); | |
1477 } | |
1478 } | |
1479 | |
1480 | |
1481 static void GetStorageCommitmentReport(RestApiGetCall& call) | |
1482 { | |
1483 ServerContext& context = OrthancRestApi::GetContext(call); | |
1484 | |
1485 const std::string& transactionUid = call.GetUriComponent("id", ""); | |
1486 | |
1487 { | |
1488 StorageCommitmentReports::Accessor accessor( | |
1489 context.GetStorageCommitmentReports(), transactionUid); | |
1490 | |
1491 if (accessor.IsValid()) | |
1492 { | |
1493 Json::Value json; | |
1494 accessor.GetReport().Format(json); | |
1495 call.GetOutput().AnswerJson(json); | |
1496 } | |
1497 else | |
1498 { | |
1499 throw OrthancException(ErrorCode_InexistentItem, | |
1500 "No storage commitment transaction with UID: " + transactionUid); | |
1501 } | |
1502 } | |
1503 } | |
1504 | |
1505 | |
1506 static void RemoveAfterStorageCommitment(RestApiPostCall& call) | |
1507 { | |
1508 ServerContext& context = OrthancRestApi::GetContext(call); | |
1509 | |
1510 const std::string& transactionUid = call.GetUriComponent("id", ""); | |
1511 | |
1512 { | |
1513 StorageCommitmentReports::Accessor accessor( | |
1514 context.GetStorageCommitmentReports(), transactionUid); | |
1515 | |
1516 if (!accessor.IsValid()) | |
1517 { | |
1518 throw OrthancException(ErrorCode_InexistentItem, | |
1519 "No storage commitment transaction with UID: " + transactionUid); | |
1520 } | |
1521 else if (accessor.GetReport().GetStatus() != StorageCommitmentReports::Report::Status_Success) | |
1522 { | |
1523 throw OrthancException(ErrorCode_BadSequenceOfCalls, | |
1524 "Cannot remove DICOM instances after failure " | |
1525 "in storage commitment transaction: " + transactionUid); | |
1526 } | |
1527 else | |
1528 { | |
1529 std::vector<std::string> sopInstanceUids; | |
1530 accessor.GetReport().GetSuccessSopInstanceUids(sopInstanceUids); | |
1531 | |
1532 for (size_t i = 0; i < sopInstanceUids.size(); i++) | |
1533 { | |
1534 std::vector<std::string> orthancId; | |
1535 context.GetIndex().LookupIdentifierExact( | |
1536 orthancId, ResourceType_Instance, DICOM_TAG_SOP_INSTANCE_UID, sopInstanceUids[i]); | |
1537 | |
1538 for (size_t j = 0; j < orthancId.size(); j++) | |
1539 { | |
1540 LOG(INFO) << "Storage commitment - Removing SOP instance UID / Orthanc ID: " | |
1541 << sopInstanceUids[i] << " / " << orthancId[j]; | |
1542 | |
1543 Json::Value tmp; | |
1544 context.GetIndex().DeleteResource(tmp, orthancId[j], ResourceType_Instance); | |
1545 } | |
1546 } | |
1547 | |
1548 call.GetOutput().AnswerBuffer("{}", MimeType_Json); | |
1549 } | |
1550 } | |
1551 } | |
1552 | |
1301 | 1553 |
1302 void OrthancRestApi::RegisterModalities() | 1554 void OrthancRestApi::RegisterModalities() |
1303 { | 1555 { |
1304 Register("/modalities", ListModalities); | 1556 Register("/modalities", ListModalities); |
1305 Register("/modalities/{id}", ListModalityOperations); | 1557 Register("/modalities/{id}", ListModalityOperations); |
1340 Register("/peers/{id}", DeletePeer); | 1592 Register("/peers/{id}", DeletePeer); |
1341 Register("/peers/{id}/store", PeerStore); | 1593 Register("/peers/{id}/store", PeerStore); |
1342 Register("/peers/{id}/system", PeerSystem); | 1594 Register("/peers/{id}/system", PeerSystem); |
1343 | 1595 |
1344 Register("/modalities/{id}/find-worklist", DicomFindWorklist); | 1596 Register("/modalities/{id}/find-worklist", DicomFindWorklist); |
1597 | |
1598 // Storage commitment | |
1599 Register("/modalities/{id}/storage-commitment", StorageCommitmentScu); | |
1600 Register("/storage-commitment/{id}", GetStorageCommitmentReport); | |
1601 Register("/storage-commitment/{id}/remove", RemoveAfterStorageCommitment); | |
1345 } | 1602 } |
1346 } | 1603 } |