Mercurial > hg > orthanc
comparison OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 4623:95ffe3b6ef7c db-changes
handling of revisions for metadata
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 16 Apr 2021 17:13:03 +0200 |
parents | bec74e29f86b |
children | f7d5372b59b3 |
comparison
equal
deleted
inserted
replaced
4622:9086aeb9d9d2 | 4623:95ffe3b6ef7c |
---|---|
59 * This semaphore is used to limit the number of concurrent HTTP | 59 * This semaphore is used to limit the number of concurrent HTTP |
60 * requests on CPU-intensive routes of the REST API, in order to | 60 * requests on CPU-intensive routes of the REST API, in order to |
61 * prevent exhaustion of resources (new in Orthanc 1.7.0). | 61 * prevent exhaustion of resources (new in Orthanc 1.7.0). |
62 **/ | 62 **/ |
63 static Orthanc::Semaphore throttlingSemaphore_(4); // TODO => PARAMETER? | 63 static Orthanc::Semaphore throttlingSemaphore_(4); // TODO => PARAMETER? |
64 | |
65 | |
66 static const std::string CHECK_REVISIONS = "CheckRevisions"; | |
64 | 67 |
65 | 68 |
66 namespace Orthanc | 69 namespace Orthanc |
67 { | 70 { |
68 static std::string GetDocumentationSampleResource(ResourceType type) | 71 static std::string GetDocumentationSampleResource(ResourceType type) |
1445 | 1448 |
1446 call.GetOutput().AnswerJson(result); | 1449 call.GetOutput().AnswerJson(result); |
1447 } | 1450 } |
1448 | 1451 |
1449 | 1452 |
1453 static bool GetRevisionHeader(int64_t& revision /* out */, | |
1454 const RestApiCall& call, | |
1455 const std::string& header) | |
1456 { | |
1457 std::string lower; | |
1458 Toolbox::ToLowerCase(lower, header); | |
1459 | |
1460 HttpToolbox::Arguments::const_iterator found = call.GetHttpHeaders().find(lower); | |
1461 if (found == call.GetHttpHeaders().end()) | |
1462 { | |
1463 return false; | |
1464 } | |
1465 else | |
1466 { | |
1467 std::string value = Toolbox::StripSpaces(found->second); | |
1468 Toolbox::RemoveSurroundingQuotes(value); | |
1469 | |
1470 try | |
1471 { | |
1472 revision = boost::lexical_cast<int64_t>(value); | |
1473 return true; | |
1474 } | |
1475 catch (boost::bad_lexical_cast&) | |
1476 { | |
1477 throw OrthancException(ErrorCode_ParameterOutOfRange, "The \"" + header + | |
1478 "\" HTTP header should contain the revision as an integer, but found: " + value); | |
1479 } | |
1480 } | |
1481 } | |
1482 | |
1483 | |
1450 static void GetMetadata(RestApiGetCall& call) | 1484 static void GetMetadata(RestApiGetCall& call) |
1451 { | 1485 { |
1452 if (call.IsDocumentation()) | 1486 if (call.IsDocumentation()) |
1453 { | 1487 { |
1454 ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str()); | 1488 ResourceType t = StringToResourceType(call.GetFullUri()[0].c_str()); |
1457 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */)) | 1491 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */)) |
1458 .SetSummary("Get metadata") | 1492 .SetSummary("Get metadata") |
1459 .SetDescription("Get the value of a metadata that is associated with the given " + r) | 1493 .SetDescription("Get the value of a metadata that is associated with the given " + r) |
1460 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") | 1494 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") |
1461 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)") | 1495 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)") |
1462 .AddAnswerType(MimeType_PlainText, "Value of the metadata"); | 1496 .AddAnswerType(MimeType_PlainText, "Value of the metadata") |
1497 .SetAnswerHeader("ETag", "Revision of the metadata, to be used in further `PUT` or `DELETE` operations") | |
1498 .SetHttpHeader("If-None-Match", "Optional revision of the metadata, to check if its content has changed"); | |
1463 return; | 1499 return; |
1464 } | 1500 } |
1465 | 1501 |
1466 assert(!call.GetFullUri().empty()); | 1502 assert(!call.GetFullUri().empty()); |
1467 const std::string publicId = call.GetUriComponent("id", ""); | 1503 const std::string publicId = call.GetUriComponent("id", ""); |
1469 | 1505 |
1470 std::string name = call.GetUriComponent("name", ""); | 1506 std::string name = call.GetUriComponent("name", ""); |
1471 MetadataType metadata = StringToMetadata(name); | 1507 MetadataType metadata = StringToMetadata(name); |
1472 | 1508 |
1473 std::string value; | 1509 std::string value; |
1474 if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, level, metadata)) | 1510 int64_t revision; |
1475 { | 1511 if (OrthancRestApi::GetIndex(call).LookupMetadata(value, revision, publicId, level, metadata)) |
1476 call.GetOutput().AnswerBuffer(value, MimeType_PlainText); | 1512 { |
1513 call.GetOutput().GetLowLevelOutput(). | |
1514 AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(revision) + "\""); // New in Orthanc 1.9.2 | |
1515 | |
1516 int64_t userRevision; | |
1517 if (GetRevisionHeader(userRevision, call, "If-None-Match") && | |
1518 revision == userRevision) | |
1519 { | |
1520 call.GetOutput().GetLowLevelOutput().SendStatus(HttpStatus_304_NotModified); | |
1521 } | |
1522 else | |
1523 { | |
1524 call.GetOutput().AnswerBuffer(value, MimeType_PlainText); | |
1525 } | |
1477 } | 1526 } |
1478 } | 1527 } |
1479 | 1528 |
1480 | 1529 |
1481 static void DeleteMetadata(RestApiDeleteCall& call) | 1530 static void DeleteMetadata(RestApiDeleteCall& call) |
1488 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */)) | 1537 .SetTag(GetResourceTypeText(t, true /* plural */, true /* upper case */)) |
1489 .SetSummary("Delete metadata") | 1538 .SetSummary("Delete metadata") |
1490 .SetDescription("Delete some metadata associated with the given DICOM " + r + | 1539 .SetDescription("Delete some metadata associated with the given DICOM " + r + |
1491 ". This call will fail if trying to delete a system metadata (i.e. whose index is < 1024).") | 1540 ". This call will fail if trying to delete a system metadata (i.e. whose index is < 1024).") |
1492 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") | 1541 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") |
1493 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)"); | 1542 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)") |
1543 .SetHttpHeader("If-Match", "Revision of the metadata, to check if its content has not changed and can " | |
1544 "be deleted. This option is mandatory if `CheckRevision` option is `true`."); | |
1494 return; | 1545 return; |
1495 } | 1546 } |
1496 | 1547 |
1497 CheckValidResourceType(call); | 1548 CheckValidResourceType(call); |
1498 | 1549 const std::string publicId = call.GetUriComponent("id", ""); |
1499 std::string publicId = call.GetUriComponent("id", ""); | 1550 |
1500 std::string name = call.GetUriComponent("name", ""); | 1551 std::string name = call.GetUriComponent("name", ""); |
1501 MetadataType metadata = StringToMetadata(name); | 1552 MetadataType metadata = StringToMetadata(name); |
1502 | 1553 |
1503 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata | 1554 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata |
1504 { | 1555 { |
1505 OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); | 1556 bool found; |
1506 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | 1557 int64_t revision; |
1558 if (GetRevisionHeader(revision, call, "if-match")) | |
1559 { | |
1560 found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, true, revision); | |
1561 } | |
1562 else | |
1563 { | |
1564 OrthancConfiguration::ReaderLock lock; | |
1565 if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false)) | |
1566 { | |
1567 throw OrthancException(ErrorCode_Revision, | |
1568 "HTTP header \"If-Match\" is missing, as \"CheckRevision\" is \"true\""); | |
1569 } | |
1570 else | |
1571 { | |
1572 found = OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata, false, -1 /* dummy value */); | |
1573 } | |
1574 } | |
1575 | |
1576 if (found) | |
1577 { | |
1578 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | |
1579 } | |
1580 else | |
1581 { | |
1582 throw OrthancException(ErrorCode_UnknownResource); | |
1583 } | |
1507 } | 1584 } |
1508 else | 1585 else |
1509 { | 1586 { |
1510 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | 1587 call.GetOutput().SignalError(HttpStatus_403_Forbidden); |
1511 } | 1588 } |
1523 .SetSummary("Set metadata") | 1600 .SetSummary("Set metadata") |
1524 .SetDescription("Set the value of some metadata in the given DICOM " + r + | 1601 .SetDescription("Set the value of some metadata in the given DICOM " + r + |
1525 ". This call will fail if trying to modify a system metadata (i.e. whose index is < 1024).") | 1602 ". This call will fail if trying to modify a system metadata (i.e. whose index is < 1024).") |
1526 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") | 1603 .SetUriArgument("id", "Orthanc identifier of the " + r + " of interest") |
1527 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)") | 1604 .SetUriArgument("name", "The name of the metadata, or its index (cf. `UserMetadata` configuration option)") |
1528 .AddRequestType(MimeType_PlainText, "String value of the metadata"); | 1605 .AddRequestType(MimeType_PlainText, "String value of the metadata") |
1606 .SetHttpHeader("If-Match", "Revision of the metadata, if this is not the first time this metadata is set."); | |
1529 return; | 1607 return; |
1530 } | 1608 } |
1531 | 1609 |
1532 CheckValidResourceType(call); | 1610 CheckValidResourceType(call); |
1533 | 1611 |
1538 std::string value; | 1616 std::string value; |
1539 call.BodyToString(value); | 1617 call.BodyToString(value); |
1540 | 1618 |
1541 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata | 1619 if (IsUserMetadata(metadata)) // It is forbidden to modify internal metadata |
1542 { | 1620 { |
1543 // It is forbidden to modify internal metadata | 1621 int64_t oldRevision; |
1544 OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); | 1622 bool hasOldRevision = GetRevisionHeader(oldRevision, call, "if-match"); |
1623 | |
1624 if (!hasOldRevision) | |
1625 { | |
1626 OrthancConfiguration::ReaderLock lock; | |
1627 if (lock.GetConfiguration().GetBooleanParameter(CHECK_REVISIONS, false)) | |
1628 { | |
1629 // "StatelessDatabaseOperations::SetMetadata()" will ignore | |
1630 // the actual value of "oldRevision" if the metadata is | |
1631 // inexistent as expected | |
1632 hasOldRevision = true; | |
1633 oldRevision = -1; // dummy value | |
1634 } | |
1635 } | |
1636 | |
1637 int64_t newRevision; | |
1638 OrthancRestApi::GetIndex(call).SetMetadata(newRevision, publicId, metadata, value, hasOldRevision, oldRevision); | |
1639 | |
1640 call.GetOutput().GetLowLevelOutput(). | |
1641 AddHeader("ETag", "\"" + boost::lexical_cast<std::string>(newRevision) + "\""); // New in Orthanc 1.9.2 | |
1642 | |
1545 call.GetOutput().AnswerBuffer("", MimeType_PlainText); | 1643 call.GetOutput().AnswerBuffer("", MimeType_PlainText); |
1546 } | 1644 } |
1547 else | 1645 else |
1548 { | 1646 { |
1549 call.GetOutput().SignalError(HttpStatus_403_Forbidden); | 1647 call.GetOutput().SignalError(HttpStatus_403_Forbidden); |