001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.tool.getscu.test; 040 041import static org.junit.Assert.assertTrue; 042 043import java.io.File; 044import java.io.FileInputStream; 045import java.io.FileNotFoundException; 046import java.io.IOException; 047import java.net.URL; 048import java.nio.file.Path; 049import java.security.GeneralSecurityException; 050import java.util.ArrayList; 051import java.util.List; 052import java.util.Map.Entry; 053import java.util.Properties; 054import java.util.Set; 055import java.util.concurrent.ExecutorService; 056import java.util.concurrent.Executors; 057import java.util.concurrent.ScheduledExecutorService; 058 059import org.dcm4che3.data.Attributes; 060import org.dcm4che3.data.ElementDictionary; 061import org.dcm4che3.data.Tag; 062import org.dcm4che3.data.UID; 063import org.dcm4che3.data.VR; 064import org.dcm4che3.io.DicomInputStream; 065import org.dcm4che3.net.*; 066import org.dcm4che3.net.pdu.PresentationContext; 067import org.dcm4che3.net.service.BasicCStoreSCP; 068import org.dcm4che3.net.service.DicomServiceException; 069import org.dcm4che3.net.service.DicomServiceRegistry; 070import org.dcm4che3.tool.common.CLIUtils; 071import org.dcm4che3.tool.common.test.TestResult; 072import org.dcm4che3.tool.common.test.TestTool; 073import org.dcm4che3.tool.getscu.GetSCU; 074import org.dcm4che3.tool.getscu.GetSCU.InformationModel; 075import org.dcm4che3.util.StringUtils; 076import org.slf4j.Logger; 077import org.slf4j.LoggerFactory; 078 079/** 080 * @author Umberto Cappellini <umberto.cappellini@agfa.com> 081 * @author Hesham Elbadawi <bsdreko@gmail.com> 082 */ 083public class RetrieveTool implements TestTool{ 084 085 private static final Logger LOG = LoggerFactory.getLogger(RetrieveTool.class); 086 087 private final String host; 088 private final int port; 089 private final String aeTitle; 090 private final Device device; 091 private final Connection conn; 092 private final File retrieveDir; 093 094 private int numCGetFailed; 095 private int numCGetSuccess; 096 private int numCGetWarning; 097 098 private int numCStores; 099 private int numCStoreSuccess; 100 private int numCStoreFailed; 101 102 private int expectedMatches = Integer.MIN_VALUE; 103 private final Attributes retrieveatts = new Attributes(); 104 private final GetSCU retrievescu; 105 106 private final List<Attributes> response = new ArrayList<Attributes>(); 107 private TestResult result; 108 private final String retrieveLevel; 109 private final InformationModel retrieveInformationModel; 110 111 private boolean rememberResultAttributes = true; 112 113 private static String[] IVR_LE_FIRST = { UID.ImplicitVRLittleEndian, 114 UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired }; 115 116 117 public RetrieveTool(String host, int port, String aeTitle, File retrieveDir, Device device, String sourceAETitle,String retrieveLevel 118 , String informationModel, boolean relational, Connection conn) { 119 super(); 120 this.host = host; 121 this.port = port; 122 this.aeTitle = aeTitle; 123 this.device = device; 124 this.retrieveDir = retrieveDir; 125 this.retrieveLevel = retrieveLevel; 126 this.retrieveInformationModel = informationModel.equalsIgnoreCase("StudyRoot") 127 ? InformationModel.StudyRoot : InformationModel.PatientRoot; 128 this.conn = conn; 129 130 //setup device and connection 131 device.setInstalled(true); 132 ApplicationEntity ae = new ApplicationEntity(sourceAETitle); 133 device.addApplicationEntity(ae); 134 ae.addConnection(conn); 135 retrievescu = new GetSCU(ae); 136 137 retrievescu.setInformationModel(retrieveInformationModel, IVR_LE_FIRST, relational); 138 try { 139 configureServiceClass(retrievescu); 140 } catch (IOException e) { 141 throw new RuntimeException(e); 142 } 143 } 144 145 public void retrieve(String testDescription) throws IOException, InterruptedException, 146 IncompatibleConnectionException, GeneralSecurityException, 147 FileNotFoundException, IOException { 148 149 retrievescu.getAAssociateRQ().setCalledAET(aeTitle); 150 retrievescu.getRemoteConnection().setHostname(host); 151 retrievescu.getRemoteConnection().setPort(port); 152 153 //ensure secure connection 154 retrievescu.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites()); 155 retrievescu.getRemoteConnection().setTlsProtocols(conn.tlsProtocols()); 156 157 retrievescu.setStorageDirectory(retrieveDir); 158 159 registerSCPservice(retrieveDir); 160 161 // add retrieve attrs 162 163 // create executor 164 ExecutorService executorService = Executors.newCachedThreadPool(); 165 ScheduledExecutorService scheduledExecutorService = Executors 166 .newSingleThreadScheduledExecutor(); 167 retrievescu.getDevice().setExecutor(executorService); 168 retrievescu.getDevice().setScheduledExecutor(scheduledExecutorService); 169 170 retrievescu.addLevel(retrieveLevel); 171 retrievescu.getKeys().addAll(retrieveatts); 172 173 long timeStart = System.currentTimeMillis(); 174 175 // open, send and wait for response 176 try { 177 retrievescu.open(); 178 retrievescu.retrieve(getResponseHandler()); 179 } catch (Exception exception){ 180 LOG.error("Error while retrieving", exception); 181 } finally { 182 retrievescu.close(); 183 executorService.shutdown(); 184 scheduledExecutorService.shutdown(); 185 } 186 187 long timeEnd = System.currentTimeMillis(); 188 189 if (this.expectedMatches >= 0) { 190 assertTrue("test[" + testDescription 191 + "] not returned expected result:" + this.expectedMatches 192 + " but:" + numCStores, numCStores == this.expectedMatches); 193 } 194 195 assertTrue("test[" + testDescription + "] had failed c-get responses: " + numCGetFailed, numCGetFailed == 0 ); 196 197 assertTrue("test[" + testDescription + "] had failed c-store responses: " + numCStoreFailed, numCStoreFailed == 0); 198 199 init(new RetrieveResult(testDescription, expectedMatches, numCStores, 200 numCStoreSuccess, numCStoreFailed, 201 (timeEnd - timeStart), response)); 202 } 203 204 private DimseRSPHandler getResponseHandler() { 205 return new DimseRSPHandler(retrievescu.getAssociation().nextMessageID()) { 206 @Override 207 public void onDimseRSP(Association as, Attributes cmd, Attributes data) { 208 super.onDimseRSP(as, cmd, data); 209 210 onCGetReponse(as, cmd, data); 211 } 212 }; 213 } 214 215 private void onCGetReponse(Association as, Attributes cmd, Attributes data) { 216 int status = cmd.getInt(Tag.Status, -1); 217 if(!Status.isPending(status)) { 218 numCGetSuccess = cmd.getInt(Tag.NumberOfCompletedSuboperations,0); 219 numCGetFailed = cmd.getInt(Tag.NumberOfFailedSuboperations,0); 220 numCGetWarning = cmd.getInt(Tag.NumberOfWarningSuboperations,0); 221 } 222 } 223 224 public void addOfferedStorageSOPClass(String cuid, String... tsuids) { 225 retrievescu.addOfferedStorageSOPClass(cuid, tsuids); 226 } 227 228 public void addTag(int tag, String... values) { 229 VR vr = ElementDictionary.vrOf(tag, null); 230 retrieveatts.setString(tag, vr, values); 231 } 232 233 public void addAll(Attributes attrs) { 234 retrieveatts.addAll(attrs); 235 } 236 237 public void clearTags() { 238 retrieveatts.clear(); 239 } 240 241 public void setExpectedMatches(int expectedResult) { 242 this.expectedMatches = expectedResult; 243 } 244 245 private void registerSCPservice(final File storeDir) { 246 DicomServiceRegistry serviceReg = new DicomServiceRegistry(); 247 serviceReg.addDicomService( new BasicCStoreSCP("*") { 248 249 @Override 250 protected void store(Association as, PresentationContext pc, Attributes rq, 251 PDVInputStream data, Attributes rsp) throws DicomServiceException { 252 253 if (storeDir == null) 254 return; 255 256 String iuid = rq.getString(Tag.AffectedSOPInstanceUID); 257 String cuid = rq.getString(Tag.AffectedSOPClassUID); 258 String tsuid = pc.getTransferSyntax(); 259 File file = new File(storeDir, iuid ); 260 try { 261 GetSCU.storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid), 262 data, file); 263 } catch (Exception e) { 264 throw new DicomServiceException(Status.ProcessingFailure, e); 265 } 266 //check returned result 267 try{ 268 DicomInputStream din = new DicomInputStream(file); 269 Attributes attrs = din.readDataset(-1, -1); 270 din.close(); 271 onCStoreReq(rq,attrs); 272 } 273 catch (Exception e) { 274 e.printStackTrace(); 275 } 276 } 277 }); 278 device.setDimseRQHandler(serviceReg); 279 } 280 281 protected void onCStoreReq(Attributes cmd, Attributes data) { 282 int status = cmd.getInt(Tag.Status, -1); 283 if (data != null && !data.isEmpty()) 284 ++numCStoreSuccess; 285 else 286 ++numCStoreFailed; 287 288 if (rememberResultAttributes) 289 response.add(data); 290 291 ++numCStores; 292 } 293 294 private static void configureServiceClass(GetSCU main) 295 throws FileNotFoundException, IOException { 296 297 URL defaultConfig = main.getClass().getResource( 298 "/retrieve/store-tcs.properties"); 299 Properties props = new Properties(); 300 props.load(new FileInputStream(new File(defaultConfig.getFile()))); 301 302 Set<Entry<Object, Object>> entrySet = props.entrySet(); 303 for (Entry<Object, Object> entry : entrySet) 304 configureStorageSOPClass(main, (String) entry.getKey(), 305 (String) entry.getValue()); 306 } 307 308 private static void configureStorageSOPClass(GetSCU main, String cuid, 309 String tsuids0) { 310 String[] tsuids1 = StringUtils.split(tsuids0, ';'); 311 for (String tsuids2 : tsuids1) { 312 main.addOfferedStorageSOPClass(CLIUtils.toUID(cuid), 313 CLIUtils.toUID(tsuids2)); 314 } 315 } 316 317 @Override 318 public void init(TestResult resultIn) { 319 this.result = resultIn; 320 } 321 322 @Override 323 public TestResult getResult() { 324 return this.result; 325 } 326 327 public Path getRetrieveDir() { 328 return retrieveDir.toPath(); 329 } 330 331 /** 332 * If set to false, does not keep the attributes retrieved. Useful for large performance tests to avoid VM crashes. 333 * @param rememberResultAttributes 334 */ 335 public void setRememberResultAttributes(boolean rememberResultAttributes) { 336 this.rememberResultAttributes = rememberResultAttributes; 337 } 338}