/* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is part of dcm4che, an implementation of DICOM(TM) in * Java(TM), hosted at https://github.com/gunterze/dcm4che. * * The Initial Developer of the Original Code is * Agfa Healthcare. * Portions created by the Initial Developer are Copyright (C) 2011 * the Initial Developer. All Rights Reserved. * * Contributor(s): * See @authors listed below * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ package org.dcm4che3.tool.getscu.test; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.net.URL; import java.nio.file.Path; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.ElementDictionary; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.data.VR; import org.dcm4che3.io.DicomInputStream; import org.dcm4che3.net.*; import org.dcm4che3.net.pdu.PresentationContext; import org.dcm4che3.net.service.BasicCStoreSCP; import org.dcm4che3.net.service.DicomServiceException; import org.dcm4che3.net.service.DicomServiceRegistry; import org.dcm4che3.tool.common.CLIUtils; import org.dcm4che3.tool.common.test.TestResult; import org.dcm4che3.tool.common.test.TestTool; import org.dcm4che3.tool.getscu.GetSCU; import org.dcm4che3.tool.getscu.GetSCU.InformationModel; import org.dcm4che3.util.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Umberto Cappellini <umberto.cappellini@agfa.com> * @author Hesham Elbadawi <bsdreko@gmail.com> */ public class RetrieveTool implements TestTool{ private static final Logger LOG = LoggerFactory.getLogger(RetrieveTool.class); private final String host; private final int port; private final String aeTitle; private final Device device; private final Connection conn; private final File retrieveDir; private int numCGetFailed; private int numCGetSuccess; private int numCGetWarning; private int numCStores; private int numCStoreSuccess; private int numCStoreFailed; private int expectedMatches = Integer.MIN_VALUE; private final Attributes retrieveatts = new Attributes(); private final GetSCU retrievescu; private final List<Attributes> response = new ArrayList<Attributes>(); private TestResult result; private final String retrieveLevel; private final InformationModel retrieveInformationModel; private boolean rememberResultAttributes = true; private static String[] IVR_LE_FIRST = { UID.ImplicitVRLittleEndian, UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired }; public RetrieveTool(String host, int port, String aeTitle, File retrieveDir, Device device, String sourceAETitle,String retrieveLevel , String informationModel, boolean relational, Connection conn) { super(); this.host = host; this.port = port; this.aeTitle = aeTitle; this.device = device; this.retrieveDir = retrieveDir; this.retrieveLevel = retrieveLevel; this.retrieveInformationModel = informationModel.equalsIgnoreCase("StudyRoot") ? InformationModel.StudyRoot : InformationModel.PatientRoot; this.conn = conn; //setup device and connection device.setInstalled(true); ApplicationEntity ae = new ApplicationEntity(sourceAETitle); device.addApplicationEntity(ae); ae.addConnection(conn); retrievescu = new GetSCU(ae); retrievescu.setInformationModel(retrieveInformationModel, IVR_LE_FIRST, relational); try { configureServiceClass(retrievescu); } catch (IOException e) { throw new RuntimeException(e); } } public void retrieve(String testDescription) throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException, FileNotFoundException, IOException { retrievescu.getAAssociateRQ().setCalledAET(aeTitle); retrievescu.getRemoteConnection().setHostname(host); retrievescu.getRemoteConnection().setPort(port); //ensure secure connection retrievescu.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites()); retrievescu.getRemoteConnection().setTlsProtocols(conn.tlsProtocols()); retrievescu.setStorageDirectory(retrieveDir); registerSCPservice(retrieveDir); // add retrieve attrs // create executor ExecutorService executorService = Executors.newCachedThreadPool(); ScheduledExecutorService scheduledExecutorService = Executors .newSingleThreadScheduledExecutor(); retrievescu.getDevice().setExecutor(executorService); retrievescu.getDevice().setScheduledExecutor(scheduledExecutorService); retrievescu.addLevel(retrieveLevel); retrievescu.getKeys().addAll(retrieveatts); long timeStart = System.currentTimeMillis(); // open, send and wait for response try { retrievescu.open(); retrievescu.retrieve(getResponseHandler()); } catch (Exception exception){ LOG.error("Error while retrieving", exception); } finally { retrievescu.close(); executorService.shutdown(); scheduledExecutorService.shutdown(); } long timeEnd = System.currentTimeMillis(); if (this.expectedMatches >= 0) { assertTrue("test[" + testDescription + "] not returned expected result:" + this.expectedMatches + " but:" + numCStores, numCStores == this.expectedMatches); } assertTrue("test[" + testDescription + "] had failed c-get responses: " + numCGetFailed, numCGetFailed == 0 ); assertTrue("test[" + testDescription + "] had failed c-store responses: " + numCStoreFailed, numCStoreFailed == 0); init(new RetrieveResult(testDescription, expectedMatches, numCStores, numCStoreSuccess, numCStoreFailed, (timeEnd - timeStart), response)); } private DimseRSPHandler getResponseHandler() { return new DimseRSPHandler(retrievescu.getAssociation().nextMessageID()) { @Override public void onDimseRSP(Association as, Attributes cmd, Attributes data) { super.onDimseRSP(as, cmd, data); onCGetReponse(as, cmd, data); } }; } private void onCGetReponse(Association as, Attributes cmd, Attributes data) { int status = cmd.getInt(Tag.Status, -1); if(!Status.isPending(status)) { numCGetSuccess = cmd.getInt(Tag.NumberOfCompletedSuboperations,0); numCGetFailed = cmd.getInt(Tag.NumberOfFailedSuboperations,0); numCGetWarning = cmd.getInt(Tag.NumberOfWarningSuboperations,0); } } public void addOfferedStorageSOPClass(String cuid, String... tsuids) { retrievescu.addOfferedStorageSOPClass(cuid, tsuids); } public void addTag(int tag, String... values) { VR vr = ElementDictionary.vrOf(tag, null); retrieveatts.setString(tag, vr, values); } public void addAll(Attributes attrs) { retrieveatts.addAll(attrs); } public void clearTags() { retrieveatts.clear(); } public void setExpectedMatches(int expectedResult) { this.expectedMatches = expectedResult; } private void registerSCPservice(final File storeDir) { DicomServiceRegistry serviceReg = new DicomServiceRegistry(); serviceReg.addDicomService( new BasicCStoreSCP("*") { @Override protected void store(Association as, PresentationContext pc, Attributes rq, PDVInputStream data, Attributes rsp) throws DicomServiceException { if (storeDir == null) return; String iuid = rq.getString(Tag.AffectedSOPInstanceUID); String cuid = rq.getString(Tag.AffectedSOPClassUID); String tsuid = pc.getTransferSyntax(); File file = new File(storeDir, iuid ); try { GetSCU.storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid), data, file); } catch (Exception e) { throw new DicomServiceException(Status.ProcessingFailure, e); } //check returned result try{ DicomInputStream din = new DicomInputStream(file); Attributes attrs = din.readDataset(-1, -1); din.close(); onCStoreReq(rq,attrs); } catch (Exception e) { e.printStackTrace(); } } }); device.setDimseRQHandler(serviceReg); } protected void onCStoreReq(Attributes cmd, Attributes data) { int status = cmd.getInt(Tag.Status, -1); if (data != null && !data.isEmpty()) ++numCStoreSuccess; else ++numCStoreFailed; if (rememberResultAttributes) response.add(data); ++numCStores; } private static void configureServiceClass(GetSCU main) throws FileNotFoundException, IOException { URL defaultConfig = main.getClass().getResource( "/retrieve/store-tcs.properties"); Properties props = new Properties(); props.load(new FileInputStream(new File(defaultConfig.getFile()))); Set<Entry<Object, Object>> entrySet = props.entrySet(); for (Entry<Object, Object> entry : entrySet) configureStorageSOPClass(main, (String) entry.getKey(), (String) entry.getValue()); } private static void configureStorageSOPClass(GetSCU main, String cuid, String tsuids0) { String[] tsuids1 = StringUtils.split(tsuids0, ';'); for (String tsuids2 : tsuids1) { main.addOfferedStorageSOPClass(CLIUtils.toUID(cuid), CLIUtils.toUID(tsuids2)); } } @Override public void init(TestResult resultIn) { this.result = resultIn; } @Override public TestResult getResult() { return this.result; } public Path getRetrieveDir() { return retrieveDir.toPath(); } /** * If set to false, does not keep the attributes retrieved. Useful for large performance tests to avoid VM crashes. * @param rememberResultAttributes */ public void setRememberResultAttributes(boolean rememberResultAttributes) { this.rememberResultAttributes = rememberResultAttributes; } }