# HG changeset patch # User Sebastien Jodogne # Date 1538150384 -7200 # Node ID 218e2c864d1d50ae8ca12885740fe590c6b0a93e # Parent 99863d6245b2adf316e38eca8c3276b5492be9cd serialization of SplitStudyJob diff -r 99863d6245b2 -r 218e2c864d1d Core/SerializationToolbox.cpp --- a/Core/SerializationToolbox.cpp Fri Sep 28 16:48:43 2018 +0200 +++ b/Core/SerializationToolbox.cpp Fri Sep 28 17:59:44 2018 +0200 @@ -196,6 +196,75 @@ } + void ReadMapOfStrings(std::map& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& source = value[field.c_str()]; + + target.clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tmp = source[members[i]]; + + if (tmp.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[members[i]] = tmp.asString(); + } + } + } + + + void ReadMapOfTags(std::map& target, + const Json::Value& value, + const std::string& field) + { + if (value.type() != Json::objectValue || + !value.isMember(field.c_str()) || + value[field.c_str()].type() != Json::objectValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + const Json::Value& source = value[field.c_str()]; + + target.clear(); + + Json::Value::Members members = source.getMemberNames(); + + for (size_t i = 0; i < members.size(); i++) + { + const Json::Value& tmp = source[members[i]]; + + DicomTag tag(0, 0); + + if (!DicomTag::ParseHexadecimal(tag, members[i].c_str()) || + tmp.type() != Json::stringValue) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + else + { + target[tag] = tmp.asString(); + } + } + } + + void WriteArrayOfStrings(Json::Value& target, const std::vector& values, const std::string& field) @@ -258,5 +327,49 @@ value.append(it->Format()); } } + + + void WriteMapOfStrings(Json::Value& target, + const std::map& values, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::objectValue; + + for (std::map::const_iterator + it = values.begin(); it != values.end(); ++it) + { + value[it->first] = it->second; + } + } + + + void WriteMapOfTags(Json::Value& target, + const std::map& values, + const std::string& field) + { + if (target.type() != Json::objectValue || + target.isMember(field.c_str())) + { + throw OrthancException(ErrorCode_BadFileFormat); + } + + Json::Value& value = target[field]; + + value = Json::objectValue; + + for (std::map::const_iterator + it = values.begin(); it != values.end(); ++it) + { + value[it->first.Format()] = it->second; + } + } } } diff -r 99863d6245b2 -r 218e2c864d1d Core/SerializationToolbox.h --- a/Core/SerializationToolbox.h Fri Sep 28 16:48:43 2018 +0200 +++ b/Core/SerializationToolbox.h Fri Sep 28 17:59:44 2018 +0200 @@ -37,6 +37,7 @@ #include #include +#include namespace Orthanc { @@ -70,6 +71,14 @@ const Json::Value& value, const std::string& field); + void ReadMapOfStrings(std::map& values, + const Json::Value& target, + const std::string& field); + + void ReadMapOfTags(std::map& values, + const Json::Value& target, + const std::string& field); + void WriteArrayOfStrings(Json::Value& target, const std::vector& values, const std::string& field); @@ -81,5 +90,13 @@ void WriteSetOfTags(Json::Value& target, const std::set& tags, const std::string& field); + + void WriteMapOfStrings(Json::Value& target, + const std::map& values, + const std::string& field); + + void WriteMapOfTags(Json::Value& target, + const std::map& values, + const std::string& field); } } diff -r 99863d6245b2 -r 218e2c864d1d OrthancServer/ServerJobs/OrthancJobUnserializer.cpp --- a/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Fri Sep 28 16:48:43 2018 +0200 +++ b/OrthancServer/ServerJobs/OrthancJobUnserializer.cpp Fri Sep 28 17:59:44 2018 +0200 @@ -48,6 +48,7 @@ #include "DicomModalityStoreJob.h" #include "OrthancPeerStoreJob.h" #include "ResourceModificationJob.h" +#include "SplitStudyJob.h" namespace Orthanc { @@ -78,6 +79,10 @@ { return new ResourceModificationJob(context_, source); } + else if (type == "SplitStudy") + { + return new SplitStudyJob(context_, source); + } else { return GenericJobUnserializer::UnserializeJob(source); diff -r 99863d6245b2 -r 218e2c864d1d OrthancServer/ServerJobs/SplitStudyJob.cpp --- a/OrthancServer/ServerJobs/SplitStudyJob.cpp Fri Sep 28 16:48:43 2018 +0200 +++ b/OrthancServer/ServerJobs/SplitStudyJob.cpp Fri Sep 28 17:59:44 2018 +0200 @@ -33,8 +33,9 @@ #include "SplitStudyJob.h" +#include "../../Core/DicomParsing/FromDcmtkBridge.h" #include "../../Core/Logging.h" -#include "../../Core/DicomParsing/FromDcmtkBridge.h" +#include "../../Core/SerializationToolbox.h" namespace Orthanc { @@ -50,36 +51,14 @@ } - void SplitStudyJob::Setup(const std::string& sourceStudy) + void SplitStudyJob::Setup() { SetPermissive(false); - keepSource_ = false; - sourceStudy_ = sourceStudy; - targetStudyUid_ = FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study); - DicomTag::AddTagsForModule(allowedTags_, DicomModule_Patient); DicomTag::AddTagsForModule(allowedTags_, DicomModule_Study); allowedTags_.erase(DICOM_TAG_STUDY_INSTANCE_UID); allowedTags_.erase(DICOM_TAG_SERIES_INSTANCE_UID); - - ResourceType type; - - if (!context_.GetIndex().LookupResourceType(type, sourceStudy) || - type != ResourceType_Study) - { - LOG(ERROR) << "Cannot split unknown study: " << sourceStudy; - throw OrthancException(ErrorCode_UnknownResource); - } - - std::list children; - context_.GetIndex().GetChildren(children, sourceStudy); - - for (std::list::const_iterator - it = children.begin(); it != children.end(); ++it) - { - sourceSeries_.insert(*it); - } } @@ -132,7 +111,7 @@ * Apply user-specified modifications **/ - for (Removals::const_iterator it = removals_.begin(); + for (std::set::const_iterator it = removals_.begin(); it != removals_.end(); ++it) { modified->Remove(*it); @@ -192,22 +171,33 @@ SplitStudyJob::SplitStudyJob(ServerContext& context, const std::string& sourceStudy) : SetOfInstancesJob(true /* with trailing step */), - context_(context) + context_(context), + keepSource_(false), + sourceStudy_(sourceStudy), + targetStudyUid_(FromDcmtkBridge::GenerateUniqueIdentifier(ResourceType_Study)) { - Setup(sourceStudy); + Setup(); + + ResourceType type; + + if (!context_.GetIndex().LookupResourceType(type, sourceStudy) || + type != ResourceType_Study) + { + LOG(ERROR) << "Cannot split unknown study: " << sourceStudy; + throw OrthancException(ErrorCode_UnknownResource); + } + + std::list children; + context_.GetIndex().GetChildren(children, sourceStudy); + + for (std::list::const_iterator + it = children.begin(); it != children.end(); ++it) + { + sourceSeries_.insert(*it); + } } - SplitStudyJob::SplitStudyJob(ServerContext& context, - const Json::Value& serialized) : - SetOfInstancesJob(serialized), - context_(context) - { - //assert(HasTrailingStep()); - //Setup(); - } - - void SplitStudyJob::SetOrigin(const DicomInstanceOrigin& origin) { if (IsStarted()) @@ -300,10 +290,63 @@ value["TargetStudyUID"] = targetStudyUid_; } + + + static const char* SOURCE_STUDY = "SourceStudy"; + static const char* SOURCE_SERIES = "SourceSeries"; + static const char* KEEP_SOURCE = "KeepSource"; + static const char* TARGET_STUDY = "TargetStudy"; + static const char* TARGET_STUDY_UID = "TargetStudyUID"; + static const char* TARGET_SERIES = "TargetSeries"; + static const char* ORIGIN = "Origin"; + static const char* REPLACEMENTS = "Replacements"; + static const char* REMOVALS = "Removals"; + + + SplitStudyJob::SplitStudyJob(ServerContext& context, + const Json::Value& serialized) : + SetOfInstancesJob(serialized), // (*) + context_(context) + { + if (!HasTrailingStep()) + { + // Should have been set by (*) + throw OrthancException(ErrorCode_InternalError); + } + + Setup(); + + keepSource_ = SerializationToolbox::ReadBoolean(serialized, KEEP_SOURCE); + sourceStudy_ = SerializationToolbox::ReadString(serialized, SOURCE_STUDY); + SerializationToolbox::ReadSetOfStrings(sourceSeries_, serialized, SOURCE_SERIES); + targetStudy_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY); + targetStudyUid_ = SerializationToolbox::ReadString(serialized, TARGET_STUDY_UID); + SerializationToolbox::ReadMapOfStrings(targetSeries_, serialized, TARGET_SERIES); + origin_ = DicomInstanceOrigin(serialized[ORIGIN]); + SerializationToolbox::ReadMapOfTags(replacements_, serialized, REPLACEMENTS); + SerializationToolbox::ReadSetOfTags(removals_, serialized, REMOVALS); + } + - bool SplitStudyJob::Serialize(Json::Value& target) { - return true; + if (!SetOfInstancesJob::Serialize(target)) + { + return false; + } + else + { + target[KEEP_SOURCE] = keepSource_; + target[SOURCE_STUDY] = sourceStudy_; + SerializationToolbox::WriteSetOfStrings(target, sourceSeries_, SOURCE_SERIES); + target[TARGET_STUDY] = targetStudy_; + target[TARGET_STUDY_UID] = targetStudyUid_; + SerializationToolbox::WriteMapOfStrings(target, targetSeries_, TARGET_SERIES); + origin_.Serialize(target[ORIGIN]); + SerializationToolbox::WriteMapOfTags(target, replacements_, REPLACEMENTS); + SerializationToolbox::WriteSetOfTags(target, removals_, REMOVALS); + + return true; + } } } diff -r 99863d6245b2 -r 218e2c864d1d OrthancServer/ServerJobs/SplitStudyJob.h --- a/OrthancServer/ServerJobs/SplitStudyJob.h Fri Sep 28 16:48:43 2018 +0200 +++ b/OrthancServer/ServerJobs/SplitStudyJob.h Fri Sep 28 17:59:44 2018 +0200 @@ -44,24 +44,23 @@ private: typedef std::map SeriesUidMap; typedef std::map Replacements; - typedef std::set Removals; ServerContext& context_; + bool keepSource_; std::string sourceStudy_; std::set sourceSeries_; - bool keepSource_; std::string targetStudy_; std::string targetStudyUid_; SeriesUidMap targetSeries_; std::set allowedTags_; DicomInstanceOrigin origin_; Replacements replacements_; - Removals removals_; + std::set removals_; void CheckAllowedTag(const DicomTag& tag) const; - void Setup(const std::string& sourceStudy); + void Setup(); protected: virtual bool HandleInstance(const std::string& instance); @@ -75,9 +74,10 @@ SplitStudyJob(ServerContext& context, const Json::Value& serialized); - void SetOrigin(const DicomInstanceOrigin& origin); - - void SetOrigin(const RestApiCall& call); + const std::string& GetSourceStudy() const + { + return sourceStudy_; + } void AddSourceSeries(const std::string& series); @@ -88,18 +88,27 @@ void SetKeepSource(bool keep); - void Remove(const DicomTag& tag); - void Replace(const DicomTag& tag, const std::string& value); + void Remove(const DicomTag& tag); + + void SetOrigin(const DicomInstanceOrigin& origin); + + void SetOrigin(const RestApiCall& call); + + const DicomInstanceOrigin& GetOrigin() const + { + return origin_; + } + virtual void Stop(JobStopReason reason) { } virtual void GetJobType(std::string& target) { - target = "SplitStudyJob"; + target = "SplitStudy"; } virtual void GetPublicContent(Json::Value& value); diff -r 99863d6245b2 -r 218e2c864d1d UnitTestsSources/MultiThreadingTests.cpp --- a/UnitTestsSources/MultiThreadingTests.cpp Fri Sep 28 16:48:43 2018 +0200 +++ b/UnitTestsSources/MultiThreadingTests.cpp Fri Sep 28 17:59:44 2018 +0200 @@ -63,6 +63,7 @@ #include "../OrthancServer/ServerJobs/DicomModalityStoreJob.h" #include "../OrthancServer/ServerJobs/OrthancPeerStoreJob.h" #include "../OrthancServer/ServerJobs/ResourceModificationJob.h" +#include "../OrthancServer/ServerJobs/SplitStudyJob.h" using namespace Orthanc; @@ -1463,9 +1464,9 @@ TEST_F(OrthancJobsSerialization, Jobs) { - // ArchiveJob + Json::Value s; - Json::Value s; + // ArchiveJob { boost::shared_ptr tmp(new TemporaryFile); @@ -1557,6 +1558,43 @@ ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); } + + // SplitStudyJob + + std::string id; + ASSERT_TRUE(CreateInstance(id)); + + std::string study, series; + + { + ServerContext::DicomCacheLocker lock(GetContext(), id); + study = lock.GetDicom().GetHasher().HashStudy(); + series = lock.GetDicom().GetHasher().HashSeries(); + } + + { + ASSERT_THROW(SplitStudyJob(GetContext(), std::string("nope")), OrthancException); + + SplitStudyJob job(GetContext(), study); + job.SetKeepSource(true); + job.AddSourceSeries(series); + ASSERT_THROW(job.AddSourceSeries("nope"), OrthancException); + job.SetOrigin(DicomInstanceOrigin::FromLua()); + + ASSERT_TRUE(CheckIdempotentSetOfInstances(unserializer, job)); + ASSERT_TRUE(job.Serialize(s)); + } + + { + std::auto_ptr job; + job.reset(unserializer.UnserializeJob(s)); + + SplitStudyJob& tmp = dynamic_cast(*job); + ASSERT_TRUE(tmp.IsKeepSource()); + ASSERT_EQ(study, tmp.GetSourceStudy()); + ASSERT_EQ(RequestOrigin_Lua, tmp.GetOrigin().GetRequestOrigin()); + //ASSERT_TRUE(tmp.GetModification().IsRemoved(DICOM_TAG_STUDY_DESCRIPTION)); + } }