001/* ***** BEGIN LICENSE BLOCK ***** 002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1 003 * 004 * The contents of this file are subject to the Mozilla Public License Version 005 * 1.1 (the "License"); you may not use this file except in compliance with 006 * the License. You may obtain a copy of the License at 007 * http://www.mozilla.org/MPL/ 008 * 009 * Software distributed under the License is distributed on an "AS IS" basis, 010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 011 * for the specific language governing rights and limitations under the 012 * License. 013 * 014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in 015 * Java(TM), hosted at https://github.com/gunterze/dcm4che. 016 * 017 * The Initial Developer of the Original Code is 018 * Agfa Healthcare. 019 * Portions created by the Initial Developer are Copyright (C) 2011 020 * the Initial Developer. All Rights Reserved. 021 * 022 * Contributor(s): 023 * See @authors listed below 024 * 025 * Alternatively, the contents of this file may be used under the terms of 026 * either the GNU General Public License Version 2 or later (the "GPL"), or 027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 028 * in which case the provisions of the GPL or the LGPL are applicable instead 029 * of those above. If you wish to allow use of your version of this file only 030 * under the terms of either the GPL or the LGPL, and not to allow others to 031 * use your version of this file under the terms of the MPL, indicate your 032 * decision by deleting the provisions above and replace them with the notice 033 * and other provisions required by the GPL or the LGPL. If you do not delete 034 * the provisions above, a recipient may use your version of this file under 035 * the terms of any one of the MPL, the GPL or the LGPL. 036 * 037 * ***** END LICENSE BLOCK ***** */ 038 039package org.dcm4che3.tool.dcmqrscp; 040 041import java.io.File; 042import java.io.IOException; 043import java.util.ArrayList; 044import java.util.EnumSet; 045import java.util.HashMap; 046import java.util.List; 047import java.util.Map; 048import java.util.Properties; 049import java.util.ResourceBundle; 050import java.util.concurrent.ExecutorService; 051import java.util.concurrent.Executors; 052import java.util.concurrent.ScheduledExecutorService; 053 054import org.apache.commons.cli.CommandLine; 055import org.apache.commons.cli.MissingOptionException; 056import org.apache.commons.cli.OptionBuilder; 057import org.apache.commons.cli.Options; 058import org.apache.commons.cli.ParseException; 059import org.dcm4che3.data.Attributes; 060import org.dcm4che3.data.Tag; 061import org.dcm4che3.data.UID; 062import org.dcm4che3.media.DicomDirReader; 063import org.dcm4che3.media.DicomDirWriter; 064import org.dcm4che3.media.RecordFactory; 065import org.dcm4che3.net.ApplicationEntity; 066import org.dcm4che3.net.Association; 067import org.dcm4che3.net.Connection; 068import org.dcm4che3.net.Device; 069import org.dcm4che3.net.Dimse; 070import org.dcm4che3.net.QueryOption; 071import org.dcm4che3.net.Status; 072import org.dcm4che3.net.TransferCapability; 073import org.dcm4che3.net.pdu.AAssociateRQ; 074import org.dcm4che3.net.pdu.ExtendedNegotiation; 075import org.dcm4che3.net.pdu.PresentationContext; 076import org.dcm4che3.net.service.BasicCEchoSCP; 077import org.dcm4che3.net.service.BasicCFindSCP; 078import org.dcm4che3.net.service.BasicCGetSCP; 079import org.dcm4che3.net.service.BasicCMoveSCP; 080import org.dcm4che3.net.service.BasicCStoreSCU; 081import org.dcm4che3.net.service.BasicRetrieveTask; 082import org.dcm4che3.net.service.CStoreSCU; 083import org.dcm4che3.net.service.DicomServiceException; 084import org.dcm4che3.net.service.DicomServiceRegistry; 085import org.dcm4che3.net.service.InstanceLocator; 086import org.dcm4che3.net.service.QueryRetrieveLevel; 087import org.dcm4che3.net.service.QueryTask; 088import org.dcm4che3.net.service.RetrieveTask; 089import org.dcm4che3.tool.common.CLIUtils; 090import org.dcm4che3.tool.common.FilesetInfo; 091import org.dcm4che3.util.AttributesFormat; 092import org.dcm4che3.util.StringUtils; 093import org.dcm4che3.util.UIDUtils; 094import org.slf4j.Logger; 095import org.slf4j.LoggerFactory; 096 097/** 098 * @author Gunter Zeilinger <gunterze@gmail.com> 099 * 100 */ 101public class DcmQRSCP<T extends InstanceLocator> { 102 103 static final Logger LOG = LoggerFactory.getLogger(DcmQRSCP.class); 104 105 private static final String[] PATIENT_ROOT_LEVELS = { "PATIENT", "STUDY", 106 "SERIES", "IMAGE" }; 107 private static final String[] STUDY_ROOT_LEVELS = { "STUDY", "SERIES", 108 "IMAGE" }; 109 private static final String[] PATIENT_STUDY_ONLY_LEVELS = { "PATIENT", 110 "STUDY" }; 111 private static ResourceBundle rb = ResourceBundle 112 .getBundle("org.dcm4che3.tool.dcmqrscp.messages"); 113 114 private Device device = new Device("dcmqrscp"); 115 private ApplicationEntity ae = new ApplicationEntity("*"); 116 private final Connection conn = new Connection(); 117 118 private File storageDir; 119 private File dicomDir; 120 private AttributesFormat filePathFormat; 121 private RecordFactory recFact; 122 private String availability; 123 private boolean stgCmtOnSameAssoc; 124 private boolean sendPendingCGet; 125 private int sendPendingCMoveInterval; 126 private final FilesetInfo fsInfo = new FilesetInfo(); 127 private DicomDirReader ddReader; 128 private DicomDirWriter ddWriter; 129 private HashMap<String, Connection> remoteConnections = new HashMap<String, Connection>(); 130 131 private final class CFindSCPImpl extends BasicCFindSCP { 132 133 private final String[] qrLevels; 134 private final QueryRetrieveLevel rootLevel; 135 136 public CFindSCPImpl(String sopClass, String... qrLevels) { 137 super(sopClass); 138 this.qrLevels = qrLevels; 139 this.rootLevel = QueryRetrieveLevel.valueOf(qrLevels[0]); 140 } 141 142 @Override 143 protected QueryTask calculateMatches(Association as, 144 PresentationContext pc, Attributes rq, Attributes keys) 145 throws DicomServiceException { 146 QueryRetrieveLevel level = QueryRetrieveLevel.valueOf(keys, 147 qrLevels); 148 level.validateQueryKeys(keys, rootLevel, 149 rootLevel == QueryRetrieveLevel.IMAGE || relational(as, rq)); 150 DicomDirReader ddr = getDicomDirReader(); 151 String availability = getInstanceAvailability(); 152 switch (level) { 153 case PATIENT: 154 return new PatientQueryTask(as, pc, rq, keys, ddr, availability); 155 case STUDY: 156 return new StudyQueryTask(as, pc, rq, keys, ddr, availability); 157 case SERIES: 158 return new SeriesQueryTask(as, pc, rq, keys, ddr, availability); 159 case IMAGE: 160 return new InstanceQueryTask(as, pc, rq, keys, ddr, 161 availability); 162 default: 163 assert true; 164 } 165 throw new AssertionError(); 166 } 167 168 private boolean relational(Association as, Attributes rq) { 169 String cuid = rq.getString(Tag.AffectedSOPClassUID); 170 ExtendedNegotiation extNeg = as.getAAssociateAC() 171 .getExtNegotiationFor(cuid); 172 return QueryOption.toOptions(extNeg).contains( 173 QueryOption.RELATIONAL); 174 } 175 } 176 177 private final class CGetSCPImpl extends BasicCGetSCP { 178 179 private final String[] qrLevels; 180 private final boolean withoutBulkData; 181 private final QueryRetrieveLevel rootLevel; 182 183 public CGetSCPImpl(String sopClass, String... qrLevels) { 184 super(sopClass); 185 this.qrLevels = qrLevels; 186 this.withoutBulkData = qrLevels.length == 0; 187 this.rootLevel = withoutBulkData ? QueryRetrieveLevel.IMAGE 188 : QueryRetrieveLevel.valueOf(qrLevels[0]); 189 } 190 191 @Override 192 protected RetrieveTask calculateMatches(Association as, 193 PresentationContext pc, Attributes rq, Attributes keys) 194 throws DicomServiceException { 195 QueryRetrieveLevel level = withoutBulkData ? QueryRetrieveLevel.IMAGE 196 : QueryRetrieveLevel.valueOf(keys, qrLevels); 197 level.validateRetrieveKeys(keys, rootLevel, relational(as, rq)); 198 List<T> matches = DcmQRSCP.this 199 .calculateMatches(keys); 200 if (matches.isEmpty()) 201 return null; 202 203 CStoreSCU<T> storescu = new CStoreSCUImpl<T>(withoutBulkData); 204 205 BasicRetrieveTask<T> retrieveTask = new BasicRetrieveTask<T>( 206 Dimse.C_GET_RQ, as, pc, rq, matches, as, storescu); 207 retrieveTask.setSendPendingRSP(isSendPendingCGet()); 208 return retrieveTask; 209 } 210 211 private boolean relational(Association as, Attributes rq) { 212 String cuid = rq.getString(Tag.AffectedSOPClassUID); 213 ExtendedNegotiation extNeg = as.getAAssociateAC() 214 .getExtNegotiationFor(cuid); 215 return QueryOption.toOptions(extNeg).contains( 216 QueryOption.RELATIONAL); 217 } 218 219 } 220 221 private final class CMoveSCPImpl extends BasicCMoveSCP { 222 223 private final String[] qrLevels; 224 private final QueryRetrieveLevel rootLevel; 225 226 public CMoveSCPImpl(String sopClass, String... qrLevels) { 227 super(sopClass); 228 this.qrLevels = qrLevels; 229 this.rootLevel = QueryRetrieveLevel.valueOf(qrLevels[0]); 230 } 231 232 @Override 233 protected RetrieveTask calculateMatches(Association as, 234 PresentationContext pc, final Attributes rq, Attributes keys) 235 throws DicomServiceException { 236 QueryRetrieveLevel level = QueryRetrieveLevel.valueOf(keys, 237 qrLevels); 238 level.validateRetrieveKeys(keys, rootLevel, relational(as, rq)); 239 String moveDest = rq.getString(Tag.MoveDestination); 240 final Connection remote = getRemoteConnection(moveDest); 241 if (remote == null) 242 throw new DicomServiceException(Status.MoveDestinationUnknown, 243 "Move Destination: " + moveDest + " unknown"); 244 List<T> matches = DcmQRSCP.this.calculateMatches(keys); 245 if (matches.isEmpty()) 246 return null; 247 248 AAssociateRQ aarq = makeAAssociateRQ(as.getLocalAET(), moveDest, 249 matches); 250 Association storeas = openStoreAssociation(as, remote, aarq); 251 252 BasicRetrieveTask<T> retrieveTask = new BasicRetrieveTask<T>( 253 Dimse.C_MOVE_RQ, as, pc, rq, matches, storeas, 254 new BasicCStoreSCU<T>()); 255 retrieveTask 256 .setSendPendingRSPInterval(getSendPendingCMoveInterval()); 257 return retrieveTask; 258 } 259 260 private Association openStoreAssociation(Association as, 261 Connection remote, AAssociateRQ aarq) 262 throws DicomServiceException { 263 try { 264 return as.getApplicationEntity().connect(as.getConnection(), 265 remote, aarq); 266 } catch (Exception e) { 267 throw new DicomServiceException( 268 Status.UnableToPerformSubOperations, e); 269 } 270 } 271 272 private AAssociateRQ makeAAssociateRQ(String callingAET, 273 String calledAET, List<T> matches) { 274 AAssociateRQ aarq = new AAssociateRQ(); 275 aarq.setCalledAET(calledAET); 276 aarq.setCallingAET(callingAET); 277 for (InstanceLocator match : matches) { 278 if (aarq.addPresentationContextFor(match.cuid, match.tsuid)) { 279 if (!UID.ExplicitVRLittleEndian.equals(match.tsuid)) 280 aarq.addPresentationContextFor(match.cuid, 281 UID.ExplicitVRLittleEndian); 282 if (!UID.ImplicitVRLittleEndian.equals(match.tsuid)) 283 aarq.addPresentationContextFor(match.cuid, 284 UID.ImplicitVRLittleEndian); 285 } 286 } 287 return aarq; 288 } 289 290 private boolean relational(Association as, Attributes rq) { 291 String cuid = rq.getString(Tag.AffectedSOPClassUID); 292 ExtendedNegotiation extNeg = as.getAAssociateAC() 293 .getExtNegotiationFor(cuid); 294 return QueryOption.toOptions(extNeg).contains( 295 QueryOption.RELATIONAL); 296 } 297 } 298 299 public DcmQRSCP() throws IOException { 300 device.addConnection(conn); 301 device.addApplicationEntity(ae); 302 ae.setAssociationAcceptor(true); 303 ae.addConnection(conn); 304 } 305 306 public void init() { 307 device.setDimseRQHandler(createServiceRegistry()); 308 } 309 310 protected void addCStoreSCPService(DicomServiceRegistry serviceRegistry ) { 311 serviceRegistry.addDicomService(new CStoreSCPImpl(ddWriter, filePathFormat, recFact)); 312 } 313 314 protected void addStgCmtSCPService(DicomServiceRegistry serviceRegistry ) { 315 serviceRegistry.addDicomService(new StgCmtSCPImpl(ddReader, remoteConnections, stgCmtOnSameAssoc, device.getExecutor())); 316 } 317 318 private DicomServiceRegistry createServiceRegistry() { 319 DicomServiceRegistry serviceRegistry = new DicomServiceRegistry(); 320 serviceRegistry.addDicomService(new BasicCEchoSCP()); 321 322 addCStoreSCPService(serviceRegistry); 323 addStgCmtSCPService(serviceRegistry); 324 325 serviceRegistry.addDicomService(new CFindSCPImpl( 326 UID.PatientRootQueryRetrieveInformationModelFIND, 327 PATIENT_ROOT_LEVELS)); 328 serviceRegistry.addDicomService(new CFindSCPImpl( 329 UID.StudyRootQueryRetrieveInformationModelFIND, 330 STUDY_ROOT_LEVELS)); 331 serviceRegistry.addDicomService(new CFindSCPImpl( 332 UID.PatientStudyOnlyQueryRetrieveInformationModelFINDRetired, 333 PATIENT_STUDY_ONLY_LEVELS)); 334 serviceRegistry.addDicomService(new CGetSCPImpl( 335 UID.PatientRootQueryRetrieveInformationModelGET, 336 PATIENT_ROOT_LEVELS)); 337 serviceRegistry.addDicomService(new CGetSCPImpl( 338 UID.StudyRootQueryRetrieveInformationModelGET, 339 STUDY_ROOT_LEVELS)); 340 serviceRegistry.addDicomService(new CGetSCPImpl( 341 UID.PatientStudyOnlyQueryRetrieveInformationModelGETRetired, 342 PATIENT_STUDY_ONLY_LEVELS)); 343 serviceRegistry.addDicomService(new CGetSCPImpl( 344 UID.CompositeInstanceRetrieveWithoutBulkDataGET)); 345 serviceRegistry.addDicomService(new CMoveSCPImpl( 346 UID.PatientRootQueryRetrieveInformationModelMOVE, 347 PATIENT_ROOT_LEVELS)); 348 serviceRegistry.addDicomService(new CMoveSCPImpl( 349 UID.StudyRootQueryRetrieveInformationModelMOVE, 350 STUDY_ROOT_LEVELS)); 351 serviceRegistry.addDicomService(new CMoveSCPImpl( 352 UID.PatientStudyOnlyQueryRetrieveInformationModelMOVERetired, 353 PATIENT_STUDY_ONLY_LEVELS)); 354 return serviceRegistry; 355 } 356 357 public final Device getDevice() { 358 return device; 359 } 360 361 public void setDevice(Device device) { 362 this.device = device; 363 } 364 365 public void setApplicationEntity(ApplicationEntity ae) { 366 this.ae = ae; 367 } 368 369 public final void setDicomDirectory(File dicomDir) { 370 File storageDir = dicomDir.getParentFile(); 371 if (storageDir.mkdirs()) 372 System.out.println("M-WRITE " + storageDir); 373 this.storageDir = storageDir; 374 this.dicomDir = dicomDir; 375 } 376 377 public final File getStorageDirectory() { 378 return storageDir; 379 } 380 381 public final AttributesFormat getFilePathFormat() { 382 return filePathFormat; 383 } 384 385 public void setFilePathFormat(String pattern) { 386 this.filePathFormat = new AttributesFormat(pattern); 387 } 388 389 public final File getDicomDirectory() { 390 return dicomDir; 391 } 392 393 public boolean isWriteable() { 394 return storageDir.canWrite(); 395 } 396 397 public final void setInstanceAvailability(String availability) { 398 this.availability = availability; 399 } 400 401 public final String getInstanceAvailability() { 402 return availability; 403 } 404 405 public boolean isStgCmtOnSameAssoc() { 406 return stgCmtOnSameAssoc; 407 } 408 409 public void setStgCmtOnSameAssoc(boolean stgCmtOnSameAssoc) { 410 this.stgCmtOnSameAssoc = stgCmtOnSameAssoc; 411 } 412 413 public final void setSendPendingCGet(boolean sendPendingCGet) { 414 this.sendPendingCGet = sendPendingCGet; 415 } 416 417 public final boolean isSendPendingCGet() { 418 return sendPendingCGet; 419 } 420 421 public final void setSendPendingCMoveInterval(int sendPendingCMoveInterval) { 422 this.sendPendingCMoveInterval = sendPendingCMoveInterval; 423 } 424 425 public final int getSendPendingCMoveInterval() { 426 return sendPendingCMoveInterval; 427 } 428 429 public final void setRecordFactory(RecordFactory recFact) { 430 this.recFact = recFact; 431 } 432 433 public final RecordFactory getRecordFactory() { 434 return recFact; 435 } 436 437 private static CommandLine parseComandLine(String[] args) 438 throws ParseException { 439 Options opts = new Options(); 440 CLIUtils.addFilesetInfoOptions(opts); 441 CLIUtils.addBindServerOption(opts); 442 CLIUtils.addConnectTimeoutOption(opts); 443 CLIUtils.addAcceptTimeoutOption(opts); 444 CLIUtils.addAEOptions(opts); 445 CLIUtils.addCommonOptions(opts); 446 CLIUtils.addResponseTimeoutOption(opts); 447 addDicomDirOption(opts); 448 addTransferCapabilityOptions(opts); 449 addInstanceAvailabilityOption(opts); 450 addStgCmtOptions(opts); 451 addSendingPendingOptions(opts); 452 addRemoteConnectionsOption(opts); 453 return CLIUtils.parseComandLine(args, opts, rb, DcmQRSCP.class); 454 } 455 456 @SuppressWarnings("static-access") 457 private static void addInstanceAvailabilityOption(Options opts) { 458 opts.addOption(OptionBuilder.hasArg().withArgName("code") 459 .withDescription(rb.getString("availability")) 460 .withLongOpt("availability").create()); 461 } 462 463 private static void addStgCmtOptions(Options opts) { 464 opts.addOption(null, "stgcmt-same-assoc", false, 465 rb.getString("stgcmt-same-assoc")); 466 } 467 468 @SuppressWarnings("static-access") 469 private static void addSendingPendingOptions(Options opts) { 470 opts.addOption(null, "pending-cget", false, 471 rb.getString("pending-cget")); 472 opts.addOption(OptionBuilder.hasArg().withArgName("s") 473 .withDescription(rb.getString("pending-cmove")) 474 .withLongOpt("pending-cmove").create()); 475 } 476 477 @SuppressWarnings("static-access") 478 private static void addDicomDirOption(Options opts) { 479 opts.addOption(OptionBuilder.hasArg().withArgName("file") 480 .withDescription(rb.getString("dicomdir")) 481 .withLongOpt("dicomdir").create()); 482 opts.addOption(OptionBuilder.hasArg().withArgName("pattern") 483 .withDescription(rb.getString("filepath")) 484 .withLongOpt("filepath").create(null)); 485 } 486 487 @SuppressWarnings("static-access") 488 private static void addTransferCapabilityOptions(Options opts) { 489 opts.addOption(null, "all-storage", false, rb.getString("all-storage")); 490 opts.addOption(null, "no-storage", false, rb.getString("no-storage")); 491 opts.addOption(null, "no-query", false, rb.getString("no-query")); 492 opts.addOption(null, "no-retrieve", false, rb.getString("no-retrieve")); 493 opts.addOption(null, "relational", false, rb.getString("relational")); 494 opts.addOption(OptionBuilder.hasArg().withArgName("file|url") 495 .withDescription(rb.getString("storage-sop-classes")) 496 .withLongOpt("storage-sop-classes").create()); 497 opts.addOption(OptionBuilder.hasArg().withArgName("file|url") 498 .withDescription(rb.getString("query-sop-classes")) 499 .withLongOpt("query-sop-classes").create()); 500 opts.addOption(OptionBuilder.hasArg().withArgName("file|url") 501 .withDescription(rb.getString("retrieve-sop-classes")) 502 .withLongOpt("retrieve-sop-classes").create()); 503 } 504 505 @SuppressWarnings("static-access") 506 private static void addRemoteConnectionsOption(Options opts) { 507 opts.addOption(OptionBuilder.hasArg().withArgName("file|url") 508 .withDescription(rb.getString("ae-config")) 509 .withLongOpt("ae-config").create()); 510 } 511 512 public static void main(String[] args) { 513 try { 514 CommandLine cl = parseComandLine(args); 515 DcmQRSCP<InstanceLocator> main = new DcmQRSCP<InstanceLocator>(); 516 CLIUtils.configure(main.fsInfo, cl); 517 CLIUtils.configureBindServer(main.conn, main.ae, cl); 518 CLIUtils.configure(main.conn, cl); 519 configureDicomFileSet(main, cl); 520 configureTransferCapability(main, cl); 521 configureInstanceAvailability(main, cl); 522 configureStgCmt(main, cl); 523 configureSendPending(main, cl); 524 configureRemoteConnections(main, cl); 525 ExecutorService executorService = Executors.newCachedThreadPool(); 526 ScheduledExecutorService scheduledExecutorService = Executors 527 .newSingleThreadScheduledExecutor(); 528 main.device.setScheduledExecutor(scheduledExecutorService); 529 main.device.setExecutor(executorService); 530 main.device.bindConnections(); 531 main.init(); 532 } catch (ParseException e) { 533 System.err.println("dcmqrscp: " + e.getMessage()); 534 System.err.println(rb.getString("try")); 535 System.exit(2); 536 } catch (Exception e) { 537 System.err.println("dcmqrscp: " + e.getMessage()); 538 e.printStackTrace(); 539 System.exit(2); 540 } 541 } 542 543 private static void configureDicomFileSet(DcmQRSCP<InstanceLocator> main, CommandLine cl) 544 throws ParseException { 545 if (!cl.hasOption("dicomdir")) 546 throw new MissingOptionException(rb.getString("missing-dicomdir")); 547 main.setDicomDirectory(new File(cl.getOptionValue("dicomdir"))); 548 main.setFilePathFormat(cl.getOptionValue("filepath", 549 "DICOM/{0020000D,hash}/{0020000E,hash}/{00080018,hash}")); 550 main.setRecordFactory(new RecordFactory()); 551 } 552 553 private static void configureInstanceAvailability(DcmQRSCP<InstanceLocator> main, 554 CommandLine cl) { 555 main.setInstanceAvailability(cl.getOptionValue("availability")); 556 } 557 558 private static void configureStgCmt(DcmQRSCP<InstanceLocator> main, CommandLine cl) { 559 main.setStgCmtOnSameAssoc(cl.hasOption("stgcmt-same-assoc")); 560 } 561 562 private static void configureSendPending(DcmQRSCP<InstanceLocator> main, CommandLine cl) { 563 main.setSendPendingCGet(cl.hasOption("pending-cget")); 564 if (cl.hasOption("pending-cmove")) 565 main.setSendPendingCMoveInterval(Integer.parseInt(cl 566 .getOptionValue("pending-cmove"))); 567 } 568 569 private static void configureTransferCapability(DcmQRSCP<InstanceLocator> main, 570 CommandLine cl) throws IOException { 571 ApplicationEntity ae = main.ae; 572 EnumSet<QueryOption> queryOptions = cl.hasOption("relational") ? EnumSet 573 .of(QueryOption.RELATIONAL) : EnumSet.noneOf(QueryOption.class); 574 boolean storage = !cl.hasOption("no-storage") && main.isWriteable(); 575 if (storage && cl.hasOption("all-storage")) { 576 TransferCapability tc = new TransferCapability(null, "*", 577 TransferCapability.Role.SCP, "*"); 578 tc.setQueryOptions(queryOptions); 579 ae.addTransferCapability(tc); 580 } else { 581 ae.addTransferCapability(new TransferCapability(null, 582 UID.VerificationSOPClass, TransferCapability.Role.SCP, 583 UID.ImplicitVRLittleEndian)); 584 Properties storageSOPClasses = CLIUtils.loadProperties(cl 585 .getOptionValue("storage-sop-classes", 586 "resource:storage-sop-classes.properties"), null); 587 if (storage) 588 addTransferCapabilities(ae, storageSOPClasses, 589 TransferCapability.Role.SCP, null); 590 if (!cl.hasOption("no-retrieve")) { 591 addTransferCapabilities(ae, storageSOPClasses, 592 TransferCapability.Role.SCU, null); 593 Properties p = CLIUtils.loadProperties(cl.getOptionValue( 594 "retrieve-sop-classes", 595 "resource:retrieve-sop-classes.properties"), null); 596 addTransferCapabilities(ae, p, TransferCapability.Role.SCP, 597 queryOptions); 598 } 599 if (!cl.hasOption("no-query")) { 600 Properties p = CLIUtils.loadProperties(cl.getOptionValue( 601 "query-sop-classes", 602 "resource:query-sop-classes.properties"), null); 603 addTransferCapabilities(ae, p, TransferCapability.Role.SCP, 604 queryOptions); 605 } 606 } 607 if (storage) 608 main.openDicomDir(); 609 else 610 main.openDicomDirForReadOnly(); 611 } 612 613 private static void addTransferCapabilities(ApplicationEntity ae, 614 Properties p, TransferCapability.Role role, 615 EnumSet<QueryOption> queryOptions) { 616 for (String cuid : p.stringPropertyNames()) { 617 String ts = p.getProperty(cuid); 618 TransferCapability tc = new TransferCapability(null, 619 CLIUtils.toUID(cuid), role, CLIUtils.toUIDs(ts)); 620 tc.setQueryOptions(queryOptions); 621 ae.addTransferCapability(tc); 622 } 623 } 624 625 private static void configureRemoteConnections(DcmQRSCP<InstanceLocator> main, CommandLine cl) 626 throws Exception { 627 String file = cl.getOptionValue("ae-config", "resource:ae.properties"); 628 Properties aeConfig = CLIUtils.loadProperties(file, null); 629 for (Map.Entry<Object, Object> entry : aeConfig.entrySet()) { 630 String aet = (String) entry.getKey(); 631 String value = (String) entry.getValue(); 632 try { 633 String[] hostPortCiphers = StringUtils.split(value, ':'); 634 String[] ciphers = new String[hostPortCiphers.length - 2]; 635 System.arraycopy(hostPortCiphers, 2, ciphers, 0, ciphers.length); 636 Connection remote = new Connection(); 637 remote.setHostname(hostPortCiphers[0]); 638 remote.setPort(Integer.parseInt(hostPortCiphers[1])); 639 remote.setTlsCipherSuites(ciphers); 640 main.addRemoteConnection(aet, remote); 641 } catch (Exception e) { 642 throw new IllegalArgumentException("Invalid entry in " + file 643 + ": " + aet + "=" + value); 644 } 645 } 646 } 647 648 final DicomDirReader getDicomDirReader() { 649 return ddReader; 650 } 651 652 public void setDicomDirReader(DicomDirReader ddReader) { 653 this.ddReader = ddReader; 654 } 655 656 final DicomDirWriter getDicomDirWriter() { 657 return ddWriter; 658 } 659 660 private void openDicomDir() throws IOException { 661 if (!dicomDir.exists()) 662 DicomDirWriter.createEmptyDirectory(dicomDir, 663 UIDUtils.createUIDIfNull(fsInfo.getFilesetUID()), 664 fsInfo.getFilesetID(), fsInfo.getDescriptorFile(), 665 fsInfo.getDescriptorFileCharset()); 666 ddReader = ddWriter = DicomDirWriter.open(dicomDir); 667 } 668 669 private void openDicomDirForReadOnly() throws IOException { 670 ddReader = new DicomDirReader(dicomDir); 671 } 672 673 public void addRemoteConnection(String aet, Connection remote) { 674 remoteConnections.put(aet, remote); 675 } 676 677 Connection getRemoteConnection(String dest) { 678 return remoteConnections.get(dest); 679 } 680 681 public List<T> calculateMatches(Attributes keys) 682 throws DicomServiceException { 683 try { 684 List<T> list = new ArrayList<T>(); 685 String[] patIDs = keys.getStrings(Tag.PatientID); 686 String[] studyIUIDs = keys.getStrings(Tag.StudyInstanceUID); 687 String[] seriesIUIDs = keys.getStrings(Tag.SeriesInstanceUID); 688 String[] sopIUIDs = keys.getStrings(Tag.SOPInstanceUID); 689 DicomDirReader ddr = ddReader; 690 Attributes patRec = ddr.findPatientRecord(patIDs); 691 while (patRec != null) { 692 Attributes studyRec = ddr.findStudyRecord(patRec, studyIUIDs); 693 while (studyRec != null) { 694 Attributes seriesRec = ddr.findSeriesRecord(studyRec, 695 seriesIUIDs); 696 while (seriesRec != null) { 697 Attributes instRec = ddr.findLowerInstanceRecord( 698 seriesRec, true, sopIUIDs); 699 while (instRec != null) { 700 String cuid = instRec 701 .getString(Tag.ReferencedSOPClassUIDInFile); 702 String iuid = instRec 703 .getString(Tag.ReferencedSOPInstanceUIDInFile); 704 String tsuid = instRec 705 .getString(Tag.ReferencedTransferSyntaxUIDInFile); 706 String[] fileIDs = instRec 707 .getStrings(Tag.ReferencedFileID); 708 String uri = ddr.toFile(fileIDs).toURI().toString(); 709 list.add((T)new InstanceLocator(cuid, iuid, tsuid, uri)); 710 if (sopIUIDs != null && sopIUIDs.length == 1) 711 break; 712 713 instRec = ddr.findNextInstanceRecord(instRec, true, 714 sopIUIDs); 715 } 716 if (seriesIUIDs != null && seriesIUIDs.length == 1) 717 break; 718 719 seriesRec = ddr.findNextSeriesRecord(seriesRec, 720 seriesIUIDs); 721 } 722 if (studyIUIDs != null && studyIUIDs.length == 1) 723 break; 724 725 studyRec = ddr.findNextStudyRecord(studyRec, studyIUIDs); 726 } 727 if (patIDs != null && patIDs.length == 1) 728 break; 729 730 patRec = ddr.findNextPatientRecord(patRec, patIDs); 731 } 732 return list; 733 } catch (IOException e) { 734 throw new DicomServiceException( 735 Status.UnableToCalculateNumberOfMatches, e); 736 } 737 } 738 739 public Connection getConnection() { 740 return conn; 741 } 742 743 public ApplicationEntity getApplicationEntity() { 744 return ae; 745 } 746 747}