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 org.dcm4che3.conf.core.api.ConfigurableClass; 042import org.dcm4che3.conf.core.api.ConfigurableProperty; 043import org.dcm4che3.conf.core.api.ConfigurableProperty.ConfigurablePropertyType; 044import org.dcm4che3.conf.core.api.ConfigurableProperty.Tag; 045import org.dcm4che3.conf.core.api.LDAP; 046import org.dcm4che3.conf.core.api.Parent; 047import org.dcm4che3.data.Attributes; 048import org.dcm4che3.net.pdu.*; 049import org.dcm4che3.util.StringUtils; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import java.io.IOException; 054import java.io.Serializable; 055import java.net.Socket; 056import java.security.GeneralSecurityException; 057import java.util.*; 058 059/** 060 * DICOM Part 15, Annex H compliant description of a DICOM network service. 061 * <p/> 062 * A Network AE is an application entity that provides services on a network. A 063 * Network AE will have the 16 same functional capability regardless of the 064 * particular network connection used. If there are functional differences based 065 * on selected network connection, then these are separate Network AEs. If there 066 * are 18 functional differences based on other internal structures, then these 067 * are separate Network AEs. 068 * 069 * @author Gunter Zeilinger <gunterze@gmail.com> 070 */ 071@LDAP(objectClasses = {"dcmNetworkAE", "dicomNetworkAE"}, distinguishingField = "dicomAETitle") 072@ConfigurableClass(referable = true) 073public class ApplicationEntity implements Serializable { 074 075 private static final long serialVersionUID = 3883790997057469573L; 076 077 protected static final Logger LOG = LoggerFactory.getLogger(ApplicationEntity.class); 078 079 080 @ConfigurableProperty(name = "dicomAETitle", tags = Tag.PRIMARY) 081 private String AETitle; 082 083 @ConfigurableProperty(type = ConfigurablePropertyType.UUID, description = "An immutable unique identifier") 084 private String uuid = UUID.randomUUID().toString(); 085 086 @ConfigurableProperty(name = "dicomDescription") 087 private String description; 088 089 @ConfigurableProperty(type = ConfigurablePropertyType.OptimisticLockingHash) 090 private String olockHash; 091 092 @ConfigurableProperty(name = "dicomVendorData") 093 private byte[][] vendorData = {}; 094 095 @ConfigurableProperty(name = "dicomApplicationCluster") 096 private String[] applicationClusters = {}; 097 098 @ConfigurableProperty(name = "dicomPreferredCalledAETitle") 099 private String[] preferredCalledAETitles = {}; 100 101 @ConfigurableProperty(name = "dicomPreferredCallingAETitle") 102 private String[] preferredCallingAETitles = {}; 103 104 @ConfigurableProperty(name = "dicomSupportedCharacterSet") 105 private String[] supportedCharacterSets = {}; 106 107 @ConfigurableProperty(name = "dicomInstalled") 108 private Boolean aeInstalled; 109 110 @ConfigurableProperty(name = "dcmAcceptedCallingAETitle") 111 private final Set<String> acceptedCallingAETitlesSet = 112 new LinkedHashSet<String>(); 113 114 // Connections are dereferenced by DicomConfiguration 115 @ConfigurableProperty(name = "dicomNetworkConnectionReference", collectionOfReferences = true, tags = Tag.PRIMARY) 116 private final List<Connection> connections = new ArrayList<Connection>(1); 117 118 /** 119 * "Proxy" property, actually forwards everything to scuTCs and scpTCs in its setter/getter 120 */ 121 @LDAP(noContainerNode = true) 122 @ConfigurableProperty(name = "dcmTransferCapability", 123 description = "DICOM Transfer Capabilities", 124 tags = Tag.PRIMARY) 125 private Collection<TransferCapability> transferCapabilities; 126 127 // populated/collected by transferCapabilities' setter/getter 128 private final Map<String, TransferCapability> scuTCs = 129 new TreeMap<String, TransferCapability>(); 130 131 // populated/collected by transferCapabilities' setter/getter 132 private final Map<String, TransferCapability> scpTCs = 133 new TreeMap<String, TransferCapability>(); 134 135 @ConfigurableProperty(name = "aeExtensions", isExtensionsProperty = true) 136 private Map<Class<? extends AEExtension>, AEExtension> extensions = 137 new HashMap<Class<? extends AEExtension>, AEExtension>(); 138 139 @ConfigurableProperty(name = "dicomAssociationAcceptor") 140 private boolean associationAcceptor = true; 141 142 @ConfigurableProperty(name = "dicomAssociationInitiator") 143 private boolean associationInitiator = true; 144 145 @ConfigurableProperty(name = "dcmAETitleAliases", 146 label = "Aliases (alternative AE titles)") 147 private List<String> AETitleAliases = new ArrayList<String>(); 148 149 @Parent 150 private Device device; 151 152 private transient DimseRQHandler dimseRQHandler; 153 154 public ApplicationEntity() { 155 } 156 157 public ApplicationEntity(String aeTitle) { 158 setAETitle(aeTitle); 159 } 160 161 public List<String> getAETitleAliases() { 162 return new ArrayList<String>(AETitleAliases); 163 } 164 165 public void setAETitleAliases(List<String> AETitleAliases) { 166 this.AETitleAliases = AETitleAliases; 167 } 168 169 public Map<Class<? extends AEExtension>, AEExtension> getExtensions() { 170 return extensions; 171 } 172 173 public void setExtensions(Map<Class<? extends AEExtension>, AEExtension> extensions) { 174 this.extensions = extensions; 175 } 176 177 public String getUuid() { 178 return uuid; 179 } 180 181 public void setUuid(String uuid) { 182 this.uuid = uuid; 183 } 184 185 public void setTransferCapabilities(Collection<TransferCapability> transferCapabilities) { 186 scpTCs.clear(); 187 scuTCs.clear(); 188 189 for (TransferCapability tc : transferCapabilities) { 190 tc.setApplicationEntity(this); 191 switch (tc.getRole()) { 192 case SCP: 193 scpTCs.put(tc.getSopClass(), tc); 194 break; 195 case SCU: 196 scuTCs.put(tc.getSopClass(), tc); 197 } 198 } 199 } 200 201 public Collection<TransferCapability> getTransferCapabilities() { 202 ArrayList<TransferCapability> tcs = 203 new ArrayList<TransferCapability>(scuTCs.size() + scpTCs.size()); 204 tcs.addAll(scpTCs.values()); 205 tcs.addAll(scuTCs.values()); 206 return tcs; 207 } 208 209 /** 210 * Get the device that is identified by this application entity. 211 * 212 * @return The owning <code>Device</code>. 213 */ 214 public Device getDevice() { 215 return device; 216 } 217 218 /** 219 * Set the device that is identified by this application entity. 220 * 221 * @param device The owning <code>Device</code>. 222 */ 223 public void setDevice(Device device) { 224 if (device != null) { 225 if (this.device != null && this.device != device) 226 throw new IllegalStateException("already owned by " + this.device.getDeviceName()); 227 for (Connection conn : connections) 228 if (conn.getDevice() != device) 229 throw new IllegalStateException(conn + " not owned by " + 230 device.getDeviceName()); 231 } 232 this.device = device; 233 } 234 235 /** 236 * Get the AE title for this Network AE. 237 * <p/> 238 * <p/> 239 * Please note that there could also be alias AE titles for the same AE. You 240 * can get them via {@link #getAETitleAliases()}. 241 * 242 * @return A String containing the AE title. 243 */ 244 public final String getAETitle() { 245 return AETitle; 246 } 247 248 /** 249 * Set the AE title for this Network AE. 250 * 251 * @param aet A String containing the AE title. 252 */ 253 public void setAETitle(String aet) { 254 if (aet.isEmpty()) 255 throw new IllegalArgumentException("AE title cannot be empty"); 256 Device device = this.device; 257 if (device != null && this.AETitle != null) 258 device.removeApplicationEntity(this.AETitle); 259 this.AETitle = aet; 260 if (device != null) 261 device.addApplicationEntity(this); 262 } 263 264 /** 265 * Get the description of this network AE 266 * 267 * @return A String containing the description. 268 */ 269 public final String getDescription() { 270 return description; 271 } 272 273 /** 274 * Set a description of this network AE. 275 * 276 * @param description A String containing the description. 277 */ 278 public final void setDescription(String description) { 279 this.description = description; 280 } 281 282 /** 283 * Get any vendor information or configuration specific to this network AE. 284 * 285 * @return An Object of the vendor data. 286 */ 287 public final byte[][] getVendorData() { 288 return vendorData; 289 } 290 291 /** 292 * Set any vendor information or configuration specific to this network AE 293 * 294 * @param vendorData An Object of the vendor data. 295 */ 296 public final void setVendorData(byte[]... vendorData) { 297 this.vendorData = vendorData; 298 } 299 300 /** 301 * Get the locally defined names for a subset of related applications. E.g. 302 * neuroradiology. 303 * 304 * @return A String[] containing the names. 305 */ 306 public String[] getApplicationClusters() { 307 return applicationClusters; 308 } 309 310 public void setApplicationClusters(String... clusters) { 311 applicationClusters = clusters; 312 } 313 314 /** 315 * Get the AE Title(s) that are preferred for initiating associations 316 * from this network AE. 317 * 318 * @return A String[] of the preferred called AE titles. 319 */ 320 public String[] getPreferredCalledAETitles() { 321 return preferredCalledAETitles; 322 } 323 324 public void setPreferredCalledAETitles(String... aets) { 325 preferredCalledAETitles = aets; 326 } 327 328 /** 329 * Get the AE title(s) that are preferred for accepting associations by 330 * this network AE. 331 * 332 * @return A String[] containing the preferred calling AE titles. 333 */ 334 public String[] getPreferredCallingAETitles() { 335 return preferredCallingAETitles; 336 } 337 338 public void setPreferredCallingAETitles(String... aets) { 339 preferredCallingAETitles = aets; 340 } 341 342 public String[] getAcceptedCallingAETitles() { 343 return acceptedCallingAETitlesSet.toArray( 344 new String[acceptedCallingAETitlesSet.size()]); 345 } 346 347 public void setAcceptedCallingAETitles(String... aets) { 348 acceptedCallingAETitlesSet.clear(); 349 for (String name : aets) 350 acceptedCallingAETitlesSet.add(name); 351 } 352 353 public boolean isAcceptedCallingAETitle(String aet) { 354 return acceptedCallingAETitlesSet.isEmpty() 355 || acceptedCallingAETitlesSet.contains(aet); 356 } 357 358 /** 359 * Get the Character Set(s) supported by the Network AE for data sets it 360 * receives. The value shall be selected from the Defined Terms for Specific 361 * Character Set (0008,0005) in PS3.3. If no values are present, this 362 * implies that the Network AE supports only the default character 363 * repertoire (ISO IR 6). 364 * 365 * @return A String array of the supported character sets. 366 */ 367 public String[] getSupportedCharacterSets() { 368 return supportedCharacterSets; 369 } 370 371 /** 372 * Set the Character Set(s) supported by the Network AE for data sets it 373 * receives. The value shall be selected from the Defined Terms for Specific 374 * Character Set (0008,0005) in PS3.3. If no values are present, this 375 * implies that the Network AE supports only the default character 376 * repertoire (ISO IR 6). 377 * 378 * @param characterSets A String array of the supported character sets. 379 */ 380 public void setSupportedCharacterSets(String... characterSets) { 381 supportedCharacterSets = characterSets; 382 } 383 384 /** 385 * Determine whether or not this network AE can accept associations. 386 * 387 * @return A boolean value. True if the Network AE can accept associations, 388 * false otherwise. 389 */ 390 public final boolean isAssociationAcceptor() { 391 return associationAcceptor; 392 } 393 394 /** 395 * Set whether or not this network AE can accept associations. 396 * 397 * @param acceptor A boolean value. True if the Network AE can accept 398 * associations, false otherwise. 399 */ 400 public final void setAssociationAcceptor(boolean acceptor) { 401 this.associationAcceptor = acceptor; 402 } 403 404 /** 405 * Determine whether or not this network AE can initiate associations. 406 * 407 * @return A boolean value. True if the Network AE can accept associations, 408 * false otherwise. 409 */ 410 public final boolean isAssociationInitiator() { 411 return associationInitiator; 412 } 413 414 /** 415 * Set whether or not this network AE can initiate associations. 416 * 417 * @param initiator A boolean value. True if the Network AE can accept 418 * associations, false otherwise. 419 */ 420 public final void setAssociationInitiator(boolean initiator) { 421 this.associationInitiator = initiator; 422 } 423 424 425 /** 426 * Determine whether or not this network AE is installed on a network. 427 * 428 * @return A Boolean value. True if the AE is installed on a network. If not 429 * present, information about the installed status of the AE is 430 * inherited from the device 431 */ 432 public boolean isInstalled() { 433 return device != null && device.isInstalled() 434 && (aeInstalled == null || aeInstalled.booleanValue()); 435 } 436 437 public Boolean getAeInstalled() { 438 return aeInstalled; 439 } 440 441 442 /** 443 * Set whether or not this network AE is installed on a network. 444 * 445 * @param aeInstalled A Boolean value. True if the AE is installed on a network. 446 * If not present, information about the installed status of 447 * the AE is inherited from the device 448 */ 449 public void setAeInstalled(Boolean aeInstalled) { 450 this.aeInstalled = aeInstalled; 451 } 452 453 public DimseRQHandler getDimseRQHandler() { 454 DimseRQHandler handler = dimseRQHandler; 455 if (handler != null) 456 return handler; 457 458 Device device = this.device; 459 return device != null 460 ? device.getDimseRQHandler() 461 : null; 462 } 463 464 public final void setDimseRQHandler(DimseRQHandler dimseRQHandler) { 465 this.dimseRQHandler = dimseRQHandler; 466 } 467 468 private void checkInstalled() { 469 if (!isInstalled()) 470 throw new IllegalStateException("Not installed"); 471 } 472 473 private void checkDevice() { 474 if (device == null) 475 throw new IllegalStateException("Not attached to Device"); 476 } 477 478 void onDimseRQ(Association as, PresentationContext pc, Dimse cmd, 479 Attributes cmdAttrs, PDVInputStream data) throws IOException { 480 DimseRQHandler tmp = getDimseRQHandler(); 481 if (tmp == null) { 482 LOG.error("DimseRQHandler not initalized"); 483 throw new AAbort(); 484 } 485 tmp.onDimseRQ(as, pc, cmd, cmdAttrs, data); 486 } 487 488 public void addConnection(Connection conn) { 489 if (conn.getProtocol() != Connection.Protocol.DICOM) 490 throw new IllegalArgumentException( 491 "protocol != DICOM - " + conn.getProtocol()); 492 493 if (device != null && device != conn.getDevice()) 494 throw new IllegalStateException(conn + " not contained by Device: " + 495 device.getDeviceName()); 496 connections.add(conn); 497 } 498 499 public boolean removeConnection(Connection conn) { 500 return connections.remove(conn); 501 } 502 503 public void setConnections(List<Connection> connections) { 504 this.connections.clear(); 505 for (Connection connection : connections) addConnection(connection); 506 } 507 508 public List<Connection> getConnections() { 509 return connections; 510 } 511 512 public TransferCapability addTransferCapability(TransferCapability tc) { 513 tc.setApplicationEntity(this); 514 TransferCapability prev = (tc.getRole() == TransferCapability.Role.SCU 515 ? scuTCs : scpTCs).put(tc.getSopClass(), tc); 516 if (prev != null && prev != tc) 517 prev.setApplicationEntity(null); 518 return prev; 519 } 520 521 public TransferCapability removeTransferCapabilityFor(String sopClass, 522 TransferCapability.Role role) { 523 TransferCapability tc = (role == TransferCapability.Role.SCU ? scuTCs : scpTCs) 524 .remove(sopClass); 525 if (tc != null) 526 tc.setApplicationEntity(null); 527 return tc; 528 } 529 530 public Collection<TransferCapability> getTransferCapabilitiesWithRole( 531 TransferCapability.Role role) { 532 return (role == TransferCapability.Role.SCU ? scuTCs : scpTCs).values(); 533 } 534 535 public TransferCapability getTransferCapabilityFor( 536 String sopClass, TransferCapability.Role role) { 537 return (role == TransferCapability.Role.SCU ? scuTCs : scpTCs).get(sopClass); 538 } 539 540 protected PresentationContext negotiate(AAssociateRQ rq, AAssociateAC ac, 541 PresentationContext rqpc) { 542 String as = rqpc.getAbstractSyntax(); 543 TransferCapability tc = roleSelection(rq, ac, as); 544 int pcid = rqpc.getPCID(); 545 if (tc == null) 546 return new PresentationContext(pcid, 547 PresentationContext.ABSTRACT_SYNTAX_NOT_SUPPORTED, 548 rqpc.getTransferSyntax()); 549 550 for (String ts : rqpc.getTransferSyntaxes()) 551 if (tc.containsTransferSyntax(ts)) { 552 byte[] info = negotiate(rq.getExtNegotiationFor(as), tc); 553 if (info != null) 554 ac.addExtendedNegotiation(new ExtendedNegotiation(as, info)); 555 return new PresentationContext(pcid, 556 PresentationContext.ACCEPTANCE, ts); 557 } 558 559 return new PresentationContext(pcid, 560 PresentationContext.TRANSFER_SYNTAX_NOT_SUPPORTED, 561 rqpc.getTransferSyntax()); 562 } 563 564 private TransferCapability roleSelection(AAssociateRQ rq, 565 AAssociateAC ac, String asuid) { 566 RoleSelection rqrs = rq.getRoleSelectionFor(asuid); 567 if (rqrs == null) 568 return getTC(scpTCs, asuid, rq); 569 570 RoleSelection acrs = ac.getRoleSelectionFor(asuid); 571 if (acrs != null) 572 return getTC(acrs.isSCU() ? scpTCs : scuTCs, asuid, rq); 573 574 TransferCapability tcscu = null; 575 TransferCapability tcscp = null; 576 boolean scu = rqrs.isSCU() 577 && (tcscp = getTC(scpTCs, asuid, rq)) != null; 578 boolean scp = rqrs.isSCP() 579 && (tcscu = getTC(scuTCs, asuid, rq)) != null; 580 ac.addRoleSelection(new RoleSelection(asuid, scu, scp)); 581 return scu ? tcscp : tcscu; 582 } 583 584 private TransferCapability getTC(Map<String, TransferCapability> tcs, 585 String asuid, AAssociateRQ rq) { 586 TransferCapability tc = tcs.get(asuid); 587 if (tc != null) 588 return tc; 589 590 CommonExtendedNegotiation commonExtNeg = 591 rq.getCommonExtendedNegotiationFor(asuid); 592 if (commonExtNeg != null) { 593 for (String cuid : commonExtNeg.getRelatedGeneralSOPClassUIDs()) { 594 tc = tcs.get(cuid); 595 if (tc != null) 596 return tc; 597 } 598 tc = tcs.get(commonExtNeg.getServiceClassUID()); 599 if (tc != null) 600 return tc; 601 } 602 603 return tcs.get("*"); 604 } 605 606 private byte[] negotiate(ExtendedNegotiation exneg, TransferCapability tc) { 607 if (exneg == null) 608 return null; 609 610 StorageOptions storageOptions = tc.getStorageOptions(); 611 if (storageOptions != null) 612 return storageOptions.toExtendedNegotiationInformation(); 613 614 EnumSet<QueryOption> queryOptions = tc.getQueryOptions(); 615 if (queryOptions != null) { 616 EnumSet<QueryOption> commonOpts = QueryOption.toOptions(exneg); 617 commonOpts.retainAll(queryOptions); 618 return QueryOption.toExtendedNegotiationInformation(commonOpts); 619 } 620 return null; 621 } 622 623 public Association connect(Connection local, Connection remote, AAssociateRQ rq) 624 throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException { 625 checkDevice(); 626 checkInstalled(); 627 if (rq.getCallingAET() == null) 628 rq.setCallingAET(AETitle); 629 rq.setMaxOpsInvoked(local.getMaxOpsInvoked()); 630 rq.setMaxOpsPerformed(local.getMaxOpsPerformed()); 631 rq.setMaxPDULength(local.getReceivePDULength()); 632 633 final Socket sock = local.connect(remote); // automatically closes the socket in case an exception is thrown 634 635 Association as; 636 try { 637 as = new Association(this, local, sock); 638 } catch (final IOException e) { 639 LOG.warn("Failed to open new association, will close underlying socket"); 640 local.close(sock); 641 throw e; 642 } 643 try { 644 as.write(rq); 645 as.waitForLeaving(State.Sta5); 646 } catch (final IOException e) { 647 LOG.warn("{}: Failed to write A-ASSOCIATE-RQ, will abort association", as.toString()); 648 as.abort(); 649 throw e; 650 } catch (final InterruptedException e) { 651 LOG.warn("{}: Interrupted while waiting to leave state Sta 5, will abort association", as.toString()); 652 as.abort(); 653 throw e; 654 } 655 return as; 656 } 657 658 public Association connect(Connection remote, AAssociateRQ rq) 659 throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException { 660 return connect(findCompatibelConnection(remote), remote, rq); 661 } 662 663 public Connection findCompatibelConnection(Connection remoteConn) 664 throws IncompatibleConnectionException { 665 for (Connection conn : connections) 666 if (conn.isInstalled() && conn.isCompatible(remoteConn)) 667 return conn; 668 throw new IncompatibleConnectionException( 669 "No compatible connection to " + remoteConn + " available on " + this); 670 } 671 672 public CompatibleConnection findCompatibelConnection(ApplicationEntity remote) 673 throws IncompatibleConnectionException { 674 CompatibleConnection cc = null; 675 for (Connection remoteConn : remote.connections) 676 if (remoteConn.isInstalled() && remoteConn.isServer()) 677 for (Connection conn : connections) 678 if (conn.isInstalled() && conn.isCompatible(remoteConn)) { 679 if (cc == null 680 || conn.isTls() 681 || conn.getProtocol() == Connection.Protocol.SYSLOG_TLS) 682 cc = new CompatibleConnection(conn, remoteConn); 683 } 684 if (cc == null) 685 throw new IncompatibleConnectionException( 686 "No compatible connection to " + remote + " available on " + this); 687 return cc; 688 } 689 690 public Association connect(ApplicationEntity remote, AAssociateRQ rq) 691 throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException { 692 CompatibleConnection cc = findCompatibelConnection(remote); 693 if (rq.getCalledAET() == null) 694 rq.setCalledAET(remote.getAETitle()); 695 return connect(cc.getLocalConnection(), cc.getRemoteConnection(), rq); 696 } 697 698 @Override 699 public String toString() { 700 return promptTo(new StringBuilder(512), "").toString(); 701 } 702 703 public StringBuilder promptTo(StringBuilder sb, String indent) { 704 String indent2 = indent + " "; 705 StringUtils.appendLine(sb, indent, "ApplicationEntity[title: ", AETitle); 706 StringUtils.appendLine(sb, indent2, "alias titles: ", AETitleAliases); 707 StringUtils.appendLine(sb, indent2, "desc: ", description); 708 StringUtils.appendLine(sb, indent2, "acceptor: ", associationAcceptor); 709 StringUtils.appendLine(sb, indent2, "initiator: ", associationInitiator); 710 StringUtils.appendLine(sb, indent2, "installed: ", getAeInstalled()); 711 for (Connection conn : connections) 712 conn.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR); 713 for (TransferCapability tc : getTransferCapabilities()) 714 tc.promptTo(sb, indent2).append(StringUtils.LINE_SEPARATOR); 715 return sb.append(indent).append(']'); 716 } 717 718 void reconfigure(ApplicationEntity src) { 719 setApplicationEntityAttributes(src); 720 device.reconfigureConnections(connections, src.connections); 721 reconfigureTransferCapabilities(src); 722 reconfigureAEExtensions(src); 723 } 724 725 private void reconfigureTransferCapabilities(ApplicationEntity src) { 726 scuTCs.clear(); 727 scuTCs.putAll(src.scuTCs); 728 scpTCs.clear(); 729 scpTCs.putAll(src.scpTCs); 730 } 731 732 private void reconfigureAEExtensions(ApplicationEntity from) { 733 for (Iterator<Class<? extends AEExtension>> it = 734 extensions.keySet().iterator(); it.hasNext(); ) { 735 if (!from.extensions.containsKey(it.next())) 736 it.remove(); 737 } 738 for (AEExtension src : from.extensions.values()) { 739 Class<? extends AEExtension> clazz = src.getClass(); 740 AEExtension ext = extensions.get(clazz); 741 if (ext == null) 742 try { 743 addAEExtension(ext = clazz.newInstance()); 744 } catch (Exception e) { 745 throw new RuntimeException( 746 "Failed to instantiate " + clazz.getName(), e); 747 } 748 ext.reconfigure(src); 749 } 750 } 751 752 protected void setApplicationEntityAttributes(ApplicationEntity from) { 753 setOlockHash(from.olockHash); 754 setDescription(from.description); 755 setAETitleAliases(from.getAETitleAliases()); 756 setVendorData(from.vendorData); 757 setApplicationClusters(from.applicationClusters); 758 setPreferredCalledAETitles(from.preferredCalledAETitles); 759 setPreferredCallingAETitles(from.preferredCallingAETitles); 760 setAcceptedCallingAETitles(from.getAcceptedCallingAETitles()); 761 setSupportedCharacterSets(from.supportedCharacterSets); 762 setAssociationAcceptor(from.associationAcceptor); 763 setAssociationInitiator(from.associationInitiator); 764 setAeInstalled(from.aeInstalled); 765 setUuid(from.getUuid()); 766 } 767 768 public Set<String> getAcceptedCallingAETitlesSet() { 769 return acceptedCallingAETitlesSet; 770 } 771 772 public void setAcceptedCallingAETitlesSet(Set<String> acceptedCallingAETitlesSet) { 773 this.acceptedCallingAETitlesSet.clear(); 774 if (acceptedCallingAETitlesSet != null) 775 this.acceptedCallingAETitlesSet.addAll(acceptedCallingAETitlesSet); 776 } 777 778 public void addAEExtension(AEExtension ext) { 779 Class<? extends AEExtension> clazz = ext.getClass(); 780 if (extensions.containsKey(clazz)) 781 throw new IllegalStateException( 782 "already contains AE Extension:" + clazz); 783 784 ext.setApplicationEntity(this); 785 extensions.put(clazz, ext); 786 } 787 788 public boolean removeAEExtension(AEExtension ext) { 789 if (extensions.remove(ext.getClass()) == null) 790 return false; 791 792 ext.setApplicationEntity(null); 793 return true; 794 } 795 796 public String getOlockHash() { 797 return olockHash; 798 } 799 800 public void setOlockHash(String olockHash) { 801 this.olockHash = olockHash; 802 } 803 804 public Collection<AEExtension> listAEExtensions() { 805 return extensions.values(); 806 } 807 808 @SuppressWarnings("unchecked") 809 public <T extends AEExtension> T getAEExtension(Class<T> clazz) { 810 return (T) extensions.get(clazz); 811 } 812 813 public <T extends AEExtension> T getAEExtensionNotNull(Class<T> clazz) { 814 T aeExt = getAEExtension(clazz); 815 if (aeExt == null) 816 throw new IllegalStateException("No " + clazz.getName() 817 + " configured for AE: " + AETitle); 818 return aeExt; 819 } 820 821 public boolean supportsTransferCapability( 822 TransferCapability transferCapability, boolean onlyAbstractSyntax) { 823 TransferCapability matchingTC = this.getTransferCapabilityFor( 824 transferCapability.getSopClass(), transferCapability.getRole()); 825 if (matchingTC == null) 826 return false; 827 else 828 for (String ts : transferCapability.getTransferSyntaxes()) 829 if (!matchingTC.containsTransferSyntax(ts) 830 && !onlyAbstractSyntax) 831 return false; 832 833 return true; 834 } 835 836}