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