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.storescu.test; 040 041import java.io.File; 042import java.io.IOException; 043import java.nio.file.Path; 044import java.nio.file.Paths; 045import java.security.GeneralSecurityException; 046import java.text.MessageFormat; 047import java.util.ArrayList; 048import java.util.List; 049import java.util.concurrent.ExecutorService; 050import java.util.concurrent.Executors; 051import java.util.concurrent.ScheduledExecutorService; 052 053import org.dcm4che3.data.Attributes; 054import org.dcm4che3.data.Tag; 055import org.dcm4che3.net.ApplicationEntity; 056import org.dcm4che3.net.Association; 057import org.dcm4che3.net.Connection; 058import org.dcm4che3.net.Device; 059import org.dcm4che3.net.DimseRSPHandler; 060import org.dcm4che3.net.IncompatibleConnectionException; 061import org.dcm4che3.net.Status; 062import org.dcm4che3.net.pdu.AAssociateRQ; 063import org.dcm4che3.tool.common.test.TestResult; 064import org.dcm4che3.tool.common.test.TestTool; 065import org.dcm4che3.tool.storescu.StoreSCU; 066import org.dcm4che3.util.TagUtils; 067import org.slf4j.Logger; 068import org.slf4j.LoggerFactory; 069 070import static org.junit.Assert.assertTrue; 071 072/** 073 * @author Umberto Cappellini <umberto.cappellini@agfa.com> 074 * @author Hesham Elbadawi <bsdreko@gmail.com> 075 */ 076public class StoreTool implements TestTool { 077 078 public static final Logger LOG = LoggerFactory.getLogger(StoreTool.class); 079 080 081 private String host; 082 private int port; 083 private String aeTitle; 084 private File baseDirectory; 085 private Device device; 086 private Connection conn; 087 088 private String sourceAETitle; 089 private long totalSize; 090 private int filesSent; 091 private int warnings; 092 private int failures; 093 private ArrayList<Attributes> cmdRSP = new ArrayList<Attributes>(); 094 095 private StoreResult result; 096 private long timeStarted; 097 098 private String implementationClassUID; 099 100 public StoreTool(String host, int port, String aeTitle, File baseDirectory, Device device, String sourceAETitle, Connection conn) { 101 super(); 102 this.host = host; 103 this.port = port; 104 this.aeTitle = aeTitle; 105 this.baseDirectory = baseDirectory; 106 this.device = device; 107 this.sourceAETitle = sourceAETitle; 108 this.conn = conn; 109 } 110 111 public void store(String testDescription, String... fileNames) 112 throws IOException, InterruptedException, 113 IncompatibleConnectionException, GeneralSecurityException { 114 115 long t2; 116 117 List<String> absoluteFileNames = new ArrayList<String>(fileNames.length); 118 for(String fileName : fileNames) { 119 Path p = Paths.get(fileName); 120 if (!p.isAbsolute() && baseDirectory == null) 121 throw new IllegalArgumentException("No base Directory and file to send is provided as a relative path"); 122 123 File file = p.isAbsolute() ? new File(fileName) : new File(baseDirectory, fileName); 124 125 assertTrue("file or directory does not exists: " + file.getAbsolutePath(), file.exists()); 126 127 absoluteFileNames.add(file.getAbsolutePath()); 128 } 129 130 device.setInstalled(true); 131 ApplicationEntity ae = new ApplicationEntity(sourceAETitle); 132 device.addApplicationEntity(ae); 133 ae.addConnection(conn); 134 135 StoreSCU main = new StoreSCU(ae); 136 137 main.setRspHandlerFactory(new StoreSCU.RSPHandlerFactory() { 138 139 @Override 140 public DimseRSPHandler createDimseRSPHandler(final File f) { 141 142 return new DimseRSPHandler(0) { 143 144 @Override 145 public void onDimseRSP(Association as, Attributes cmd, 146 Attributes data) { 147 super.onDimseRSP(as, cmd, data); 148 StoreTool.this.onCStoreRSP(cmd, f); 149 } 150 }; 151 } 152 153 }); 154 155 // configure connection params 156 AAssociateRQ aAssociateRQ = main.getAAssociateRQ(); 157 if (implementationClassUID != null) 158 aAssociateRQ.setImplClassUID(implementationClassUID); 159 aAssociateRQ.setCalledAET(aeTitle); 160 main.getRemoteConnection().setHostname(host); 161 main.getRemoteConnection().setPort(port); 162 //ensure secure connection 163 main.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites()); 164 main.getRemoteConnection().setTlsProtocols(conn.tlsProtocols()); 165 main.setAttributes(new Attributes()); 166 // scan 167 main.scanFiles(absoluteFileNames, false); 168 // create executor 169 ExecutorService executorService = Executors.newSingleThreadExecutor(); 170 ScheduledExecutorService scheduledExecutorService = Executors 171 .newSingleThreadScheduledExecutor(); 172 device.setExecutor(executorService); 173 device.setScheduledExecutor(scheduledExecutorService); 174 175 // open and send 176 try { 177 main.open(); 178 179 timeStarted = System.currentTimeMillis(); 180 main.sendFiles(); 181 t2 = System.currentTimeMillis(); 182 } finally { 183 main.close(); 184 executorService.shutdown(); 185 scheduledExecutorService.shutdown(); 186 } 187 init(new StoreResult(testDescription, fileNames, totalSize, (t2 - timeStarted), 188 filesSent, warnings, failures, cmdRSP)); 189 } 190 191 private void onCStoreRSP(Attributes cmd, File f) { 192 cmdRSP.add(cmd); 193 194 int status = cmd.getInt(Tag.Status, -1); 195 switch (status) { 196 case Status.Success: 197 totalSize += f.length(); 198 ++filesSent; 199 break; 200 case Status.CoercionOfDataElements: 201 case Status.ElementsDiscarded: 202 case Status.DataSetDoesNotMatchSOPClassWarning: 203 totalSize += f.length(); 204 ++filesSent; 205 ++warnings; 206 // System.err.println(MessageFormat.format("warning", 207 // TagUtils.shortToHexString(status), f)); 208 // System.err.println(cmd); 209 break; 210 default: 211 ++failures; 212 System.err.println(MessageFormat.format("error", 213 TagUtils.shortToHexString(status), f)); 214 System.err.println(cmd); 215 } 216 217 if (filesSent % 100 == 0) 218 LOG.warn("Files sent: {}, took {} sec.", filesSent, (System.currentTimeMillis() - timeStarted) / 1000.0); 219 220 } 221 222 @Override 223 public void init(TestResult result) { 224 this.result = (StoreResult) result; 225 } 226 227 @Override 228 public StoreResult getResult() { 229 return this.result; 230 } 231 232 public void setbaseDir(String dir) { 233 this.baseDirectory = new File (dir); 234 } 235 236 public void setImplementationClassUID(String implementationClassUID) { 237 this.implementationClassUID = implementationClassUID; 238 } 239 240}