1 /** 2 * Orthanc - A Lightweight, RESTful DICOM Store 3 * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics 4 * Department, University Hospital of Liege, Belgium 5 * Copyright (C) 2017-2020 Osimis S.A., Belgium 6 * 7 * This program is free software: you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License as 9 * published by the Free Software Foundation, either version 3 of the 10 * License, or (at your option) any later version. 11 * 12 * In addition, as a special exception, the copyright holders of this 13 * program give permission to link the code of its release with the 14 * OpenSSL project's "OpenSSL" library (or with modified versions of it 15 * that use the same license as the "OpenSSL" library), and distribute 16 * the linked executables. You must obey the GNU General Public License 17 * in all respects for all of the code used other than "OpenSSL". If you 18 * modify file(s) with this exception, you may extend this exception to 19 * your version of the file(s), but you are not obligated to do so. If 20 * you do not wish to do so, delete this exception statement from your 21 * version. If you delete this exception statement from all source files 22 * in the program, then also delete it here. 23 * 24 * This program is distributed in the hope that it will be useful, but 25 * WITHOUT ANY WARRANTY; without even the implied warranty of 26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 27 * General Public License for more details. 28 * 29 * You should have received a copy of the GNU General Public License 30 * along with this program. If not, see <http://www.gnu.org/licenses/>. 31 **/ 32 33 34 #include "../PrecompiledHeadersServer.h" 35 #include "DicomModalityStoreJob.h" 36 37 #include "../../../OrthancFramework/Sources/Compatibility.h" 38 #include "../../../OrthancFramework/Sources/DicomNetworking/DicomAssociation.h" 39 #include "../../../OrthancFramework/Sources/Logging.h" 40 #include "../../../OrthancFramework/Sources/SerializationToolbox.h" 41 #include "../ServerContext.h" 42 #include "../StorageCommitmentReports.h" 43 44 45 namespace Orthanc 46 { OpenConnection()47 void DicomModalityStoreJob::OpenConnection() 48 { 49 if (connection_.get() == NULL) 50 { 51 connection_.reset(new DicomStoreUserConnection(parameters_)); 52 } 53 } 54 55 HandleInstance(const std::string & instance)56 bool DicomModalityStoreJob::HandleInstance(const std::string& instance) 57 { 58 assert(IsStarted()); 59 OpenConnection(); 60 61 LOG(INFO) << "Sending instance " << instance << " to modality \"" 62 << parameters_.GetRemoteModality().GetApplicationEntityTitle() << "\""; 63 64 std::string dicom; 65 66 try 67 { 68 context_.ReadDicom(dicom, instance); 69 } 70 catch (OrthancException& e) 71 { 72 LOG(WARNING) << "An instance was removed after the job was issued: " << instance; 73 return false; 74 } 75 76 std::string sopClassUid, sopInstanceUid; 77 context_.StoreWithTranscoding(sopClassUid, sopInstanceUid, *connection_, dicom, 78 HasMoveOriginator(), moveOriginatorAet_, moveOriginatorId_); 79 80 if (storageCommitment_) 81 { 82 sopClassUids_.push_back(sopClassUid); 83 sopInstanceUids_.push_back(sopInstanceUid); 84 85 if (sopClassUids_.size() != sopInstanceUids_.size() || 86 sopClassUids_.size() > GetInstancesCount()) 87 { 88 throw OrthancException(ErrorCode_InternalError); 89 } 90 91 if (sopClassUids_.size() == GetInstancesCount()) 92 { 93 assert(IsStarted()); 94 connection_.reset(NULL); 95 96 const std::string& remoteAet = parameters_.GetRemoteModality().GetApplicationEntityTitle(); 97 98 LOG(INFO) << "Sending storage commitment request to modality: " << remoteAet; 99 100 // Create a "pending" storage commitment report BEFORE the 101 // actual SCU call in order to avoid race conditions 102 context_.GetStorageCommitmentReports().Store( 103 transactionUid_, new StorageCommitmentReports::Report(remoteAet)); 104 105 std::vector<std::string> a(sopClassUids_.begin(), sopClassUids_.end()); 106 std::vector<std::string> b(sopInstanceUids_.begin(), sopInstanceUids_.end()); 107 108 DicomAssociation::RequestStorageCommitment(parameters_, transactionUid_, a, b); 109 } 110 } 111 112 //boost::this_thread::sleep(boost::posix_time::milliseconds(500)); 113 114 return true; 115 } 116 117 HandleTrailingStep()118 bool DicomModalityStoreJob::HandleTrailingStep() 119 { 120 throw OrthancException(ErrorCode_InternalError); 121 } 122 123 DicomModalityStoreJob(ServerContext & context)124 DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context) : 125 context_(context), 126 moveOriginatorId_(0), // By default, not a C-MOVE 127 storageCommitment_(false) // By default, no storage commitment 128 { 129 ResetStorageCommitment(); 130 } 131 132 SetLocalAet(const std::string & aet)133 void DicomModalityStoreJob::SetLocalAet(const std::string& aet) 134 { 135 if (IsStarted()) 136 { 137 throw OrthancException(ErrorCode_BadSequenceOfCalls); 138 } 139 else 140 { 141 parameters_.SetLocalApplicationEntityTitle(aet); 142 } 143 } 144 145 SetRemoteModality(const RemoteModalityParameters & remote)146 void DicomModalityStoreJob::SetRemoteModality(const RemoteModalityParameters& remote) 147 { 148 if (IsStarted()) 149 { 150 throw OrthancException(ErrorCode_BadSequenceOfCalls); 151 } 152 else 153 { 154 parameters_.SetRemoteModality(remote); 155 } 156 } 157 158 SetTimeout(uint32_t seconds)159 void DicomModalityStoreJob::SetTimeout(uint32_t seconds) 160 { 161 if (IsStarted()) 162 { 163 throw OrthancException(ErrorCode_BadSequenceOfCalls); 164 } 165 else 166 { 167 parameters_.SetTimeout(seconds); 168 } 169 } 170 171 GetMoveOriginatorAet() const172 const std::string& DicomModalityStoreJob::GetMoveOriginatorAet() const 173 { 174 if (HasMoveOriginator()) 175 { 176 return moveOriginatorAet_; 177 } 178 else 179 { 180 throw OrthancException(ErrorCode_BadSequenceOfCalls); 181 } 182 } 183 184 GetMoveOriginatorId() const185 uint16_t DicomModalityStoreJob::GetMoveOriginatorId() const 186 { 187 if (HasMoveOriginator()) 188 { 189 return moveOriginatorId_; 190 } 191 else 192 { 193 throw OrthancException(ErrorCode_BadSequenceOfCalls); 194 } 195 } 196 197 SetMoveOriginator(const std::string & aet,int id)198 void DicomModalityStoreJob::SetMoveOriginator(const std::string& aet, 199 int id) 200 { 201 if (IsStarted()) 202 { 203 throw OrthancException(ErrorCode_BadSequenceOfCalls); 204 } 205 else if (id < 0 || 206 id >= 65536) 207 { 208 throw OrthancException(ErrorCode_ParameterOutOfRange); 209 } 210 else 211 { 212 moveOriginatorId_ = static_cast<uint16_t>(id); 213 moveOriginatorAet_ = aet; 214 } 215 } 216 Stop(JobStopReason reason)217 void DicomModalityStoreJob::Stop(JobStopReason reason) // For pausing jobs 218 { 219 connection_.reset(NULL); 220 } 221 222 ResetStorageCommitment()223 void DicomModalityStoreJob::ResetStorageCommitment() 224 { 225 if (storageCommitment_) 226 { 227 transactionUid_ = Toolbox::GenerateDicomPrivateUniqueIdentifier(); 228 sopClassUids_.clear(); 229 sopInstanceUids_.clear(); 230 } 231 } 232 233 Reset()234 void DicomModalityStoreJob::Reset() 235 { 236 SetOfInstancesJob::Reset(); 237 238 /** 239 * "After the N-EVENT-REPORT has been sent, the Transaction UID is 240 * no longer active and shall not be reused for other 241 * transactions." => Need to reset the transaction UID here 242 * http://dicom.nema.org/medical/dicom/2019a/output/chtml/part04/sect_J.3.3.html 243 **/ 244 ResetStorageCommitment(); 245 } 246 247 EnableStorageCommitment(bool enabled)248 void DicomModalityStoreJob::EnableStorageCommitment(bool enabled) 249 { 250 storageCommitment_ = enabled; 251 ResetStorageCommitment(); 252 } 253 254 GetPublicContent(Json::Value & value)255 void DicomModalityStoreJob::GetPublicContent(Json::Value& value) 256 { 257 SetOfInstancesJob::GetPublicContent(value); 258 259 value["LocalAet"] = parameters_.GetLocalApplicationEntityTitle(); 260 value["RemoteAet"] = parameters_.GetRemoteModality().GetApplicationEntityTitle(); 261 262 if (HasMoveOriginator()) 263 { 264 value["MoveOriginatorAET"] = GetMoveOriginatorAet(); 265 value["MoveOriginatorID"] = GetMoveOriginatorId(); 266 } 267 268 if (storageCommitment_) 269 { 270 value["StorageCommitmentTransactionUID"] = transactionUid_; 271 } 272 } 273 274 275 static const char* MOVE_ORIGINATOR_AET = "MoveOriginatorAet"; 276 static const char* MOVE_ORIGINATOR_ID = "MoveOriginatorId"; 277 static const char* STORAGE_COMMITMENT = "StorageCommitment"; 278 279 DicomModalityStoreJob(ServerContext & context,const Json::Value & serialized)280 DicomModalityStoreJob::DicomModalityStoreJob(ServerContext& context, 281 const Json::Value& serialized) : 282 SetOfInstancesJob(serialized), 283 context_(context) 284 { 285 moveOriginatorAet_ = SerializationToolbox::ReadString(serialized, MOVE_ORIGINATOR_AET); 286 moveOriginatorId_ = static_cast<uint16_t> 287 (SerializationToolbox::ReadUnsignedInteger(serialized, MOVE_ORIGINATOR_ID)); 288 EnableStorageCommitment(SerializationToolbox::ReadBoolean(serialized, STORAGE_COMMITMENT)); 289 290 parameters_ = DicomAssociationParameters::UnserializeJob(serialized); 291 } 292 293 Serialize(Json::Value & target)294 bool DicomModalityStoreJob::Serialize(Json::Value& target) 295 { 296 if (!SetOfInstancesJob::Serialize(target)) 297 { 298 return false; 299 } 300 else 301 { 302 parameters_.SerializeJob(target); 303 target[MOVE_ORIGINATOR_AET] = moveOriginatorAet_; 304 target[MOVE_ORIGINATOR_ID] = moveOriginatorId_; 305 target[STORAGE_COMMITMENT] = storageCommitment_; 306 return true; 307 } 308 } 309 } 310