Mercurial > hg > orthanc
diff OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp @ 5834:79a497908b04 attach-custom-data tip
merged find-refactoring -> attach-custom-data
author | Alain Mazy <am@orthanc.team> |
---|---|
date | Wed, 09 Oct 2024 11:06:20 +0200 |
parents | ca5622c27d6c dd2af8692cbc |
children |
line wrap: on
line diff
--- a/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Oct 02 11:41:01 2024 +0200 +++ b/OrthancServer/Sources/OrthancRestApi/OrthancRestResources.cpp Wed Oct 09 11:06:20 2024 +0200 @@ -41,6 +41,7 @@ #include "../OrthancConfiguration.h" #include "../Search/DatabaseLookup.h" +#include "../Search/DatabaseMetadataConstraint.h" #include "../ServerContext.h" #include "../ServerToolbox.h" #include "../SliceOrdering.h" @@ -136,7 +137,14 @@ DicomToJsonFormat format, bool retrieveMetadata) { - ResourceFinder finder(level, true /* expand */); + ResponseContentFlags responseContent = ResponseContentFlags_ExpandTrue; + + if (retrieveMetadata) + { + responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | ResponseContentFlags_Metadata); + } + + ResourceFinder finder(level, responseContent); finder.SetOrthancId(level, identifier); finder.SetRetrieveMetadata(retrieveMetadata); @@ -257,7 +265,7 @@ std::set<DicomTag> requestedTags; OrthancRestApi::GetRequestedTags(requestedTags, call); - ResourceFinder finder(resourceType, expand); + ResourceFinder finder(resourceType, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID)); finder.AddRequestedTags(requestedTags); if (call.HasArgument("limit") || @@ -363,7 +371,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(resourceType, true /* expand */); + ResourceFinder finder(resourceType, ResponseContentFlags_ExpandTrue); finder.AddRequestedTags(requestedTags); finder.SetOrthancId(resourceType, call.GetUriComponent("id", "")); @@ -3250,6 +3258,15 @@ static const char* const KEY_SINCE = "Since"; static const char* const KEY_LABELS = "Labels"; // New in Orthanc 1.12.0 static const char* const KEY_LABELS_CONSTRAINT = "LabelsConstraint"; // New in Orthanc 1.12.0 + static const char* const KEY_ORDER_BY = "OrderBy"; // New in Orthanc 1.12.5 + static const char* const KEY_ORDER_BY_KEY = "Key"; // New in Orthanc 1.12.5 + static const char* const KEY_ORDER_BY_TYPE = "Type"; // New in Orthanc 1.12.5 + static const char* const KEY_ORDER_BY_DIRECTION = "Direction"; // New in Orthanc 1.12.5 + static const char* const KEY_PARENT_PATIENT = "ParentPatient"; // New in Orthanc 1.12.5 + static const char* const KEY_PARENT_STUDY = "ParentStudy"; // New in Orthanc 1.12.5 + static const char* const KEY_PARENT_SERIES = "ParentSeries"; // New in Orthanc 1.12.5 + static const char* const KEY_QUERY_METADATA = "QueryMetadata"; // New in Orthanc 1.12.5 + static const char* const KEY_RESPONSE_CONTENT = "ResponseContent"; // New in Orthanc 1.12.5 if (call.IsDocumentation()) { @@ -3283,6 +3300,20 @@ "List of strings specifying which labels to look for in the resources (new in Orthanc 1.12.0)", true) .SetRequestField(KEY_LABELS_CONSTRAINT, RestApiCallDocumentation::Type_String, "Constraint on the labels, can be `All`, `Any`, or `None` (defaults to `All`, new in Orthanc 1.12.0)", true) + .SetRequestField(KEY_ORDER_BY, RestApiCallDocumentation::Type_JsonListOfObjects, + "Array of associative arrays containing the requested ordering (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_PARENT_PATIENT, RestApiCallDocumentation::Type_String, + "Limit the reported resources to descendants of this patient (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_PARENT_STUDY, RestApiCallDocumentation::Type_String, + "Limit the reported resources to descendants of this study (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_PARENT_SERIES, RestApiCallDocumentation::Type_String, + "Limit the reported resources to descendants of this series (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_QUERY_METADATA, RestApiCallDocumentation::Type_JsonObject, + "Associative array containing the filter on the values of the metadata (new in Orthanc 1.12.5)", true) + .SetRequestField(KEY_RESPONSE_CONTENT, RestApiCallDocumentation::Type_JsonListOfStrings, + "Defines the content of response for each returned resource. Allowed values are `MainDicomTags`, " + "`Metadata`, `Children`, `Parent`, `Labels`, `Status`, `IsStable`, `Attachments`. " + "(new in Orthanc 1.12.5)", true) .AddAnswerType(MimeType_Json, "JSON array containing either the Orthanc identifiers, or detailed information " "about the reported resources (if `Expand` argument is `true`)"); return; @@ -3333,6 +3364,12 @@ throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_REQUESTED_TAGS) + "\" must be an array"); } + else if (request.isMember(KEY_RESPONSE_CONTENT) && + request[KEY_RESPONSE_CONTENT].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_RESPONSE_CONTENT) + "\" must be an array"); + } else if (request.isMember(KEY_LABELS) && request[KEY_LABELS].type() != Json::arrayValue) { @@ -3345,21 +3382,61 @@ throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_LABELS_CONSTRAINT) + "\" must be an array of strings"); } + else if (request.isMember(KEY_ORDER_BY) && + request[KEY_ORDER_BY].type() != Json::arrayValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_ORDER_BY) + "\" must be an array"); + } + else if (request.isMember(KEY_QUERY_METADATA) && + request[KEY_QUERY_METADATA].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_QUERY_METADATA) + "\" must be an JSON object"); + } + else if (request.isMember(KEY_PARENT_PATIENT) && + request[KEY_PARENT_PATIENT].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_PARENT_PATIENT) + "\" must be a string"); + } + else if (request.isMember(KEY_PARENT_STUDY) && + request[KEY_PARENT_STUDY].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_PARENT_STUDY) + "\" must be a string"); + } + else if (request.isMember(KEY_PARENT_SERIES) && + request[KEY_PARENT_SERIES].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Field \"" + std::string(KEY_PARENT_SERIES) + "\" must be a string"); + } else if (true) { /** * EXPERIMENTAL VERSION **/ - bool expand = false; - if (request.isMember(KEY_EXPAND)) + ResponseContentFlags responseContent = ResponseContentFlags_ID; + + if (request.isMember(KEY_RESPONSE_CONTENT)) { - expand = request[KEY_EXPAND].asBool(); + responseContent = ResponseContentFlags_Default; + + for (Json::ArrayIndex i = 0; i < request[KEY_RESPONSE_CONTENT].size(); ++i) + { + responseContent = static_cast<ResponseContentFlags>(static_cast<uint32_t>(responseContent) | StringToResponseContent(request[KEY_RESPONSE_CONTENT][i].asString())); + } + } + else if (request.isMember(KEY_EXPAND) && request[KEY_EXPAND].asBool()) + { + responseContent = ResponseContentFlags_ExpandTrue; } const ResourceType level = StringToResourceType(request[KEY_LEVEL].asCString()); - ResourceFinder finder(level, expand); + ResourceFinder finder(level, responseContent); finder.SetDatabaseLimits(context.GetDatabaseLimits(level)); const DicomToJsonFormat format = OrthancRestApi::GetDicomFormat(request, DicomToJsonFormat_Human); @@ -3399,30 +3476,66 @@ caseSensitive = request[KEY_CASE_SENSITIVE].asBool(); } - DatabaseLookup query; - - Json::Value::Members members = request[KEY_QUERY].getMemberNames(); - for (size_t i = 0; i < members.size(); i++) - { - if (request[KEY_QUERY][members[i]].type() != Json::stringValue) + { // DICOM Tag query + DatabaseLookup dicomTagLookup; + + Json::Value::Members members = request[KEY_QUERY].getMemberNames(); + for (size_t i = 0; i < members.size(); i++) { - throw OrthancException(ErrorCode_BadRequest, - "Tag \"" + members[i] + "\" must be associated with a string"); + if (request[KEY_QUERY][members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Tag \"" + members[i] + "\" must be associated with a string"); + } + + const std::string value = request[KEY_QUERY][members[i]].asString(); + + if (!value.empty()) + { + // An empty string corresponds to an universal constraint, + // so we ignore it. This mimics the behavior of class + // "OrthancFindRequestHandler" + dicomTagLookup.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), + value, caseSensitive, true); + } } - const std::string value = request[KEY_QUERY][members[i]].asString(); - - if (!value.empty()) + finder.SetDatabaseLookup(dicomTagLookup); + } + + { // Metadata query + Json::Value::Members members = request[KEY_QUERY_METADATA].getMemberNames(); + for (size_t i = 0; i < members.size(); i++) { - // An empty string corresponds to an universal constraint, - // so we ignore it. This mimics the behavior of class - // "OrthancFindRequestHandler" - query.AddRestConstraint(FromDcmtkBridge::ParseTag(members[i]), - value, caseSensitive, true); + if (request[KEY_QUERY_METADATA][members[i]].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, + "Tag \"" + members[i] + "\" must be associated with a string"); + } + MetadataType metadata = StringToMetadata(members[i]); + + const std::string value = request[KEY_QUERY_METADATA][members[i]].asString(); + + if (!value.empty()) + { + if (value.find('\\') != std::string::npos) + { + std::vector<std::string> items; + Toolbox::TokenizeString(items, value, '\\'); + + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_List, items, caseSensitive)); + } + else if (value.find('*') != std::string::npos || value.find('?') != std::string::npos) + { + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Wildcard, value, caseSensitive)); + } + else + { + finder.AddMetadataConstraint(new DatabaseMetadataConstraint(metadata, ConstraintType_Equal, value, caseSensitive)); + } + } } } - - finder.SetDatabaseLookup(query); } if (request.isMember(KEY_REQUESTED_TAGS)) @@ -3470,6 +3583,82 @@ } } + if (request.isMember(KEY_PARENT_PATIENT)) // New in Orthanc 1.12.5 + { + finder.SetOrthancId(ResourceType_Patient, request[KEY_PARENT_PATIENT].asString()); + } + else if (request.isMember(KEY_PARENT_STUDY)) + { + finder.SetOrthancId(ResourceType_Study, request[KEY_PARENT_STUDY].asString()); + } + else if (request.isMember(KEY_PARENT_SERIES)) + { + finder.SetOrthancId(ResourceType_Series, request[KEY_PARENT_SERIES].asString()); + } + + if (request.isMember(KEY_ORDER_BY)) // New in Orthanc 1.12.5 + { + for (Json::Value::ArrayIndex i = 0; i < request[KEY_ORDER_BY].size(); i++) + { + if (request[KEY_ORDER_BY][i].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY) + "\" must contain objects"); + } + else + { + const Json::Value& order = request[KEY_ORDER_BY][i]; + FindRequest::OrderingDirection direction; + std::string directionString; + std::string typeString; + + if (!order.isMember(KEY_ORDER_BY_KEY) || order[KEY_ORDER_BY_KEY].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_KEY) + "\" must be a string"); + } + + if (!order.isMember(KEY_ORDER_BY_DIRECTION) || order[KEY_ORDER_BY_DIRECTION].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\""); + } + + Toolbox::ToLowerCase(directionString, order[KEY_ORDER_BY_DIRECTION].asString()); + if (directionString == "asc") + { + direction = FindRequest::OrderingDirection_Ascending; + } + else if (directionString == "desc") + { + direction = FindRequest::OrderingDirection_Descending; + } + else + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_DIRECTION) + "\" must be \"ASC\" or \"DESC\""); + } + + if (!order.isMember(KEY_ORDER_BY_TYPE) || order[KEY_ORDER_BY_TYPE].type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\""); + } + + Toolbox::ToLowerCase(typeString, order[KEY_ORDER_BY_TYPE].asString()); + if (typeString == "dicomtag") + { + DicomTag tag = FromDcmtkBridge::ParseTag(order[KEY_ORDER_BY_KEY].asString()); + finder.AddOrdering(tag, direction); + } + else if (typeString == "metadata") + { + MetadataType metadata = StringToMetadata(order[KEY_ORDER_BY_KEY].asString()); + finder.AddOrdering(metadata, direction); + } + else + { + throw OrthancException(ErrorCode_BadRequest, "Field \"" + std::string(KEY_ORDER_BY_TYPE) + "\" must be \"DicomTag\" or \"Metadata\""); + } + } + } + } + Json::Value answer; finder.Execute(answer, context, format, false /* no "Metadata" field */); call.GetOutput().AnswerJson(answer); @@ -3637,7 +3826,7 @@ * EXPERIMENTAL VERSION **/ - ResourceFinder finder(end, expand); + ResourceFinder finder(end, (expand ? ResponseContentFlags_ExpandTrue : ResponseContentFlags_ID)); finder.SetOrthancId(start, call.GetUriComponent("id", "")); finder.AddRequestedTags(requestedTags);