001/** 002 * Copyright (C) 2014 Universidade de Aveiro, DETI/IEETA, Bioinformatics Group - http://bioinformatics.ua.pt/ 003 * 004 * This file is part of Dicoogle/dicoogle. 005 * 006 * Dicoogle/dicoogle is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Dicoogle/dicoogle is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Dicoogle. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package pt.ua.dicoogle.server.queryretrieve; 020 021import java.io.*; 022import java.net.MalformedURLException; 023import java.net.URL; 024import java.security.GeneralSecurityException; 025import java.security.KeyStore; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.HashMap; 029import java.util.HashSet; 030import java.util.Iterator; 031import java.util.List; 032import java.util.Map; 033import java.util.Set; 034import java.util.concurrent.Executor; 035import java.util.zip.GZIPInputStream; 036 037 038import org.dcm4che2.data.BasicDicomObject; 039import org.dcm4che2.data.DicomElement; 040import org.dcm4che2.data.DicomObject; 041import org.dcm4che2.data.Tag; 042import org.dcm4che2.data.UID; 043import org.dcm4che2.data.UIDDictionary; 044import org.dcm4che2.data.VR; 045import org.dcm4che2.io.DicomInputStream; 046import org.dcm4che2.io.DicomOutputStream; 047import org.dcm4che2.io.StopTagInputHandler; 048import org.dcm4che2.io.TranscoderInputHandler; 049import org.dcm4che2.net.Association; 050import org.dcm4che2.net.CommandUtils; 051import org.dcm4che2.net.ConfigurationException; 052import org.dcm4che2.net.Device; 053import org.dcm4che2.net.DimseRSP; 054import org.dcm4che2.net.DimseRSPHandler; 055import org.dcm4che2.net.NetworkApplicationEntity; 056import org.dcm4che2.net.NetworkConnection; 057import org.dcm4che2.net.NewThreadExecutor; 058import org.dcm4che2.net.NoPresentationContextException; 059import org.dcm4che2.net.PDVOutputStream; 060import org.dcm4che2.net.TransferCapability; 061import org.dcm4che2.net.UserIdentity; 062import org.dcm4che2.net.service.StorageCommitmentService; 063import org.dcm4che2.util.CloseUtils; 064import org.dcm4che2.util.StringUtils; 065import org.dcm4che2.util.UIDUtils; 066import pt.ua.dicoogle.core.ServerSettings; 067 068/** 069 * @author gunter zeilinger(gunterze@gmail.com) 070 * @version $Revision: 10933 $ $Date: 2009-04-21 01:48:38 +0100 (Ter, 21 Abr 2009) $ 071 * @since Oct 13, 2005 072 */ 073public class DcmSnd extends StorageCommitmentService { 074 075 private static final int KB = 1024; 076 077 private static final int MB = KB * KB; 078 079 private static final int PEEK_LEN = 1024; 080 081 private static final String USAGE = 082 "dcmsnd [Options] <aet>[@<host>[:<port>]] <file>|<directory>..."; 083 084 private static final String DESCRIPTION = 085 "\nLoad composite DICOM Object(s) from specified DICOM file(s) and send it " 086 + "to the specified remote Application Entity. If a directory is specified," 087 + "DICOM Object in files under that directory and further sub-directories " 088 + "are sent. If <port> is not specified, DICOM default port 104 is assumed. " 089 + "If also no <host> is specified, localhost is assumed. Optionally, a " 090 + "Storage Commitment Request for successfully tranferred objects is sent " 091 + "to the remote Application Entity after the storage. The Storage Commitment " 092 + "result is accepted on the same association or - if a local port is " 093 + "specified by option -L - in a separate association initiated by the " 094 + "remote Application Entity\n" 095 + "OPTIONS:"; 096 097 private static final String EXAMPLE = 098 "\nExample: dcmsnd -stgcmt -L DCMSND:11113 STORESCP@localhost:11112 image.dcm \n" 099 + "=> Start listening on local port 11113 for receiving Storage Commitment " 100 + "results, send DICOM object image.dcm to Application Entity STORESCP, " 101 + "listening on local port 11112, and request Storage Commitment in same association."; 102 103 private static String[] TLS1 = { "TLSv1" }; 104 105 private static String[] SSL3 = { "SSLv3" }; 106 107 private static String[] NO_TLS1 = { "SSLv3", "SSLv2Hello" }; 108 109 private static String[] NO_SSL2 = { "TLSv1", "SSLv3" }; 110 111 private static String[] NO_SSL3 = { "TLSv1", "SSLv2Hello" }; 112 113 private static char[] SECRET = { 's', 'e', 'c', 'r', 'e', 't' }; 114 115 private static final String[] ONLY_IVLE_TS = { 116 UID.ImplicitVRLittleEndian 117 }; 118 119 private static final String[] IVLE_TS = { 120 UID.ImplicitVRLittleEndian, 121 UID.ExplicitVRLittleEndian, 122 UID.ExplicitVRBigEndian, 123 }; 124 125 private static final String[] EVLE_TS = { 126 UID.ExplicitVRLittleEndian, 127 UID.ImplicitVRLittleEndian, 128 UID.ExplicitVRBigEndian, 129 }; 130 131 private static final String[] EVBE_TS = { 132 UID.ExplicitVRBigEndian, 133 UID.ExplicitVRLittleEndian, 134 UID.ImplicitVRLittleEndian, 135 }; 136 137 private static final int STG_CMT_ACTION_TYPE = 1; 138 139 /** TransferSyntax: DCM4CHE URI Referenced */ 140 private static final String DCM4CHEE_URI_REFERENCED_TS_UID = 141 "1.2.40.0.13.1.1.2.4.94"; 142 143 private Executor executor = new NewThreadExecutor("DCMSND"); 144 145 private NetworkApplicationEntity remoteAE = new NetworkApplicationEntity(); 146 147 private NetworkApplicationEntity remoteStgcmtAE; 148 149 private NetworkConnection remoteConn = new NetworkConnection(); 150 151 private NetworkConnection remoteStgcmtConn = new NetworkConnection(); 152 153 private Device device = new Device("DCMSND"); 154 155 private NetworkApplicationEntity ae = new NetworkApplicationEntity(); 156 157 private NetworkConnection conn = new NetworkConnection(); 158 159 private Map<String, Set<String>> as2ts = new HashMap<String, Set<String>>(); 160 161 private ArrayList<FileInfo> files = new ArrayList<FileInfo>(); 162 163 private Association assoc; 164 165 private int priority = 0; 166 167 private int transcoderBufferSize = 1024; 168 169 private int filesSent = 0; 170 171 private long totalSize = 0L; 172 173 private boolean fileref = false; 174 175 private boolean stgcmt = false; 176 177 private long shutdownDelay = 1000L; 178 179 private DicomObject stgCmtResult; 180 181 private String keyStoreURL = "resource:tls/test_sys_1.p12"; 182 183 private char[] keyStorePassword = SECRET; 184 185 private char[] keyPassword; 186 187 private String trustStoreURL = "resource:tls/mesa_certs.jks"; 188 189 private char[] trustStorePassword = SECRET; 190 191 private String MoveOriginatorMessageID = null; 192 193 private boolean gzip = ServerSettings.getInstance().isGzipStorage(); 194 195 public DcmSnd() { 196 remoteAE.setInstalled(true); 197 remoteAE.setAssociationAcceptor(true); 198 remoteAE.setNetworkConnection(new NetworkConnection[] { remoteConn }); 199 200 device.setNetworkApplicationEntity(ae); 201 device.setNetworkConnection(conn); 202 ae.setNetworkConnection(conn); 203 ae.setAssociationInitiator(true); 204 ae.setAssociationAcceptor(true); 205 ae.register(this); 206 ae.setAETitle(ServerSettings.getInstance().getAE()); 207 } 208 209 public final void setLocalHost(String hostname) { 210 conn.setHostname(hostname); 211 } 212 213 public final void setLocalPort(int port) { 214 conn.setPort(port); 215 } 216 217 public final void setRemoteHost(String hostname) { 218 remoteConn.setHostname(hostname); 219 } 220 221 public final void setRemotePort(int port) { 222 remoteConn.setPort(port); 223 } 224 225 public final void setRemoteStgcmtHost(String hostname) { 226 remoteStgcmtConn.setHostname(hostname); 227 } 228 229 public final void setRemoteStgcmtPort(int port) { 230 remoteStgcmtConn.setPort(port); 231 } 232 233 public final void setTlsProtocol(String[] tlsProtocol) { 234 conn.setTlsProtocol(tlsProtocol); 235 } 236 237 public final void setTlsWithoutEncyrption() { 238 conn.setTlsWithoutEncyrption(); 239 remoteConn.setTlsWithoutEncyrption(); 240 remoteStgcmtConn.setTlsWithoutEncyrption(); 241 } 242 243 public final void setTls3DES_EDE_CBC() { 244 conn.setTls3DES_EDE_CBC(); 245 remoteConn.setTls3DES_EDE_CBC(); 246 remoteStgcmtConn.setTls3DES_EDE_CBC(); 247 } 248 249 public final void setTlsAES_128_CBC() { 250 conn.setTlsAES_128_CBC(); 251 remoteConn.setTlsAES_128_CBC(); 252 remoteStgcmtConn.setTlsAES_128_CBC(); 253 } 254 255 public final void setTlsNeedClientAuth(boolean needClientAuth) { 256 conn.setTlsNeedClientAuth(needClientAuth); 257 } 258 259 public final void setKeyStoreURL(String url) { 260 keyStoreURL = url; 261 } 262 263 public final void setKeyStorePassword(String pw) { 264 keyStorePassword = pw.toCharArray(); 265 } 266 267 public final void setKeyPassword(String pw) { 268 keyPassword = pw.toCharArray(); 269 } 270 271 public final void setTrustStorePassword(String pw) { 272 trustStorePassword = pw.toCharArray(); 273 } 274 275 public final void setTrustStoreURL(String url) { 276 trustStoreURL = url; 277 } 278 279 public final void setCalledAET(String called) { 280 remoteAE.setAETitle(called); 281 } 282 283 public final void setCalling(String calling) { 284 ae.setAETitle(calling); 285 } 286 287 public final void setUserIdentity(UserIdentity userIdentity) { 288 ae.setUserIdentity(userIdentity); 289 } 290 291 public final void setOfferDefaultTransferSyntaxInSeparatePresentationContext( 292 boolean enable) { 293 ae.setOfferDefaultTransferSyntaxInSeparatePresentationContext(enable); 294 } 295 296 public final void setSendFileRef(boolean fileref) { 297 this.fileref = fileref; 298 } 299 300 public final void setStorageCommitment(boolean stgcmt) { 301 this.stgcmt = stgcmt; 302 } 303 304 public final boolean isStorageCommitment() { 305 return stgcmt; 306 } 307 308 public final void setStgcmtCalledAET(String called) { 309 remoteStgcmtAE = new NetworkApplicationEntity(); 310 remoteStgcmtAE.setInstalled(true); 311 remoteStgcmtAE.setAssociationAcceptor(true); 312 remoteStgcmtAE.setNetworkConnection( 313 new NetworkConnection[] { remoteStgcmtConn }); 314 remoteStgcmtAE.setAETitle(called); 315 } 316 317 public final void setShutdownDelay(int shutdownDelay) { 318 this.shutdownDelay = shutdownDelay; 319 } 320 321 322 public final void setConnectTimeout(int connectTimeout) { 323 conn.setConnectTimeout(connectTimeout); 324 } 325 326 public final void setMaxPDULengthReceive(int maxPDULength) { 327 ae.setMaxPDULengthReceive(maxPDULength); 328 } 329 330 public final void setMaxOpsInvoked(int maxOpsInvoked) { 331 ae.setMaxOpsInvoked(maxOpsInvoked); 332 } 333 334 public final void setPackPDV(boolean packPDV) { 335 ae.setPackPDV(packPDV); 336 } 337 338 public final void setAssociationReaperPeriod(int period) { 339 device.setAssociationReaperPeriod(period); 340 } 341 342 public final void setDimseRspTimeout(int timeout) { 343 ae.setDimseRspTimeout(timeout); 344 } 345 346 public final void setPriority(int priority) { 347 this.priority = priority; 348 } 349 350 public final void setTcpNoDelay(boolean tcpNoDelay) { 351 conn.setTcpNoDelay(tcpNoDelay); 352 } 353 354 public final void setAcceptTimeout(int timeout) { 355 conn.setAcceptTimeout(timeout); 356 } 357 358 public final void setReleaseTimeout(int timeout) { 359 conn.setReleaseTimeout(timeout); 360 } 361 362 public final void setSocketCloseDelay(int timeout) { 363 conn.setSocketCloseDelay(timeout); 364 } 365 366 public final void setMaxPDULengthSend(int maxPDULength) { 367 ae.setMaxPDULengthSend(maxPDULength); 368 } 369 370 public final void setReceiveBufferSize(int bufferSize) { 371 conn.setReceiveBufferSize(bufferSize); 372 } 373 374 public final void setSendBufferSize(int bufferSize) { 375 conn.setSendBufferSize(bufferSize); 376 } 377 378 public final void setTranscoderBufferSize(int transcoderBufferSize) { 379 this.transcoderBufferSize = transcoderBufferSize; 380 } 381 382 public final int getNumberOfFilesToSend() { 383 return files.size(); 384 } 385 386 public final int getNumberOfFilesSent() { 387 return filesSent; 388 } 389 390 public final long getTotalSizeSent() { 391 return totalSize; 392 } 393 394 public List<FileInfo> getFileInfos() { 395 return files; 396 } 397 398 399 private static void promptStgCmt(DicomObject cmtrslt, float seconds) { 400 401 DicomElement refSOPSq = cmtrslt.get(Tag.ReferencedSOPSequence); 402 System.out.print(refSOPSq.countItems()); 403 System.out.println(" successful"); 404 DicomElement failedSOPSq = cmtrslt.get(Tag.FailedSOPSequence); 405 if (failedSOPSq != null) { 406 System.out.print(failedSOPSq.countItems()); 407 System.out.println(" FAILED!"); 408 } 409 } 410 411 private synchronized DicomObject waitForStgCmtResult() throws InterruptedException { 412 while (stgCmtResult == null) wait(); 413 return stgCmtResult; 414 } 415 416 private static void prompt(DcmSnd dcmsnd, float seconds) { 417 System.out.print("\nSent "); 418 System.out.print(dcmsnd.getNumberOfFilesSent()); 419 System.out.print(" objects (="); 420 promptBytes(dcmsnd.getTotalSizeSent()); 421 System.out.print(") in "); 422 System.out.print(seconds); 423 System.out.print("s (="); 424 promptBytes(dcmsnd.getTotalSizeSent() / seconds); 425 System.out.println("/s)"); 426 } 427 428 private static void promptBytes(float totalSizeSent) { 429 if (totalSizeSent > MB) { 430 System.out.print(totalSizeSent / MB); 431 System.out.print("MB"); 432 } else { 433 System.out.print(totalSizeSent / KB); 434 System.out.print("KB"); 435 } 436 } 437 438 private static int toPort(String port) { 439 return port != null ? parseInt(port, "illegal port number", 1, 0xffff) 440 : 104; 441 } 442 443 private static String[] split(String s, char delim) { 444 String[] s2 = { s, null }; 445 int pos = s.indexOf(delim); 446 if (pos != -1) { 447 s2[0] = s.substring(0, pos); 448 s2[1] = s.substring(pos + 1); 449 } 450 return s2; 451 } 452 453 private static void exit(String msg) { 454 System.err.println(msg); 455 System.err.println("Try 'dcmsnd -h' for more information."); 456 System.exit(1); 457 } 458 459 private static int parseInt(String s, String errPrompt, int min, int max) { 460 try { 461 int i = Integer.parseInt(s); 462 if (i >= min && i <= max) 463 return i; 464 } catch (NumberFormatException e) { 465 // parameter is not a valid integer; fall through to exit 466 } 467 exit(errPrompt); 468 throw new RuntimeException(); 469 } 470 471 public void addFile(File f) { 472 if (f.isDirectory()) { 473 File[] fs = f.listFiles(); 474 for (int i = 0; i < fs.length; i++) 475 addFile(fs[i]); 476 return; 477 } 478 FileInfo info = new FileInfo(f); 479 DicomObject dcmObj = new BasicDicomObject(); 480 DicomInputStream in = null; 481 try { 482 if (f.getAbsolutePath().endsWith(".gz")) 483 { 484 in = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(f), 256))); 485 } 486 else 487 { 488 in = new DicomInputStream(f); 489 } 490 in.setHandler(new StopTagInputHandler(Tag.StudyDate)); 491 in.readDicomObject(dcmObj, PEEK_LEN); 492 info.tsuid = in.getTransferSyntax().uid(); 493 info.fmiEndPos = in.getEndOfFileMetaInfoPosition(); 494 } catch (IOException e) { 495 e.printStackTrace(); 496 System.err.println("WARNING: Failed to parse " + f + " - skipped."); 497 System.out.print('F'); 498 return; 499 } finally { 500 CloseUtils.safeClose(in); 501 } 502 info.cuid = dcmObj.getString(Tag.SOPClassUID); 503 if (info.cuid == null) { 504 System.err.println("WARNING: Missing SOP Class UID in " + f 505 + " - skipped."); 506 System.out.print('F'); 507 return; 508 } 509 info.iuid = dcmObj.getString(Tag.SOPInstanceUID); 510 if (info.iuid == null) { 511 System.err.println("WARNING: Missing SOP Instance UID in " + f 512 + " - skipped."); 513 System.out.print('F'); 514 return; 515 } 516 addTransferCapability(info.cuid, info.tsuid); 517 files.add(info); 518 System.out.print('.'); 519 } 520 521 public void addTransferCapability(String cuid, String tsuid) { 522 Set<String> ts = as2ts.get(cuid); 523 if (fileref) { 524 if (ts == null) { 525 as2ts.put(cuid, 526 Collections.singleton(DCM4CHEE_URI_REFERENCED_TS_UID)); 527 } 528 } else { 529 if (ts == null) { 530 ts = new HashSet<String>(); 531 ts.add(UID.ImplicitVRLittleEndian); 532 as2ts.put(cuid, ts); 533 } 534 ts.add(tsuid); 535 } 536 } 537 538 539 540 public void configureTransferCapability() { 541 int off = stgcmt || remoteStgcmtAE != null ? 1 : 0; 542 TransferCapability[] tc = new TransferCapability[off + as2ts.size()]; 543 if (off > 0) { 544 tc[0] = new TransferCapability( 545 UID.StorageCommitmentPushModelSOPClass, 546 ONLY_IVLE_TS, 547 TransferCapability.SCU); 548 } 549 Iterator<Map.Entry<String, Set<String>>> iter = as2ts.entrySet().iterator(); 550 for (int i = off; i < tc.length; i++) { 551 Map.Entry<String, Set<String>> e = iter.next(); 552 String cuid = e.getKey(); 553 Set<String> ts = e.getValue(); 554 tc[i] = new TransferCapability(cuid, 555 ts.toArray(new String[ts.size()]), 556 TransferCapability.SCU); 557 } 558 ae.setTransferCapability(tc); 559 } 560 561 public void start() throws IOException { 562 if (conn.isListening()) { 563 conn.bind(executor ); 564 System.out.println("Start Server listening on port " + conn.getPort()); 565 } 566 } 567 568 public void stop() { 569 if (conn.isListening()) { 570 try { 571 Thread.sleep(shutdownDelay); 572 } catch (InterruptedException e) { 573 // Should not happen 574 e.printStackTrace(); 575 } 576 conn.unbind(); 577 } 578 } 579 580 public void open() throws IOException, ConfigurationException, 581 InterruptedException { 582 assoc = ae.connect(remoteAE, executor); 583 } 584 585 public void openToStgcmtAE() throws IOException, ConfigurationException, 586 InterruptedException { 587 assoc = ae.connect(remoteStgcmtAE, executor); 588 } 589 590 public void send() { 591 for (int i = 0, n = files.size(); i < n; ++i) { 592 FileInfo info = files.get(i); 593 TransferCapability tc = assoc.getTransferCapabilityAsSCU(info.cuid); 594 if (tc == null) { 595 System.out.println(); 596 System.out.println(UIDDictionary.getDictionary().prompt( 597 info.cuid) 598 + " not supported by " + remoteAE.getAETitle()); 599 System.out.println("skip file " + info.f); 600 continue; 601 } 602 603 604 String tsuid = selectTransferSyntax(tc.getTransferSyntax(), 605 fileref ? DCM4CHEE_URI_REFERENCED_TS_UID : info.tsuid); 606 if (tsuid == null) { 607 System.out.println(); 608 System.out.println(UIDDictionary.getDictionary().prompt( 609 info.cuid) 610 + " with " 611 + UIDDictionary.getDictionary().prompt( 612 fileref ? DCM4CHEE_URI_REFERENCED_TS_UID 613 : info.tsuid) 614 + " not supported by " + remoteAE.getAETitle()); 615 System.out.println("skip file " + info.f); 616 continue; 617 } 618 619 try { 620 DimseRSPHandler rspHandler = new DimseRSPHandler() { 621 @Override 622 public void onDimseRSP(Association as, DicomObject cmd, 623 DicomObject data) { 624 DcmSnd.this.onDimseRSP(cmd); 625 } 626 }; 627 628 if(MoveOriginatorMessageID!=null) 629 { 630 int messageID = Integer.parseInt(MoveOriginatorMessageID); 631 assoc.cstore(info.cuid, info.iuid, priority, assoc.getCallingAET(), messageID, 632 new DataWriter(info), tsuid, rspHandler); 633 } 634 else 635 { 636 assoc.cstore(info.cuid, info.iuid, priority, 637 new DataWriter(info), tsuid, rspHandler); 638 } 639 640 641 642 //assoc.cstore(info.cuid, info.iuid, priority, assoc.getCallingAET(), priority, new DataWriter(info), tsuid, rspHandler); 643 644 } catch (NoPresentationContextException e) { 645 System.err.println("WARNING: " + e.getMessage() 646 + " - cannot send " + info.f); 647 System.out.print('F'); 648 } catch (IOException e) { 649 e.printStackTrace(); 650 System.err.println("ERROR: Failed to send - " + info.f + ": " 651 + e.getMessage()); 652 System.out.print('F'); 653 } catch (InterruptedException e) { 654 // should not happen 655 e.printStackTrace(); 656 } 657 } 658 try { 659 assoc.waitForDimseRSP(); 660 } catch (InterruptedException e) { 661 // should not happen 662 e.printStackTrace(); 663 } 664 } 665 666 public boolean commit() { 667 DicomObject actionInfo = new BasicDicomObject(); 668 actionInfo.putString(Tag.TransactionUID, VR.UI, UIDUtils.createUID()); 669 670 DicomElement refSOPSq = actionInfo.putSequence(Tag.ReferencedSOPSequence); 671 for (int i = 0, n = files.size(); i < n; ++i) { 672 FileInfo info = files.get(i); 673 if (info.transferred) { 674 BasicDicomObject refSOP = new BasicDicomObject(); 675 676 refSOP.putString(Tag.ReferencedSOPClassUID, VR.UI, info.cuid); 677 refSOP.putString(Tag.ReferencedSOPInstanceUID, VR.UI, info.iuid); 678 679 refSOPSq.addDicomObject(refSOP); 680 } 681 } 682 try { 683 stgCmtResult = null; 684 DimseRSP rsp = assoc.naction(UID.StorageCommitmentPushModelSOPClass, 685 UID.StorageCommitmentPushModelSOPInstance, STG_CMT_ACTION_TYPE, 686 actionInfo, UID.ImplicitVRLittleEndian); 687 rsp.next(); 688 DicomObject cmd = rsp.getCommand(); 689 int status = cmd.getInt(Tag.Status); 690 if (status == 0) { 691 return true; 692 } 693 System.err.println( 694 "WARNING: Storage Commitment request failed with status: " 695 + StringUtils.shortToHex(status) + "H"); 696 System.err.println(cmd.toString()); 697 } catch (NoPresentationContextException e) { 698 System.err.println("WARNING: " + e.getMessage() 699 + " - cannot request Storage Commitment"); 700 } catch (IOException e) { 701 e.printStackTrace(); 702 System.err.println( 703 "ERROR: Failed to send Storage Commitment request: " 704 + e.getMessage()); 705 } catch (InterruptedException e) { 706 // should not happen 707 e.printStackTrace(); 708 } 709 return false; 710 } 711 712 private String selectTransferSyntax(String[] available, String tsuid) { 713 if (tsuid.equals(UID.ImplicitVRLittleEndian)) 714 return selectTransferSyntax(available, IVLE_TS); 715 if (tsuid.equals(UID.ExplicitVRLittleEndian)) 716 return selectTransferSyntax(available, EVLE_TS); 717 if (tsuid.equals(UID.ExplicitVRBigEndian)) 718 return selectTransferSyntax(available, EVBE_TS); 719 for (int j = 0; j < available.length; j++) 720 if (available[j].equals(tsuid)) 721 return tsuid; 722 return null; 723 } 724 725 private String selectTransferSyntax(String[] available, String[] tsuids) { 726 for (int i = 0; i < tsuids.length; i++) 727 for (int j = 0; j < available.length; j++) 728 if (available[j].equals(tsuids[i])) 729 return available[j]; 730 return null; 731 } 732 733 public void close() { 734 try { 735 assoc.release(false); 736 } catch (InterruptedException e) { 737 e.printStackTrace(); 738 } 739 } 740 741 /** 742 * @return the MoveOriginatorMessageID 743 */ 744 public String getMoveOriginatorMessageID() { 745 return MoveOriginatorMessageID; 746 } 747 748 /** 749 * @param MoveOriginatorMessageID the MoveOriginatorMessageID to set 750 */ 751 public void setMoveOriginatorMessageID(String MoveOriginatorMessageID) { 752 this.MoveOriginatorMessageID = MoveOriginatorMessageID; 753 } 754 755 public static final class FileInfo { 756 File f; 757 758 String cuid; 759 760 String iuid; 761 762 String tsuid; 763 764 long fmiEndPos; 765 766 long length; 767 768 boolean transferred; 769 770 int status; 771 772 public FileInfo(File f) { 773 this.f = f; 774 this.length = f.length(); 775 } 776 777 } 778 779 780 781 private class DataWriter implements org.dcm4che2.net.DataWriter { 782 783 private FileInfo info; 784 785 public DataWriter(FileInfo info) { 786 this.info = info; 787 } 788 789 public void writeTo(PDVOutputStream out, String tsuid) 790 throws IOException { 791 if (tsuid.equals(info.tsuid)) { 792 InputStream fis = null; 793 if (info.f.getAbsolutePath().endsWith(".gz")) 794 fis = new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256)); 795 else 796 fis = new FileInputStream(info.f); 797 798 try { 799 long skip = info.fmiEndPos; 800 while (skip > 0) 801 skip -= fis.skip(skip); 802 out.copyFrom(fis); 803 } finally { 804 fis.close(); 805 } 806 } else if (tsuid.equals(DCM4CHEE_URI_REFERENCED_TS_UID)) { 807 DicomObject attrs; 808 DicomInputStream dis = null; 809 if (info.f.getAbsolutePath().endsWith(".gz")) 810 { 811 dis = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256))); 812 } 813 else 814 { 815 dis= new DicomInputStream(info.f); 816 } 817 818 try { 819 dis.setHandler(new StopTagInputHandler(Tag.PixelData)); 820 attrs = dis.readDicomObject(); 821 } finally { 822 dis.close(); 823 } 824 DicomOutputStream dos = new DicomOutputStream(out); 825 attrs.putString(Tag.RetrieveURI, VR.UT, info.f.toURI().toString()); 826 827 dos.writeDataset(attrs, tsuid); 828 } else { 829 DicomInputStream dis = null; 830 if (info.f.getAbsolutePath().endsWith(".gz")) 831 { 832 dis = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256))); 833 } 834 else 835 { 836 dis = new DicomInputStream(info.f); 837 } 838 try { 839 DicomOutputStream dos = new DicomOutputStream(out); 840 dos.setTransferSyntax(tsuid); 841 TranscoderInputHandler h = new TranscoderInputHandler(dos, 842 transcoderBufferSize); 843 dis.setHandler(h); 844 dis.readDicomObject(); 845 } finally { 846 dis.close(); 847 } 848 } 849 } 850 851 } 852 853 private void promptErrRSP(String prefix, int status, FileInfo info, 854 DicomObject cmd) { 855 System.err.println(prefix + StringUtils.shortToHex(status) + "H for " 856 + info.f + ", cuid=" + info.cuid + ", tsuid=" + info.tsuid); 857 System.err.println(cmd.toString()); 858 } 859 860 private void onDimseRSP(DicomObject cmd) { 861 int status = cmd.getInt(Tag.Status); 862 int msgId = cmd.getInt(Tag.MessageIDBeingRespondedTo); 863 FileInfo info = files.get(msgId - 1); 864 info.status = status; 865 switch (status) { 866 case 0: 867 info.transferred = true; 868 totalSize += info.length; 869 ++filesSent; 870 System.out.print('.'); 871 break; 872 case 0xB000: 873 case 0xB006: 874 case 0xB007: 875 info.transferred = true; 876 totalSize += info.length; 877 ++filesSent; 878 promptErrRSP("WARNING: Received RSP with Status ", status, info, 879 cmd); 880 System.out.print('W'); 881 break; 882 default: 883 promptErrRSP("ERROR: Received RSP with Status ", status, info, cmd); 884 System.out.print('F'); 885 } 886 } 887 888 @Override 889 protected synchronized void onNEventReportRSP(Association as, int pcid, 890 DicomObject rq, DicomObject info, DicomObject rsp) { 891 stgCmtResult = info; 892 notifyAll(); 893 } 894 895 public void initTLS() throws GeneralSecurityException, IOException { 896 KeyStore keyStore = loadKeyStore(keyStoreURL, keyStorePassword); 897 KeyStore trustStore = loadKeyStore(trustStoreURL, trustStorePassword); 898 device.initTLS(keyStore, 899 keyPassword != null ? keyPassword : keyStorePassword, 900 trustStore); 901 } 902 903 private static KeyStore loadKeyStore(String url, char[] password) 904 throws GeneralSecurityException, IOException { 905 KeyStore key = KeyStore.getInstance(toKeyStoreType(url)); 906 InputStream in = openFileOrURL(url); 907 try { 908 key.load(in, password); 909 } finally { 910 in.close(); 911 } 912 return key; 913 } 914 915 private static InputStream openFileOrURL(String url) throws IOException { 916 if (url.startsWith("resource:")) { 917 return DcmSnd.class.getClassLoader().getResourceAsStream( 918 url.substring(9)); 919 } 920 try { 921 return new URL(url).openStream(); 922 } catch (MalformedURLException e) { 923 return new FileInputStream(url); 924 } 925 } 926 927 private static String toKeyStoreType(String fname) { 928 return fname.endsWith(".p12") || fname.endsWith(".P12") 929 ? "PKCS12" : "JKS"; 930 } 931}