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.stgcmtscu.test; 040 041import java.io.File; 042import java.io.FileNotFoundException; 043import java.io.IOException; 044import java.security.GeneralSecurityException; 045import java.util.Arrays; 046import java.util.Iterator; 047import java.util.concurrent.ExecutorService; 048import java.util.concurrent.Executors; 049import java.util.concurrent.ScheduledExecutorService; 050 051import org.dcm4che3.data.Attributes; 052import org.dcm4che3.data.Tag; 053import org.dcm4che3.data.UID; 054import org.dcm4che3.io.DicomOutputStream; 055import org.dcm4che3.net.ApplicationEntity; 056import org.dcm4che3.net.Association; 057import org.dcm4che3.net.AssociationStateException; 058import org.dcm4che3.net.Commands; 059import org.dcm4che3.net.Connection; 060import org.dcm4che3.net.Device; 061import org.dcm4che3.net.Dimse; 062import org.dcm4che3.net.IncompatibleConnectionException; 063import org.dcm4che3.net.Status; 064import org.dcm4che3.net.pdu.PresentationContext; 065import org.dcm4che3.net.service.AbstractDicomService; 066import org.dcm4che3.net.service.DicomService; 067import org.dcm4che3.net.service.DicomServiceException; 068import org.dcm4che3.tool.common.DicomFiles; 069import org.dcm4che3.tool.common.test.TestResult; 070import org.dcm4che3.tool.common.test.TestTool; 071import org.dcm4che3.tool.stgcmtscu.StgCmtSCU; 072import org.dcm4che3.util.SafeClose; 073import org.junit.Assert; 074 075/** 076 * @author Hesham Elbadawi <bsdreko@gmail.com> 077 */ 078public class StgCmtTool implements TestTool { 079 080 private final String host; 081 082 private final int port; 083 084 private final File baseDirectory; 085 086 private final File storageDirectory; 087 088 String aeTitle; 089 090 Device device; 091 092 StgCmtSCU stgCmtSCU ; 093 094 private final String sourceAETitle; 095 096 private Attributes nEventReqData = new Attributes(); 097 098 private int success; 099 100 private int fails; 101 102 private TestResult result; 103 104 Connection bound; 105 106 107 public StgCmtTool(String host, int port, String aeTitle, 108 File baseDir,File storageDirectory, Device device, 109 String sourceAETitle, Connection conn) { 110 this.host = host; 111 this.port = port; 112 this.aeTitle = aeTitle; 113 this.baseDirectory = baseDir; 114 this.device = device; 115 this.sourceAETitle = sourceAETitle; 116 this.storageDirectory = storageDirectory; 117 this.bound = conn; 118 } 119 120 private final DicomService stgcmtResultHandler = 121 new AbstractDicomService(UID.StorageCommitmentPushModelSOPClass) { 122 123 @Override 124 public void onDimseRQ(Association as, PresentationContext pc, 125 Dimse dimse, Attributes cmd, Attributes data) 126 throws IOException { 127 if (dimse != Dimse.N_EVENT_REPORT_RQ) 128 throw new DicomServiceException(Status.UnrecognizedOperation); 129 130 int eventTypeID = cmd.getInt(Tag.EventTypeID, 0); 131 if (eventTypeID != 1 && eventTypeID != 2) 132 throw new DicomServiceException(Status.NoSuchEventType) 133 .setEventTypeID(eventTypeID); 134 String tuid = data.getString(Tag.TransactionUID); 135 try { 136 Attributes rsp = Commands.mkNEventReportRSP(cmd, 0); 137 Attributes rspAttrs = writeResponse(as, cmd, data); 138 nEventReqData = rspAttrs; 139 as.writeDimseRSP(pc, rsp, null); 140 141 } catch (AssociationStateException e) { 142 System.out.println(as.toString() + " << N-EVENT-RECORD-RSP failed: " + e.getMessage()); 143 } finally { 144 stgCmtSCU.removeOutstandingResult(tuid); 145 } 146 } 147 }; 148 149 private Attributes writeResponse(Association as, Attributes cmd, Attributes data) throws DicomServiceException { 150 setFailsandSuccess(data); 151 if(storageDirectory == null) { 152 return data; 153 } 154 else { 155 String cuid = cmd.getString(Tag.AffectedSOPClassUID); 156 String iuid = cmd.getString(Tag.AffectedSOPInstanceUID); 157 String tuid = data.getString(Tag.TransactionUID); 158 File file = new File(storageDirectory, tuid); 159 DicomOutputStream out = null; 160// System.out.println(as + "{}: M-WRITE {}" + file); 161 try { 162 out = new DicomOutputStream(file); 163 out.writeDataset( 164 Attributes.createFileMetaInformation(iuid, cuid, 165 UID.ExplicitVRLittleEndian), 166 data); 167 } catch (IOException e) { 168 //System.out.println(as + ": Failed to store Storage Commitment Result:" + e); 169 if(!(e instanceof FileNotFoundException)) 170 throw new DicomServiceException(Status.ProcessingFailure, e); 171 } finally { 172 SafeClose.close(out); 173 } 174 } 175 return data; 176 } 177 178 private void setFailsandSuccess(Attributes data) { 179 success = data.getSequence(Tag.ReferencedSOPSequence).size(); 180 if(data.contains(Tag.FailedSOPSequence)) 181 fails = data.getSequence(Tag.FailedSOPSequence).size(); 182 } 183 184 public void stgcmt(String description, String fileName) throws InterruptedException, IOException, GeneralSecurityException, IncompatibleConnectionException { 185 186 long t1, t2; 187 188 File file = new File(baseDirectory, fileName); 189 190 Assert.assertTrue( 191 "file or directory does not exists: " + file.getAbsolutePath(), 192 file.exists()); 193 194 ApplicationEntity ae = new ApplicationEntity(sourceAETitle); 195 device.addApplicationEntity(ae); 196 ae.addConnection(bound); 197 198 for(Iterator<Connection> iterator=device.getConnections().iterator(); iterator.hasNext();) { 199 Connection next = iterator.next(); 200 if(!next.getCommonName().equalsIgnoreCase(bound.getCommonName())) 201 iterator.remove(); 202 } 203 204 this.stgCmtSCU = new StgCmtSCU(ae, stgcmtResultHandler); 205 206 // configure 207 bound.setMaxOpsInvoked(0); 208 bound.setMaxOpsPerformed(0); 209 210 stgCmtSCU.getAAssociateRQ().setCalledAET(aeTitle); 211 stgCmtSCU.getRemoteConnection().setHostname(host); 212 stgCmtSCU.getRemoteConnection().setPort(port); 213 //ensure secure connection 214 stgCmtSCU.getRemoteConnection().setTlsCipherSuites(bound.getTlsCipherSuites()); 215 stgCmtSCU.getRemoteConnection().setTlsProtocols(bound.tlsProtocols()); 216 stgCmtSCU.setTransferSyntaxes(new String[]{UID.ImplicitVRLittleEndian, UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired}); 217 stgCmtSCU.setAttributes(new Attributes()); 218 stgCmtSCU.setStorageDirectory(storageDirectory); 219 220 // scan 221 t1 = System.currentTimeMillis(); 222 DicomFiles.scan(Arrays.asList(file.getAbsolutePath()), new DicomFiles.Callback() { 223 224 @Override 225 public boolean dicomFile(File f, Attributes fmi, long dsPos, 226 Attributes ds) { 227 return stgCmtSCU.addInstance(ds); 228 } 229 }); 230 t2 = System.currentTimeMillis(); 231 232 // create executor 233 ExecutorService executorService = 234 Executors.newCachedThreadPool(); 235 ScheduledExecutorService scheduledExecutorService = 236 Executors.newSingleThreadScheduledExecutor(); 237 device.setExecutor(executorService); 238 device.setScheduledExecutor(scheduledExecutorService); 239 device.bindConnections(); 240 241 // open, send and wait for response 242 try { 243 stgCmtSCU.open(); 244 stgCmtSCU.sendRequests(); 245 } finally { 246 stgCmtSCU.close(); 247 if (bound.isListening()) { 248 device.waitForNoOpenConnections(); 249 device.unbindConnections(); 250 } 251 executorService.shutdown(); 252 scheduledExecutorService.shutdown(); 253 } 254 init(new StgCmtResult(description,t2-t1,success, fails, nEventReqData)); 255 } 256 @Override 257 public void init(TestResult result) { 258 this.result = result; 259 } 260 261 @Override 262 public TestResult getResult() { 263 return this.result; 264 } 265 266 public File getStorageDirectory() { 267 return storageDirectory; 268 } 269 270}