Mercurial > hg > orthanc
view OrthancServer/OrthancRestApi/OrthancRestResources.cpp @ 959:bd5659f2a50a
fix possible race condition
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Wed, 25 Jun 2014 21:53:50 +0200 |
parents | 1fbe89dc18b5 |
children | abac5c83134f |
line wrap: on
line source
/** * Orthanc - A Lightweight, RESTful DICOM Store * Copyright (C) 2012-2014 Medical Physics Department, CHU of Liege, * Belgium * * This program is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * In addition, as a special exception, the copyright holders of this * program give permission to link the code of its release with the * OpenSSL project's "OpenSSL" library (or with modified versions of it * that use the same license as the "OpenSSL" library), and distribute * the linked executables. You must obey the GNU General Public License * in all respects for all of the code used other than "OpenSSL". If you * modify file(s) with this exception, you may extend this exception to * your version of the file(s), but you are not obligated to do so. If * you do not wish to do so, delete this exception statement from your * version. If you delete this exception statement from all source files * in the program, then also delete it here. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. **/ #include "../PrecompiledHeadersServer.h" #include "OrthancRestApi.h" #include "../ServerToolbox.h" #include "../FromDcmtkBridge.h" #include <glog/logging.h> namespace Orthanc { // List all the patients, studies, series or instances ---------------------- template <enum ResourceType resourceType> static void ListResources(RestApi::GetCall& call) { Json::Value result; OrthancRestApi::GetIndex(call).GetAllUuids(result, resourceType); call.GetOutput().AnswerJson(result); } template <enum ResourceType resourceType> static void GetSingleResource(RestApi::GetCall& call) { Json::Value result; if (OrthancRestApi::GetIndex(call).LookupResource(result, call.GetUriComponent("id", ""), resourceType)) { call.GetOutput().AnswerJson(result); } } template <enum ResourceType resourceType> static void DeleteSingleResource(RestApi::DeleteCall& call) { Json::Value result; if (OrthancRestApi::GetIndex(call).DeleteResource(result, call.GetUriComponent("id", ""), resourceType)) { call.GetOutput().AnswerJson(result); } } // Get information about a single patient ----------------------------------- static void IsProtectedPatient(RestApi::GetCall& call) { std::string publicId = call.GetUriComponent("id", ""); bool isProtected = OrthancRestApi::GetIndex(call).IsProtectedPatient(publicId); call.GetOutput().AnswerBuffer(isProtected ? "1" : "0", "text/plain"); } static void SetPatientProtection(RestApi::PutCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::string s = Toolbox::StripSpaces(call.GetPutBody()); if (s == "0") { context.GetIndex().SetProtectedPatient(publicId, false); call.GetOutput().AnswerBuffer("", "text/plain"); } else if (s == "1") { context.GetIndex().SetProtectedPatient(publicId, true); call.GetOutput().AnswerBuffer("", "text/plain"); } else { // Bad request } } // Get information about a single instance ---------------------------------- static void GetInstanceFile(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); context.AnswerDicomFile(call.GetOutput(), publicId, FileContentType_Dicom); } static void ExportInstanceFile(RestApi::PostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); std::string dicom; context.ReadFile(dicom, publicId, FileContentType_Dicom); Toolbox::WriteFile(dicom, call.GetPostBody()); call.GetOutput().AnswerBuffer("{}", "application/json"); } template <bool simplify> static void GetInstanceTags(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); Json::Value full; context.ReadJson(full, publicId); if (simplify) { Json::Value simplified; SimplifyTags(simplified, full); call.GetOutput().AnswerJson(simplified); } else { call.GetOutput().AnswerJson(full); } } static void ListFrames(RestApi::GetCall& call) { Json::Value instance; if (OrthancRestApi::GetIndex(call).LookupResource(instance, call.GetUriComponent("id", ""), ResourceType_Instance)) { unsigned int numberOfFrames = 1; try { Json::Value tmp = instance["MainDicomTags"]["NumberOfFrames"]; numberOfFrames = boost::lexical_cast<unsigned int>(tmp.asString()); } catch (...) { } Json::Value result = Json::arrayValue; for (unsigned int i = 0; i < numberOfFrames; i++) { result.append(i); } call.GetOutput().AnswerJson(result); } } template <enum ImageExtractionMode mode> static void GetImage(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string frameId = call.GetUriComponent("frame", "0"); unsigned int frame; try { frame = boost::lexical_cast<unsigned int>(frameId); } catch (boost::bad_lexical_cast) { return; } std::string publicId = call.GetUriComponent("id", ""); std::string dicomContent, png; context.ReadFile(dicomContent, publicId, FileContentType_Dicom); ParsedDicomFile dicom(dicomContent); try { dicom.ExtractPngImage(png, frame, mode); call.GetOutput().AnswerBuffer(png, "image/png"); } catch (OrthancException& e) { if (e.GetErrorCode() == ErrorCode_ParameterOutOfRange) { // The frame number is out of the range for this DICOM // instance, the resource is not existent } else { std::string root = ""; for (size_t i = 1; i < call.GetFullUri().size(); i++) { root += "../"; } call.GetOutput().Redirect(root + "app/images/unsupported.png"); } } } static void GetMatlabImage(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string frameId = call.GetUriComponent("frame", "0"); unsigned int frame; try { frame = boost::lexical_cast<unsigned int>(frameId); } catch (boost::bad_lexical_cast) { return; } std::string publicId = call.GetUriComponent("id", ""); std::string dicomContent; context.ReadFile(dicomContent, publicId, FileContentType_Dicom); ParsedDicomFile dicom(dicomContent); ImageBuffer buffer; dicom.ExtractImage(buffer, frame); ImageAccessor accessor(buffer.GetConstAccessor()); std::string result; accessor.ToMatlabString(result); call.GetOutput().AnswerBuffer(result, "text/plain"); } static void GetResourceStatistics(RestApi::GetCall& call) { std::string publicId = call.GetUriComponent("id", ""); Json::Value result; OrthancRestApi::GetIndex(call).GetStatistics(result, publicId); call.GetOutput().AnswerJson(result); } // Handling of metadata ----------------------------------------------------- static void CheckValidResourceType(RestApi::Call& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); StringToResourceType(resourceType.c_str()); } static void ListMetadata(RestApi::GetCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::list<MetadataType> metadata; OrthancRestApi::GetIndex(call).ListAvailableMetadata(metadata, publicId); Json::Value result = Json::arrayValue; for (std::list<MetadataType>::const_iterator it = metadata.begin(); it != metadata.end(); ++it) { result.append(EnumerationToString(*it)); } call.GetOutput().AnswerJson(result); } static void GetMetadata(RestApi::GetCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); std::string value; if (OrthancRestApi::GetIndex(call).LookupMetadata(value, publicId, metadata)) { call.GetOutput().AnswerBuffer(value, "text/plain"); } } static void DeleteMetadata(RestApi::DeleteCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); if (metadata >= MetadataType_StartUser && metadata <= MetadataType_EndUser) { // It is forbidden to modify internal metadata OrthancRestApi::GetIndex(call).DeleteMetadata(publicId, metadata); call.GetOutput().AnswerBuffer("", "text/plain"); } } static void SetMetadata(RestApi::PutCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); MetadataType metadata = StringToMetadata(name); std::string value = call.GetPutBody(); if (metadata >= MetadataType_StartUser && metadata <= MetadataType_EndUser) { // It is forbidden to modify internal metadata OrthancRestApi::GetIndex(call).SetMetadata(publicId, metadata, value); call.GetOutput().AnswerBuffer("", "text/plain"); } } // Handling of attached files ----------------------------------------------- static void ListAttachments(RestApi::GetCall& call) { std::string resourceType = call.GetUriComponent("resourceType", ""); std::string publicId = call.GetUriComponent("id", ""); std::list<FileContentType> attachments; OrthancRestApi::GetIndex(call).ListAvailableAttachments(attachments, publicId, StringToResourceType(resourceType.c_str())); Json::Value result = Json::arrayValue; for (std::list<FileContentType>::const_iterator it = attachments.begin(); it != attachments.end(); ++it) { result.append(EnumerationToString(*it)); } call.GetOutput().AnswerJson(result); } static bool GetAttachmentInfo(FileInfo& info, RestApi::Call& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); return OrthancRestApi::GetIndex(call).LookupAttachment(info, publicId, contentType); } static void GetAttachmentOperations(RestApi::GetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { Json::Value operations = Json::arrayValue; operations.append("compressed-data"); if (info.GetCompressedMD5() != "") { operations.append("compressed-md5"); } operations.append("compressed-size"); operations.append("data"); if (info.GetUncompressedMD5() != "") { operations.append("md5"); } operations.append("size"); if (info.GetCompressedMD5() != "" && info.GetUncompressedMD5() != "") { operations.append("verify-md5"); } call.GetOutput().AnswerJson(operations); } } template <int uncompress> static void GetAttachmentData(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); std::string content; context.ReadFile(content, publicId, StringToContentType(name), (uncompress == 1)); call.GetOutput().AnswerBuffer(content, "application/octet-stream"); } static void GetAttachmentSize(RestApi::GetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedSize()), "text/plain"); } } static void GetAttachmentCompressedSize(RestApi::GetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call)) { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedSize()), "text/plain"); } } static void GetAttachmentMD5(RestApi::GetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && info.GetUncompressedMD5() != "") { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetUncompressedMD5()), "text/plain"); } } static void GetAttachmentCompressedMD5(RestApi::GetCall& call) { FileInfo info; if (GetAttachmentInfo(info, call) && info.GetCompressedMD5() != "") { call.GetOutput().AnswerBuffer(boost::lexical_cast<std::string>(info.GetCompressedMD5()), "text/plain"); } } static void VerifyAttachment(RestApi::PostCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileInfo info; if (!GetAttachmentInfo(info, call) || info.GetCompressedMD5() == "" || info.GetUncompressedMD5() == "") { // Inexistent resource, or no MD5 available return; } bool ok = false; // First check whether the compressed data is correctly stored in the disk std::string data; context.ReadFile(data, publicId, StringToContentType(name), false); std::string actualMD5; Toolbox::ComputeMD5(actualMD5, data); if (actualMD5 == info.GetCompressedMD5()) { // The compressed data is OK. If a compression algorithm was // applied to it, now check the MD5 of the uncompressed data. if (info.GetCompressionType() == CompressionType_None) { ok = true; } else { context.ReadFile(data, publicId, StringToContentType(name), true); Toolbox::ComputeMD5(actualMD5, data); ok = (actualMD5 == info.GetUncompressedMD5()); } } if (ok) { LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has the right MD5"; call.GetOutput().AnswerBuffer("{}", "application/json"); } else { LOG(INFO) << "The attachment " << name << " of resource " << publicId << " has bad MD5!"; } } static void UploadAttachment(RestApi::PutCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); const void* data = call.GetPutBody().size() ? &call.GetPutBody()[0] : NULL; FileContentType contentType = StringToContentType(name); if (contentType >= FileContentType_StartUser && // It is forbidden to modify internal attachments contentType <= FileContentType_EndUser && context.AddAttachment(publicId, StringToContentType(name), data, call.GetPutBody().size())) { call.GetOutput().AnswerBuffer("{}", "application/json"); } } static void DeleteAttachment(RestApi::DeleteCall& call) { CheckValidResourceType(call); std::string publicId = call.GetUriComponent("id", ""); std::string name = call.GetUriComponent("name", ""); FileContentType contentType = StringToContentType(name); if (contentType >= FileContentType_StartUser && contentType <= FileContentType_EndUser) { // It is forbidden to delete internal attachments OrthancRestApi::GetIndex(call).DeleteAttachment(publicId, contentType); call.GetOutput().AnswerBuffer("{}", "application/json"); } } // Raw access to the DICOM tags of an instance ------------------------------ static void GetRawContent(RestApi::GetCall& call) { std::string id = call.GetUriComponent("id", ""); ServerContext::DicomCacheLocker locker(OrthancRestApi::GetContext(call), id); locker.GetDicom().SendPathValue(call.GetOutput(), call.GetTrailingUri()); } static void GetSharedTags(RestApi::GetCall& call) { ServerContext& context = OrthancRestApi::GetContext(call); std::string publicId = call.GetUriComponent("id", ""); // Retrieve all the instances of this patient/study/series typedef std::list<std::string> Instances; Instances instances; context.GetIndex().GetChildInstances(instances, publicId); // (*) // Loop over the instances bool isFirst = true; Json::Value shared = Json::objectValue; for (Instances::const_iterator it = instances.begin(); it != instances.end(); it++) { // Get the tags of the current instance, in the simplified format Json::Value full, simplified; try { context.ReadJson(full, *it); } catch (OrthancException&) { // Race condition: This instance has been removed since // (*). Ignore this instance. continue; } SimplifyTags(simplified, full); if (simplified.type() != Json::objectValue) { return; // Error } // Only keep the tags that are mapped to a string Json::Value::Members members = simplified.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { Json::ValueType type = simplified[members[i]].type(); if (type != Json::stringValue) { simplified.removeMember(members[i]); } } if (isFirst) { // This is the first instance, keep its tags as such shared = simplified; isFirst = false; } else { // Loop over all the members of the shared tags extracted so // far. If the value of one of these tags does not match its // value in the current instance, remove it. members = shared.getMemberNames(); for (size_t i = 0; i < members.size(); i++) { if (!simplified.isMember(members[i]) || simplified[members[i]].asString() != shared[members[i]].asString()) { shared.removeMember(members[i]); } } } } // Success: Send the value of the shared tags call.GetOutput().AnswerJson(shared); } void OrthancRestApi::RegisterResources() { Register("/instances", ListResources<ResourceType_Instance>); Register("/patients", ListResources<ResourceType_Patient>); Register("/series", ListResources<ResourceType_Series>); Register("/studies", ListResources<ResourceType_Study>); Register("/instances/{id}", DeleteSingleResource<ResourceType_Instance>); Register("/instances/{id}", GetSingleResource<ResourceType_Instance>); Register("/patients/{id}", DeleteSingleResource<ResourceType_Patient>); Register("/patients/{id}", GetSingleResource<ResourceType_Patient>); Register("/series/{id}", DeleteSingleResource<ResourceType_Series>); Register("/series/{id}", GetSingleResource<ResourceType_Series>); Register("/studies/{id}", DeleteSingleResource<ResourceType_Study>); Register("/studies/{id}", GetSingleResource<ResourceType_Study>); Register("/instances/{id}/statistics", GetResourceStatistics); Register("/patients/{id}/statistics", GetResourceStatistics); Register("/studies/{id}/statistics", GetResourceStatistics); Register("/series/{id}/statistics", GetResourceStatistics); Register("/patients/{id}/shared-tags", GetSharedTags); Register("/series/{id}/shared-tags", GetSharedTags); Register("/studies/{id}/shared-tags", GetSharedTags); Register("/instances/{id}/file", GetInstanceFile); Register("/instances/{id}/export", ExportInstanceFile); Register("/instances/{id}/tags", GetInstanceTags<false>); Register("/instances/{id}/simplified-tags", GetInstanceTags<true>); Register("/instances/{id}/frames", ListFrames); Register("/instances/{id}/frames/{frame}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/frames/{frame}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/frames/{frame}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/frames/{frame}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/frames/{frame}/matlab", GetMatlabImage); Register("/instances/{id}/preview", GetImage<ImageExtractionMode_Preview>); Register("/instances/{id}/image-uint8", GetImage<ImageExtractionMode_UInt8>); Register("/instances/{id}/image-uint16", GetImage<ImageExtractionMode_UInt16>); Register("/instances/{id}/image-int16", GetImage<ImageExtractionMode_Int16>); Register("/instances/{id}/matlab", GetMatlabImage); Register("/patients/{id}/protected", IsProtectedPatient); Register("/patients/{id}/protected", SetPatientProtection); Register("/{resourceType}/{id}/metadata", ListMetadata); Register("/{resourceType}/{id}/metadata/{name}", DeleteMetadata); Register("/{resourceType}/{id}/metadata/{name}", GetMetadata); Register("/{resourceType}/{id}/metadata/{name}", SetMetadata); Register("/{resourceType}/{id}/attachments", ListAttachments); Register("/{resourceType}/{id}/attachments/{name}", DeleteAttachment); Register("/{resourceType}/{id}/attachments/{name}", GetAttachmentOperations); Register("/{resourceType}/{id}/attachments/{name}/compressed-data", GetAttachmentData<0>); Register("/{resourceType}/{id}/attachments/{name}/compressed-md5", GetAttachmentCompressedMD5); Register("/{resourceType}/{id}/attachments/{name}/compressed-size", GetAttachmentCompressedSize); Register("/{resourceType}/{id}/attachments/{name}/data", GetAttachmentData<1>); Register("/{resourceType}/{id}/attachments/{name}/md5", GetAttachmentMD5); Register("/{resourceType}/{id}/attachments/{name}/size", GetAttachmentSize); Register("/{resourceType}/{id}/attachments/{name}/verify-md5", VerifyAttachment); Register("/{resourceType}/{id}/attachments/{name}", UploadAttachment); Register("/instances/{id}/content/*", GetRawContent); } }