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.net; 040 041import java.io.IOException; 042import java.io.InputStream; 043import java.io.OutputStream; 044import java.net.Socket; 045import java.util.Collections; 046import java.util.HashMap; 047import java.util.Set; 048import java.util.concurrent.TimeUnit; 049import java.util.concurrent.atomic.AtomicInteger; 050 051import org.dcm4che3.data.Attributes; 052import org.dcm4che3.data.Tag; 053import org.dcm4che3.data.UID; 054import org.dcm4che3.data.VR; 055import org.dcm4che3.net.pdu.AAbort; 056import org.dcm4che3.net.pdu.AAssociateAC; 057import org.dcm4che3.net.pdu.AAssociateRJ; 058import org.dcm4che3.net.pdu.AAssociateRQ; 059import org.dcm4che3.net.pdu.CommonExtendedNegotiation; 060import org.dcm4che3.net.pdu.PresentationContext; 061import org.dcm4che3.net.pdu.RoleSelection; 062import org.dcm4che3.util.IntHashMap; 063import org.dcm4che3.util.SafeClose; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067/** 068 * @author Gunter Zeilinger <gunterze@gmail.com> 069 */ 070public class Association { 071 072 public static final Logger LOG = LoggerFactory.getLogger(Association.class); 073 074 private static final AtomicInteger prevSerialNo = new AtomicInteger(); 075 private final AtomicInteger messageID = new AtomicInteger(); 076 private final int serialNo; 077 private final boolean requestor; 078 private String name; 079 private ApplicationEntity ae; 080 private final Device device; 081 private final Connection conn; 082 private final Socket sock; 083 private final InputStream in; 084 private final OutputStream out; 085 private final PDUEncoder encoder; 086 private PDUDecoder decoder; 087 private State state; 088 private AAssociateRQ rq; 089 private AAssociateAC ac; 090 private IOException ex; 091 092 private HashMap<String, Object> properties; 093 private int maxOpsInvoked; 094 private int maxPDULength; 095 private int performing; 096 private Timeout timeout; 097 private final IntHashMap<DimseRSPHandler> rspHandlerForMsgId = 098 new IntHashMap<DimseRSPHandler>(); 099 private final IntHashMap<CancelRQHandler> cancelHandlerForMsgId = 100 new IntHashMap<CancelRQHandler>(); 101 private final HashMap<String,HashMap<String,PresentationContext>> pcMap = 102 new HashMap<String,HashMap<String,PresentationContext>>(); 103 104 Association(ApplicationEntity ae, Connection local, Socket sock) 105 throws IOException { 106 this.serialNo = prevSerialNo.incrementAndGet(); 107 this.ae = ae; 108 this.requestor = ae != null; 109 this.name = "" + sock.getLocalSocketAddress() 110 + delim() + sock.getRemoteSocketAddress() 111 + '(' + serialNo + ')'; 112 this.conn = local; 113 this.device = local.getDevice(); 114 this.sock = sock; 115 this.in = sock.getInputStream(); 116 this.out = sock.getOutputStream(); 117 this.encoder = new PDUEncoder(this, out); 118 if (requestor) { 119 enterState(State.Sta4); 120 } else { 121 enterState(State.Sta2); 122 startRequestTimeout(); 123 } 124 activate(); 125 } 126 127 public Device getDevice() { 128 return device; 129 } 130 131 public int nextMessageID() { 132 return messageID.incrementAndGet() & 0xFFFF; 133 } 134 135 private String delim() { 136 return requestor ? "->" : "<-"; 137 } 138 139 @Override 140 public String toString() { 141 return name; 142 } 143 144 public final Socket getSocket() { 145 return sock; 146 } 147 148 public final Connection getConnection() { 149 return conn; 150 } 151 152 public final AAssociateRQ getAAssociateRQ() { 153 return rq; 154 } 155 156 public final AAssociateAC getAAssociateAC() { 157 return ac; 158 } 159 160 public final IOException getException() { 161 return ex; 162 } 163 164 public final ApplicationEntity getApplicationEntity() { 165 return ae; 166 } 167 168 public Object getProperty(String key) { 169 return properties != null ? properties.get(key) : null; 170 } 171 172 @SuppressWarnings("unchecked") 173 public <T> T getProperty(Class<T> clazz) { 174 return (T) getProperty(clazz.getName()); 175 } 176 177 public void setProperty(Object value) { 178 setProperty(value.getClass().getName(), value); 179 } 180 181 public boolean containsProperty(String key) { 182 return properties != null && properties.containsKey(key); 183 } 184 185 public Object setProperty(String key, Object value) { 186 if (properties == null) 187 properties = new HashMap<String, Object>(); 188 return properties.put(key, value); 189 } 190 191 public Object clearProperty(String key) { 192 return properties != null ? properties.remove(key) : null; 193 } 194 195 public final boolean isRequestor() { 196 return requestor; 197 } 198 199 public boolean isReadyForDataTransfer() { 200 return state == State.Sta6; 201 } 202 203 private void checkIsSCP(String cuid) throws NoRoleSelectionException { 204 if (!isSCPFor(cuid)) 205 throw new NoRoleSelectionException(cuid, 206 TransferCapability.Role.SCP); 207 } 208 209 public boolean isSCPFor(String cuid) { 210 RoleSelection rolsel = ac.getRoleSelectionFor(cuid); 211 if (rolsel == null) 212 return !requestor; 213 return requestor ? rolsel.isSCP() : rolsel.isSCU(); 214 } 215 216 private void checkIsSCU(String cuid) throws NoRoleSelectionException { 217 if (!isSCUFor(cuid)) 218 throw new NoRoleSelectionException(cuid, 219 TransferCapability.Role.SCU); 220 } 221 222 public boolean isSCUFor(String cuid) { 223 RoleSelection rolsel = ac.getRoleSelectionFor(cuid); 224 if (rolsel == null) 225 return requestor; 226 return requestor ? rolsel.isSCU() : rolsel.isSCP(); 227 } 228 229 public String getCallingAET() { 230 return rq != null ? rq.getCallingAET() : null; 231 } 232 233 public String getCalledAET() { 234 return rq != null ? rq.getCalledAET() : null; 235 } 236 237 public String getRemoteAET() { 238 return requestor ? getCalledAET() : getCallingAET(); 239 } 240 241 public String getLocalAET() { 242 return requestor ? getCallingAET() : getCalledAET(); 243 } 244 245 public String getRemoteImplVersionName() { 246 return (requestor ? ac : rq).getImplVersionName(); 247 } 248 249 public String getRemoteImplClassUID() { 250 return (requestor ? ac : rq).getImplClassUID(); 251 } 252 253 public String getLocalImplVersionName() { 254 return (requestor ? rq : ac).getImplVersionName(); 255 } 256 257 public String getLocalImplClassUID() { 258 return (requestor ? rq : ac).getImplClassUID(); 259 } 260 261 final int getMaxPDULengthSend() { 262 return maxPDULength; 263 } 264 265 boolean isPackPDV() { 266 return conn.isPackPDV(); 267 } 268 269 public void release() throws IOException { 270 state.writeAReleaseRQ(this); 271 } 272 273 public void abort() { 274 abort(new AAbort()); 275 } 276 277 void abort(AAbort aa) { 278 try { 279 state.write(this, aa); 280 } catch (IOException e) { 281 // already handled by onIOException() 282 // do not bother user about 283 } 284 } 285 286 private synchronized void closeSocket() { 287 state.closeSocket(this); 288 } 289 290 void doCloseSocket() { 291 LOG.info("{}: close {}", name, sock); 292 SafeClose.close(sock); 293 enterState(State.Sta1); 294 } 295 296 synchronized private void closeSocketDelayed() { 297 state.closeSocketDelayed(this); 298 } 299 300 void doCloseSocketDelayed() { 301 enterState(State.Sta13); 302 int delay = conn.getSocketCloseDelay(); 303 if (delay > 0) 304 device.schedule(new Runnable() { 305 306 @Override 307 public void run() { 308 closeSocket(); 309 } 310 }, delay, TimeUnit.MILLISECONDS); 311 else 312 closeSocket(); 313 } 314 315 synchronized void onIOException(IOException e) { 316 if (ex != null) 317 return; 318 319 ex = e; 320 LOG.warn("{}: i/o exception: {} in State: {}", 321 new Object[] { name, e, state }); 322 closeSocket(); 323 } 324 325 void write(AAbort aa) throws IOException { 326 LOG.info("{} << {}", name, aa); 327 encoder.write(aa); 328 ex = aa; 329 closeSocketDelayed(); 330 } 331 332 void writeAReleaseRQ() throws IOException { 333 LOG.info("{} << A-RELEASE-RQ", name); 334 enterState(State.Sta7); 335 stopTimeout(); 336 encoder.writeAReleaseRQ(); 337 startReleaseTimeout(); 338 } 339 340 private void startRequestTimeout() { 341 startTimeout("{}: start A-ASSOCIATE-RQ timeout of {}ms", 342 "{}: A-ASSOCIATE-RQ timeout expired", 343 "{}: stop A-ASSOCIATE-RQ timeout", 344 conn.getRequestTimeout(), State.Sta2); 345 } 346 347 private void startAcceptTimeout() { 348 startTimeout("{}: start A-ASSOCIATE-AC timeout of {}ms", 349 "{}: A-ASSOCIATE-AC timeout expired", 350 "{}: stop A-ASSOCIATE-AC timeout", 351 conn.getAcceptTimeout(), State.Sta5); 352 } 353 354 private void startReleaseTimeout() { 355 startTimeout("{}: start A-RELEASE-RP timeout of {}ms", 356 "{}: A-RELEASE-RP timeout expired", 357 "{}: stop A-RELEASE-RP timeout", 358 conn.getReleaseTimeout(), State.Sta7); 359 } 360 361 private void startIdleTimeout() { 362 startTimeout("{}: start idle timeout of {}ms", 363 "{}: idle timeout expired", 364 "{}: stop idle timeout", 365 conn.getIdleTimeout(), State.Sta6); 366 } 367 368 private void startTimeout(String startMsg, String expiredMsg, 369 String cancelMsg, int timeout, State state) { 370 if (timeout > 0 && performing == 0 && rspHandlerForMsgId.isEmpty()) { 371 synchronized (this) { 372 if (this.state == state) { 373 stopTimeout(); 374 this.timeout = Timeout.start(this, startMsg, expiredMsg, 375 cancelMsg, timeout); 376 } 377 } 378 } 379 } 380 381 private void startTimeout(final int msgID, int timeout) { 382 if (timeout > 0) { 383 synchronized (rspHandlerForMsgId) { 384 DimseRSPHandler rspHandler = rspHandlerForMsgId.get(msgID); 385 if (rspHandler != null) { 386 rspHandler.setTimeout(Timeout.start(this, 387 "{}: start " + msgID + ":DIMSE-RSP timeout of {}ms", 388 "{}: " + msgID + ":DIMSE-RSP timeout expired", 389 "{}: stop " + msgID + ":DIMSE-RSP timeout", 390 timeout)); 391 } 392 } 393 } 394 } 395 396 private synchronized void stopTimeout() { 397 if (timeout != null) { 398 timeout.stop(); 399 timeout = null; 400 } 401 } 402 403 public void waitForOutstandingRSP() throws InterruptedException { 404 synchronized (rspHandlerForMsgId) { 405 while (!rspHandlerForMsgId.isEmpty()) 406 rspHandlerForMsgId.wait(); 407 } 408 } 409 410 void write(AAssociateRQ rq) throws IOException { 411 name = rq.getCallingAET() + delim() + rq.getCalledAET() + '(' + serialNo + ')'; 412 this.rq = rq; 413 LOG.info("{} << A-ASSOCIATE-RQ", name); 414 LOG.debug("{}", rq); 415 enterState(State.Sta5); 416 encoder.write(rq); 417 startAcceptTimeout(); 418 } 419 420 private void write(AAssociateAC ac) throws IOException { 421 LOG.info("{} << A-ASSOCIATE-AC", name); 422 LOG.debug("{}", ac); 423 enterState(State.Sta6); 424 encoder.write(ac); 425 startIdleTimeout(); 426 } 427 428 private void write(AAssociateRJ e) throws IOException { 429 LOG.info("{} << {}", name, e); 430 encoder.write(e); 431 closeSocketDelayed(); 432 } 433 434 private void checkException() throws IOException { 435 if (ex != null) 436 throw ex; 437 } 438 439 private synchronized void enterState(State newState) { 440 LOG.debug("{}: enter state: {}", name, newState); 441 this.state = newState; 442 notifyAll(); 443 } 444 445 public final State getState() { 446 return state; 447 } 448 449 synchronized void waitForLeaving(State state) 450 throws InterruptedException, IOException { 451 while (this.state == state) 452 wait(); 453 checkException(); 454 } 455 456 synchronized void waitForEntering(State state) 457 throws InterruptedException, IOException { 458 while (this.state != state) 459 wait(); 460 checkException(); 461 } 462 463 public void waitForSocketClose() 464 throws InterruptedException, IOException { 465 waitForEntering(State.Sta1); 466 } 467 468 private void activate() { 469 device.execute(new Runnable() { 470 471 @Override 472 public void run() { 473 decoder = new PDUDecoder(Association.this, in); 474 device.incrementNumberOfOpenAssociations(); 475 try { 476 try { 477 while (!(state == State.Sta1 || state == State.Sta13)) 478 decoder.nextPDU(); 479 } catch (AAbort aa) { 480 abort(aa); 481 } catch (IOException e) { 482 onIOException(e); 483 } finally { 484 onClose(); 485 } 486 } finally { 487 device.decrementNumberOfOpenAssociations(); 488 } 489 } 490 }); 491 } 492 493 private void onClose() { 494 stopTimeout(); 495 synchronized (rspHandlerForMsgId) { 496 IntHashMap.Visitor<DimseRSPHandler> visitor = 497 new IntHashMap.Visitor<DimseRSPHandler>() { 498 499 @Override 500 public boolean visit(int key, DimseRSPHandler value) { 501 value.onClose(Association.this); 502 return true; 503 } 504 }; 505 rspHandlerForMsgId.accept(visitor); 506 rspHandlerForMsgId.clear(); 507 rspHandlerForMsgId.notifyAll(); 508 } 509 if (ae != null) 510 ae.getDevice().getAssociationHandler().onClose(this); 511 } 512 513 void onAAssociateRQ(AAssociateRQ rq) throws IOException { 514 name = rq.getCalledAET() + delim() + rq.getCallingAET() + '(' + serialNo + ')'; 515 LOG.info("{} >> A-ASSOCIATE-RQ", name); 516 LOG.debug("{}", rq); 517 stopTimeout(); 518 state.onAAssociateRQ(this, rq); 519 } 520 521 void handle(AAssociateRQ rq) throws IOException { 522 this.rq = rq; 523 enterState(State.Sta3); 524 try { 525 ae = device.getApplicationEntity(rq.getCalledAET()); 526 ac = device.getAssociationHandler().negotiate(this, rq); 527 initPCMap(); 528 maxOpsInvoked = ac.getMaxOpsPerformed(); 529 maxPDULength = Association.minZeroAsMax( 530 rq.getMaxPDULength(), conn.getSendPDULength()); 531 write(ac); 532 } catch (AAssociateRJ e) { 533 write(e); 534 } 535 } 536 537 void onAAssociateAC(AAssociateAC ac) throws IOException { 538 LOG.info("{} >> A-ASSOCIATE-AC", name); 539 LOG.debug("{}", ac); 540 stopTimeout(); 541 state.onAAssociateAC(this, ac); 542 } 543 544 void handle(AAssociateAC ac) throws IOException { 545 this.ac = ac; 546 initPCMap(); 547 maxOpsInvoked = ac.getMaxOpsInvoked(); 548 maxPDULength = Association.minZeroAsMax( 549 ac.getMaxPDULength(), conn.getSendPDULength()); 550 enterState(State.Sta6); 551 startIdleTimeout(); 552 } 553 554 void onAAssociateRJ(AAssociateRJ rj) throws IOException { 555 LOG.info("{} >> {}", name, rj); 556 state.onAAssociateRJ(this, rj); 557 } 558 559 void handle(AAssociateRJ rq) { 560 ex = rq; 561 closeSocket(); 562 } 563 564 void onAReleaseRQ() throws IOException { 565 LOG.info("{} >> A-RELEASE-RQ", name); 566 stopTimeout(); 567 state.onAReleaseRQ(this); 568 } 569 570 void handleAReleaseRQ() throws IOException { 571 enterState(State.Sta8); 572 waitForPerformingOps(); 573 LOG.info("{} << A-RELEASE-RP", name); 574 encoder.writeAReleaseRP(); 575 closeSocketDelayed(); 576 } 577 578 private synchronized void waitForPerformingOps() { 579 while (performing > 0 && state == State.Sta8) { 580 try { 581 wait(); 582 } catch (InterruptedException e) { 583 Thread.currentThread().interrupt(); 584 } 585 } 586 } 587 588 void handleAReleaseRQCollision() throws IOException { 589 if (isRequestor()) { 590 enterState(State.Sta9); 591 LOG.info("{} << A-RELEASE-RP", name); 592 encoder.writeAReleaseRP(); 593 enterState(State.Sta11); 594 } else { 595 enterState(State.Sta10); 596 } 597 } 598 599 void onAReleaseRP() throws IOException { 600 LOG.info("{} >> A-RELEASE-RP", name); 601 stopTimeout(); 602 state.onAReleaseRP(this); 603 } 604 605 void handleAReleaseRP() throws IOException { 606 closeSocket(); 607 } 608 609 void handleAReleaseRPCollision() throws IOException { 610 enterState(State.Sta12); 611 LOG.info("{} << A-RELEASE-RP", name); 612 encoder.writeAReleaseRP(); 613 closeSocketDelayed(); 614 } 615 616 void onAAbort(AAbort aa) { 617 LOG.info("{} >> {}", name, aa); 618 stopTimeout(); 619 ex = aa; 620 closeSocket(); 621 } 622 623 void unexpectedPDU(String pdu) throws AAbort { 624 LOG.warn("{} >> unexpected {} in state: {}", 625 new Object[] { name, pdu, state }); 626 throw new AAbort(AAbort.UL_SERIVE_PROVIDER, AAbort.UNEXPECTED_PDU); 627 } 628 629 void onPDataTF() throws IOException { 630 state.onPDataTF(this); 631 } 632 633 void handlePDataTF() throws IOException { 634 decoder.decodeDIMSE(); 635 } 636 637 void writePDataTF() throws IOException { 638 checkException(); 639 state.writePDataTF(this); 640 } 641 642 void doWritePDataTF() throws IOException { 643 encoder.writePDataTF(); 644 } 645 646 void onDimseRQ(PresentationContext pc, Dimse dimse, Attributes cmd, 647 PDVInputStream data) throws IOException { 648 stopTimeout(); 649 incPerforming(); 650 ae.onDimseRQ(this, pc, dimse, cmd, data); 651 } 652 653 private synchronized void incPerforming() { 654 ++performing; 655 } 656 657 private synchronized void decPerforming() { 658 --performing; 659 notifyAll(); 660 } 661 662 void onDimseRSP(Dimse dimse, Attributes cmd, Attributes data) throws AAbort { 663 int msgId = cmd.getInt(Tag.MessageIDBeingRespondedTo, -1); 664 int status = cmd.getInt(Tag.Status, 0); 665 boolean pending = Status.isPending(status); 666 DimseRSPHandler rspHandler = getDimseRSPHandler(msgId); 667 if (rspHandler == null) { 668 Dimse.LOG.info("{}: unexpected message ID in DIMSE RSP:", name); 669 Dimse.LOG.info("\n{}", cmd); 670 throw new AAbort(); 671 } 672 rspHandler.onDimseRSP(this, cmd, data); 673 if (pending) 674 startTimeout(msgId, dimse.isRetrieveRQ() 675 ? conn.getRetrieveTimeout() 676 : conn.getResponseTimeout()); 677 else { 678 removeDimseRSPHandler(msgId); 679 if (rspHandlerForMsgId.isEmpty() && performing == 0) 680 startIdleOrReleaseTimeout(); 681 } 682 } 683 684 private synchronized void startIdleOrReleaseTimeout() { 685 if (state == State.Sta6) 686 startIdleTimeout(); 687 else if (state == State.Sta7) 688 startReleaseTimeout(); 689 } 690 691 private void addDimseRSPHandler(DimseRSPHandler rspHandler) 692 throws InterruptedException { 693 synchronized (rspHandlerForMsgId) { 694 while (maxOpsInvoked > 0 695 && rspHandlerForMsgId.size() >= maxOpsInvoked) 696 rspHandlerForMsgId.wait(); 697 rspHandlerForMsgId.put(rspHandler.getMessageID(), rspHandler); 698 } 699 } 700 701 private DimseRSPHandler getDimseRSPHandler(int msgId) { 702 synchronized (rspHandlerForMsgId ) { 703 return rspHandlerForMsgId.get(msgId); 704 } 705 } 706 707 private DimseRSPHandler removeDimseRSPHandler(int msgId) { 708 synchronized (rspHandlerForMsgId ) { 709 DimseRSPHandler tmp = rspHandlerForMsgId.remove(msgId); 710 rspHandlerForMsgId.notifyAll(); 711 return tmp; 712 } 713 } 714 715 void cancel(PresentationContext pc, int msgId) throws IOException { 716 Attributes cmd = Commands.mkCCancelRQ(msgId); 717 encoder.writeDIMSE(pc, cmd, null); 718 } 719 720 public boolean tryWriteDimseRSP(PresentationContext pc, Attributes cmd) { 721 return tryWriteDimseRSP(pc, cmd, null); 722 } 723 724 public boolean tryWriteDimseRSP(PresentationContext pc, Attributes cmd, 725 Attributes data) { 726 try { 727 writeDimseRSP(pc, cmd, data); 728 return true; 729 } catch (IOException e) { 730 LOG.warn("{} << {} failed: {}", new Object[] { 731 this, 732 Dimse.valueOf(cmd.getInt(Tag.CommandField, 0)), 733 e.getMessage() }); 734 return false; 735 } 736 } 737 738 public void writeDimseRSP(PresentationContext pc, Attributes cmd) 739 throws IOException { 740 writeDimseRSP(pc, cmd, null); 741 } 742 743 public void writeDimseRSP(PresentationContext pc, Attributes cmd, 744 Attributes data) throws IOException { 745 DataWriter writer = null; 746 int datasetType = Commands.NO_DATASET; 747 if (data != null) { 748 writer = new DataWriterAdapter(data); 749 datasetType = Commands.getWithDatasetType(); 750 } 751 cmd.setInt(Tag.CommandDataSetType, VR.US, datasetType); 752 encoder.writeDIMSE(pc, cmd, writer); 753 if (!Status.isPending(cmd.getInt(Tag.Status, 0))) { 754 decPerforming(); 755 startIdleTimeout(); 756 } 757 } 758 759 void onCancelRQ(Attributes cmd) throws IOException { 760 int msgId = cmd.getInt(Tag.MessageIDBeingRespondedTo, -1); 761 CancelRQHandler handler = removeCancelRQHandler(msgId); 762 if (handler != null) 763 handler.onCancelRQ(this); 764 } 765 766 public void addCancelRQHandler(int msgId, CancelRQHandler handler) { 767 synchronized (cancelHandlerForMsgId) { 768 cancelHandlerForMsgId.put(msgId, handler); 769 } 770 } 771 772 public CancelRQHandler removeCancelRQHandler(int msgId) { 773 synchronized (cancelHandlerForMsgId) { 774 return cancelHandlerForMsgId.remove(msgId); 775 } 776 } 777 778 private void initPCMap() { 779 for (PresentationContext pc : ac.getPresentationContexts()) 780 if (pc.isAccepted()) 781 initTSMap(rq.getPresentationContext(pc.getPCID()) 782 .getAbstractSyntax()) 783 .put(pc.getTransferSyntax(), pc); 784 } 785 786 private HashMap<String, PresentationContext> initTSMap(String as) { 787 HashMap<String, PresentationContext> tsMap = pcMap.get(as); 788 if (tsMap == null) 789 pcMap.put(as, tsMap = new HashMap<String, PresentationContext>()); 790 return tsMap; 791 } 792 793 private PresentationContext pcFor(String cuid, String tsuid) 794 throws NoPresentationContextException { 795 HashMap<String, PresentationContext> tsMap = pcMap.get(cuid); 796 if (tsMap == null) 797 throw new NoPresentationContextException(cuid); 798 if (tsuid == null) 799 return tsMap.values().iterator().next(); 800 PresentationContext pc = tsMap.get(tsuid); 801 if (pc == null) 802 throw new NoPresentationContextException(cuid, tsuid); 803 return pc; 804 } 805 806 public Set<String> getTransferSyntaxesFor(String cuid) { 807 HashMap<String, PresentationContext> tsMap = pcMap.get(cuid); 808 if (tsMap == null) 809 return Collections.emptySet(); 810 return Collections.unmodifiableSet(tsMap.keySet()); 811 } 812 813 PresentationContext getPresentationContext(int pcid) { 814 return ac.getPresentationContext(pcid); 815 } 816 817 public CommonExtendedNegotiation getCommonExtendedNegotiationFor( 818 String cuid) { 819 return ac.getCommonExtendedNegotiationFor(cuid); 820 } 821 822 public void cstore(String cuid, String iuid, int priority, DataWriter data, 823 String tsuid, DimseRSPHandler rspHandler) throws IOException, 824 InterruptedException { 825 cstore(cuid, cuid, iuid, priority, data, tsuid, rspHandler); 826 } 827 828 public void cstore(String asuid, String cuid, String iuid, int priority, 829 DataWriter data, String tsuid, DimseRSPHandler rspHandler) 830 throws IOException, InterruptedException { 831 PresentationContext pc = pcFor(asuid, tsuid); 832 checkIsSCU(cuid); 833 Attributes cstorerq = Commands.mkCStoreRQ(rspHandler.getMessageID(), 834 cuid, iuid, priority); 835 invoke(pc, cstorerq, data, rspHandler, conn.getResponseTimeout()); 836 } 837 838 public DimseRSP cstore(String cuid, String iuid, int priority, 839 DataWriter data, String tsuid) 840 throws IOException, InterruptedException { 841 return cstore(cuid, cuid, iuid, priority, data, tsuid); 842 } 843 844 public DimseRSP cstore(String asuid, String cuid, String iuid, 845 int priority, DataWriter data, String tsuid) 846 throws IOException, InterruptedException { 847 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 848 cstore(asuid, cuid, iuid, priority, data, tsuid, rsp); 849 return rsp; 850 } 851 852 public void cstore(String cuid, String iuid, int priority, 853 String moveOriginatorAET, int moveOriginatorMsgId, 854 DataWriter data, String tsuid, DimseRSPHandler rspHandler) 855 throws IOException, InterruptedException { 856 cstore(cuid, cuid, iuid, priority, moveOriginatorAET, 857 moveOriginatorMsgId, data, tsuid, rspHandler); 858 } 859 860 public void cstore(String asuid, String cuid, String iuid, int priority, 861 String moveOriginatorAET, int moveOriginatorMsgId, 862 DataWriter data, String tsuid, DimseRSPHandler rspHandler) 863 throws IOException, InterruptedException { 864 PresentationContext pc = pcFor(asuid, tsuid); 865 Attributes cstorerq = Commands.mkCStoreRQ(rspHandler.getMessageID(), 866 cuid, iuid, priority, moveOriginatorAET, moveOriginatorMsgId); 867 invoke(pc, cstorerq, data, rspHandler, conn.getResponseTimeout()); 868 } 869 870 public DimseRSP cstore(String cuid, String iuid, int priority, 871 String moveOriginatorAET, int moveOriginatorMsgId, 872 DataWriter data, String tsuid) throws IOException, 873 InterruptedException { 874 return cstore(cuid, cuid, iuid, priority, moveOriginatorAET, 875 moveOriginatorMsgId, data, tsuid); 876 } 877 878 public DimseRSP cstore(String asuid, String cuid, String iuid, 879 int priority, String moveOriginatorAET, int moveOriginatorMsgId, 880 DataWriter data, String tsuid) throws IOException, 881 InterruptedException { 882 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 883 cstore(asuid, cuid, iuid, priority, moveOriginatorAET, 884 moveOriginatorMsgId, data, tsuid, rsp); 885 return rsp; 886 } 887 888 public void cfind(String cuid, int priority, Attributes data, 889 String tsuid, DimseRSPHandler rspHandler) throws IOException, 890 InterruptedException { 891 cfind(cuid, cuid, priority, data, tsuid, rspHandler); 892 } 893 894 public void cfind(String asuid, String cuid, int priority, 895 Attributes data, String tsuid, DimseRSPHandler rspHandler) 896 throws IOException, InterruptedException { 897 PresentationContext pc = pcFor(asuid, tsuid); 898 checkIsSCU(cuid); 899 Attributes cfindrq = 900 Commands.mkCFindRQ(rspHandler.getMessageID(), cuid, priority); 901 invoke(pc, cfindrq, new DataWriterAdapter(data), rspHandler, 902 conn.getResponseTimeout()); 903 } 904 905 public DimseRSP cfind(String cuid, int priority, Attributes data, 906 String tsuid, int autoCancel) throws IOException, 907 InterruptedException { 908 return cfind(cuid, cuid, priority, data, tsuid, autoCancel); 909 } 910 911 public DimseRSP cfind(String asuid, String cuid, int priority, 912 Attributes data, String tsuid, int autoCancel) throws IOException, 913 InterruptedException { 914 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 915 rsp.setAutoCancel(autoCancel); 916 cfind(asuid, cuid, priority, data, tsuid, rsp); 917 return rsp; 918 } 919 920 public void cget(String cuid, int priority, Attributes data, 921 String tsuid, DimseRSPHandler rspHandler) 922 throws IOException, InterruptedException { 923 cget(cuid, cuid, priority, data, tsuid, rspHandler); 924 } 925 926 public void cget(String asuid, String cuid, int priority, 927 Attributes data, String tsuid, DimseRSPHandler rspHandler) 928 throws IOException, 929 InterruptedException { 930 PresentationContext pc = pcFor(asuid, tsuid); 931 checkIsSCU(cuid); 932 Attributes cgetrq = Commands.mkCGetRQ(rspHandler.getMessageID(), 933 cuid, priority); 934 invoke(pc, cgetrq, new DataWriterAdapter(data), rspHandler, 935 conn.getRetrieveTimeout()); 936 } 937 938 public DimseRSP cget(String cuid, int priority, Attributes data, 939 String tsuid) throws IOException, 940 InterruptedException { 941 return cget(cuid, cuid, priority, data, tsuid); 942 } 943 944 public DimseRSP cget(String asuid, String cuid, int priority, 945 Attributes data, String tsuid) 946 throws IOException, InterruptedException { 947 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 948 cget(asuid, cuid, priority, data, tsuid, rsp); 949 return rsp; 950 } 951 952 public void cmove(String cuid, int priority, Attributes data, 953 String tsuid, String destination, DimseRSPHandler rspHandler) 954 throws IOException, InterruptedException { 955 cmove(cuid, cuid, priority, data, tsuid, destination, rspHandler); 956 } 957 958 public void cmove(String asuid, String cuid, int priority, 959 Attributes data, String tsuid, String destination, 960 DimseRSPHandler rspHandler) throws IOException, 961 InterruptedException { 962 PresentationContext pc = pcFor(asuid, tsuid); 963 checkIsSCU(cuid); 964 Attributes cmoverq = Commands.mkCMoveRQ(rspHandler.getMessageID(), 965 cuid, priority, destination); 966 invoke(pc, cmoverq, new DataWriterAdapter(data), rspHandler, 967 conn.getRetrieveTimeout()); 968 } 969 970 public DimseRSP cmove(String cuid, int priority, Attributes data, 971 String tsuid, String destination) throws IOException, 972 InterruptedException { 973 return cmove(cuid, cuid, priority, data, tsuid, destination); 974 } 975 976 public DimseRSP cmove(String asuid, String cuid, int priority, 977 Attributes data, String tsuid, String destination) 978 throws IOException, InterruptedException { 979 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 980 cmove(asuid, cuid, priority, data, tsuid, destination, rsp); 981 return rsp; 982 } 983 984 public DimseRSP cecho() throws IOException, InterruptedException { 985 return cecho(UID.VerificationSOPClass); 986 } 987 988 public DimseRSP cecho(String cuid) throws IOException, InterruptedException { 989 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 990 PresentationContext pc = pcFor(cuid, null); 991 checkIsSCU(cuid); 992 Attributes cechorq = Commands.mkCEchoRQ(rsp.getMessageID(), cuid); 993 invoke(pc, cechorq, null, rsp, conn.getResponseTimeout()); 994 return rsp; 995 } 996 997 public void neventReport(String cuid, String iuid, int eventTypeId, 998 Attributes data, String tsuid, DimseRSPHandler rspHandler) 999 throws IOException, InterruptedException { 1000 neventReport(cuid, cuid, iuid, eventTypeId, data, tsuid, rspHandler); 1001 } 1002 1003 public void neventReport(String asuid, String cuid, String iuid, int eventTypeId, 1004 Attributes data, String tsuid, DimseRSPHandler rspHandler) 1005 throws IOException, InterruptedException { 1006 PresentationContext pc = pcFor(asuid, tsuid); 1007 checkIsSCP(cuid); 1008 Attributes neventrq = 1009 Commands.mkNEventReportRQ(rspHandler.getMessageID(), cuid, iuid, 1010 eventTypeId, data); 1011 invoke(pc, neventrq, DataWriterAdapter.forAttributes(data), rspHandler, 1012 conn.getResponseTimeout()); 1013 } 1014 1015 public DimseRSP neventReport(String cuid, String iuid, int eventTypeId, 1016 Attributes data, String tsuid) throws IOException, 1017 InterruptedException { 1018 return neventReport(cuid, cuid, iuid, eventTypeId, data, tsuid); 1019 } 1020 1021 public DimseRSP neventReport(String asuid, String cuid, String iuid, 1022 int eventTypeId, Attributes data, String tsuid) 1023 throws IOException, InterruptedException { 1024 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1025 neventReport(asuid, cuid, iuid, eventTypeId, data, tsuid, rsp); 1026 return rsp; 1027 } 1028 1029 public void nget(String cuid, String iuid, int[] tags, 1030 DimseRSPHandler rspHandler) 1031 throws IOException, InterruptedException { 1032 nget(cuid, cuid, iuid, tags, rspHandler); 1033 } 1034 1035 public void nget(String asuid, String cuid, String iuid, int[] tags, 1036 DimseRSPHandler rspHandler) 1037 throws IOException, InterruptedException { 1038 PresentationContext pc = pcFor(asuid, null); 1039 checkIsSCU(cuid); 1040 Attributes ngetrq = 1041 Commands.mkNGetRQ(rspHandler.getMessageID(), cuid, iuid, tags); 1042 invoke(pc, ngetrq, null, rspHandler, conn.getResponseTimeout()); 1043 } 1044 1045 public DimseRSP nget(String cuid, String iuid, int[] tags) 1046 throws IOException, InterruptedException { 1047 return nget(cuid, cuid, iuid, tags); 1048 } 1049 1050 public DimseRSP nget(String asuid, String cuid, String iuid, int[] tags) 1051 throws IOException, InterruptedException { 1052 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1053 nget(asuid, cuid, iuid, tags, rsp); 1054 return rsp; 1055 } 1056 1057 public void nset(String cuid, String iuid, Attributes data, 1058 String tsuid, DimseRSPHandler rspHandler) 1059 throws IOException, InterruptedException { 1060 nset(cuid, cuid, iuid, new DataWriterAdapter(data), tsuid, rspHandler); 1061 } 1062 1063 public void nset(String asuid, String cuid, String iuid, 1064 Attributes data, String tsuid, DimseRSPHandler rspHandler) 1065 throws IOException, InterruptedException { 1066 nset(asuid, cuid, iuid, new DataWriterAdapter(data), tsuid, rspHandler); 1067 } 1068 1069 public DimseRSP nset(String cuid, String iuid, Attributes data, 1070 String tsuid) throws IOException, 1071 InterruptedException { 1072 return nset(cuid, cuid, iuid, new DataWriterAdapter(data), tsuid); 1073 } 1074 1075 public DimseRSP nset(String asuid, String cuid, String iuid, 1076 Attributes data, String tsuid) 1077 throws IOException, InterruptedException { 1078 return nset(asuid, cuid, iuid, new DataWriterAdapter(data), tsuid); 1079 } 1080 1081 public void nset(String cuid, String iuid, DataWriter data, 1082 String tsuid, DimseRSPHandler rspHandler) 1083 throws IOException, InterruptedException { 1084 nset(cuid, cuid, iuid, data, tsuid, rspHandler); 1085 } 1086 1087 public void nset(String asuid, String cuid, String iuid, 1088 DataWriter data, String tsuid, DimseRSPHandler rspHandler) 1089 throws IOException, InterruptedException { 1090 PresentationContext pc = pcFor(asuid, tsuid); 1091 checkIsSCU(cuid); 1092 Attributes nsetrq = 1093 Commands.mkNSetRQ(rspHandler.getMessageID(), cuid, iuid); 1094 invoke(pc, nsetrq, data, rspHandler, conn.getResponseTimeout()); 1095 } 1096 1097 public DimseRSP nset(String cuid, String iuid, DataWriter data, 1098 String tsuid) throws IOException, 1099 InterruptedException { 1100 return nset(cuid, cuid, iuid, data, tsuid); 1101 } 1102 1103 public DimseRSP nset(String asuid, String cuid, String iuid, 1104 DataWriter data, String tsuid) 1105 throws IOException, InterruptedException { 1106 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1107 nset(asuid, cuid, iuid, data, tsuid, rsp); 1108 return rsp; 1109 } 1110 1111 public void naction(String cuid, String iuid, int actionTypeId, 1112 Attributes data, String tsuid, DimseRSPHandler rspHandler) 1113 throws IOException, InterruptedException { 1114 naction(cuid, cuid, iuid, actionTypeId, data, tsuid, rspHandler); 1115 } 1116 1117 public void naction(String asuid, String cuid, String iuid, int actionTypeId, 1118 Attributes data, String tsuid, DimseRSPHandler rspHandler) 1119 throws IOException, InterruptedException { 1120 PresentationContext pc = pcFor(asuid, tsuid); 1121 checkIsSCU(cuid); 1122 Attributes nactionrq = 1123 Commands.mkNActionRQ(rspHandler.getMessageID(), cuid, iuid, 1124 actionTypeId, data); 1125 invoke(pc, nactionrq, DataWriterAdapter.forAttributes(data), rspHandler, 1126 conn.getResponseTimeout()); 1127 } 1128 1129 public DimseRSP naction(String cuid, String iuid, int actionTypeId, 1130 Attributes data, String tsuid) throws IOException, 1131 InterruptedException { 1132 return naction(cuid, cuid, iuid, actionTypeId, data, tsuid); 1133 } 1134 1135 public DimseRSP naction(String asuid, String cuid, String iuid, 1136 int actionTypeId, Attributes data, String tsuid) 1137 throws IOException, InterruptedException { 1138 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1139 naction(asuid, cuid, iuid, actionTypeId, data, tsuid, rsp); 1140 return rsp; 1141 } 1142 1143 public void ncreate(String cuid, String iuid, Attributes data, 1144 String tsuid, DimseRSPHandler rspHandler) 1145 throws IOException, InterruptedException { 1146 ncreate(cuid, cuid, iuid, data, tsuid, rspHandler); 1147 } 1148 1149 public void ncreate(String asuid, String cuid, String iuid, 1150 Attributes data, String tsuid, DimseRSPHandler rspHandler) 1151 throws IOException, InterruptedException { 1152 PresentationContext pc = pcFor(asuid, tsuid); 1153 checkIsSCU(cuid); 1154 Attributes ncreaterq = 1155 Commands.mkNCreateRQ(rspHandler.getMessageID(), cuid, iuid); 1156 invoke(pc, ncreaterq, DataWriterAdapter.forAttributes(data), rspHandler, 1157 conn.getResponseTimeout()); 1158 } 1159 1160 public DimseRSP ncreate(String cuid, String iuid, Attributes data, 1161 String tsuid) throws IOException, 1162 InterruptedException { 1163 return ncreate(cuid, cuid, iuid, data, tsuid); 1164 } 1165 1166 public DimseRSP ncreate(String asuid, String cuid, String iuid, 1167 Attributes data, String tsuid) 1168 throws IOException, InterruptedException { 1169 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1170 ncreate(asuid, cuid, iuid, data, tsuid, rsp); 1171 return rsp; 1172 } 1173 1174 public void ndelete(String cuid, String iuid, DimseRSPHandler rspHandler) 1175 throws IOException, InterruptedException { 1176 ndelete(cuid, cuid, iuid, rspHandler); 1177 } 1178 1179 public void ndelete(String asuid, String cuid, String iuid, 1180 DimseRSPHandler rspHandler) 1181 throws IOException, InterruptedException { 1182 PresentationContext pc = pcFor(asuid, null); 1183 checkIsSCU(cuid); 1184 Attributes ndeleterq = 1185 Commands.mkNDeleteRQ(rspHandler.getMessageID(), cuid, iuid); 1186 invoke(pc, ndeleterq, null, rspHandler, conn.getResponseTimeout()); 1187 } 1188 1189 public DimseRSP ndelete(String cuid, String iuid) 1190 throws IOException, InterruptedException { 1191 return ndelete(cuid, cuid, iuid); 1192 } 1193 1194 public DimseRSP ndelete(String asuid, String cuid, String iuid) 1195 throws IOException, InterruptedException { 1196 FutureDimseRSP rsp = new FutureDimseRSP(nextMessageID()); 1197 ndelete(asuid, cuid, iuid, rsp); 1198 return rsp; 1199 } 1200 1201 private void invoke(PresentationContext pc, Attributes cmd, 1202 DataWriter data, DimseRSPHandler rspHandler, int rspTimeout) 1203 throws IOException, InterruptedException { 1204 stopTimeout(); 1205 checkException(); 1206 rspHandler.setPC(pc); 1207 addDimseRSPHandler(rspHandler); 1208 startTimeout(rspHandler.getMessageID(), rspTimeout); 1209 encoder.writeDIMSE(pc, cmd, data); 1210 } 1211 1212 static int minZeroAsMax(int i1, int i2) { 1213 return i1 == 0 ? i2 : i2 == 0 ? i1 : Math.min(i1, i2); 1214 } 1215 1216 public Attributes createFileMetaInformation(String iuid, String cuid, 1217 String tsuid) { 1218 Attributes fmi = new Attributes(7); 1219 fmi.setBytes(Tag.FileMetaInformationVersion, VR.OB, new byte[] { 0, 1 }); 1220 fmi.setString(Tag.MediaStorageSOPClassUID, VR.UI, cuid); 1221 fmi.setString(Tag.MediaStorageSOPInstanceUID, VR.UI, iuid); 1222 fmi.setString(Tag.TransferSyntaxUID, VR.UI, tsuid); 1223 fmi.setString(Tag.ImplementationClassUID, VR.UI, 1224 getRemoteImplClassUID()); 1225 String versionName = getRemoteImplVersionName(); 1226 if (versionName != null) 1227 fmi.setString(Tag.ImplementationVersionName, VR.SH, versionName); 1228 fmi.setString(Tag.SourceApplicationEntityTitle, VR.SH, 1229 getRemoteAET()); 1230 return fmi; 1231 } 1232 1233 /** 1234 * Releases the association in a graceful way. 1235 * <ul> 1236 * <li>makes sure the current state is eligible for calling a release() before doing so</li> 1237 * <li>waits for outstanding RSP before release</li> 1238 * <li>handles exceptions and logs detected issues</li> 1239 * </ul> 1240 * 1241 * This method will not throw exceptions normally. 1242 */ 1243 public void releaseGracefully() { 1244 if (isReadyForDataTransfer()) { 1245 1246 try { 1247 waitForOutstandingRSP(); 1248 } catch (InterruptedException ignored) { 1249 // if we get interrupted while trying to close the association, most likely we still want to close it 1250 Thread.currentThread().interrupt(); 1251 LOG.warn("Interrupted while preparing to close the association, will try to release the association anyway: " + this.toString(), ignored); 1252 } 1253 1254 try { 1255 release(); 1256 } catch (IOException e) { 1257 LOG.warn("Failed to release association to " + getRemoteAET(), e); 1258 } 1259 1260 } else { 1261 LOG.warn("Attempted to close the association, but it was not ready for data transfer", new IOException("Association not ready for data transfer")); 1262 } 1263 } 1264} 1265