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.getscu.test;
040
041import static org.junit.Assert.assertTrue;
042
043import java.io.File;
044import java.io.FileInputStream;
045import java.io.FileNotFoundException;
046import java.io.IOException;
047import java.net.URL;
048import java.nio.file.Path;
049import java.security.GeneralSecurityException;
050import java.util.ArrayList;
051import java.util.List;
052import java.util.Map.Entry;
053import java.util.Properties;
054import java.util.Set;
055import java.util.concurrent.ExecutorService;
056import java.util.concurrent.Executors;
057import java.util.concurrent.ScheduledExecutorService;
058
059import org.dcm4che3.data.Attributes;
060import org.dcm4che3.data.ElementDictionary;
061import org.dcm4che3.data.Tag;
062import org.dcm4che3.data.UID;
063import org.dcm4che3.data.VR;
064import org.dcm4che3.io.DicomInputStream;
065import org.dcm4che3.net.*;
066import org.dcm4che3.net.pdu.PresentationContext;
067import org.dcm4che3.net.service.BasicCStoreSCP;
068import org.dcm4che3.net.service.DicomServiceException;
069import org.dcm4che3.net.service.DicomServiceRegistry;
070import org.dcm4che3.tool.common.CLIUtils;
071import org.dcm4che3.tool.common.test.TestResult;
072import org.dcm4che3.tool.common.test.TestTool;
073import org.dcm4che3.tool.getscu.GetSCU;
074import org.dcm4che3.tool.getscu.GetSCU.InformationModel;
075import org.dcm4che3.util.StringUtils;
076import org.slf4j.Logger;
077import org.slf4j.LoggerFactory;
078
079/**
080 * @author Umberto Cappellini <umberto.cappellini@agfa.com>
081 * @author Hesham Elbadawi <bsdreko@gmail.com>
082 */
083public class RetrieveTool implements TestTool{
084
085    private static final Logger LOG = LoggerFactory.getLogger(RetrieveTool.class);
086
087    private final String host;
088    private final int port;
089    private final String aeTitle;
090    private final Device device;
091    private final Connection conn;
092    private final File retrieveDir;
093
094    private int numCGetFailed;
095    private int numCGetSuccess;
096    private int numCGetWarning;
097
098    private int numCStores;
099    private int numCStoreSuccess;
100    private int numCStoreFailed;
101
102    private int expectedMatches = Integer.MIN_VALUE;
103    private final Attributes retrieveatts = new Attributes();
104    private final GetSCU retrievescu;
105    
106    private final List<Attributes> response = new ArrayList<Attributes>();
107    private TestResult result;
108    private final String retrieveLevel;
109    private final InformationModel retrieveInformationModel;
110
111    private boolean rememberResultAttributes = true;
112
113    private static String[] IVR_LE_FIRST = { UID.ImplicitVRLittleEndian,
114            UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired };
115
116
117    public RetrieveTool(String host, int port, String aeTitle, File retrieveDir, Device device, String sourceAETitle,String retrieveLevel
118            , String informationModel, boolean relational, Connection conn) {
119        super();
120        this.host = host;
121        this.port = port;
122        this.aeTitle = aeTitle;
123        this.device = device;
124        this.retrieveDir = retrieveDir;
125        this.retrieveLevel = retrieveLevel;
126        this.retrieveInformationModel = informationModel.equalsIgnoreCase("StudyRoot")
127                ? InformationModel.StudyRoot : InformationModel.PatientRoot;
128        this.conn = conn;
129
130        //setup device and connection
131        device.setInstalled(true);
132        ApplicationEntity ae = new ApplicationEntity(sourceAETitle);
133        device.addApplicationEntity(ae);
134        ae.addConnection(conn);
135        retrievescu = new GetSCU(ae);
136
137        retrievescu.setInformationModel(retrieveInformationModel, IVR_LE_FIRST, relational);
138        try {
139            configureServiceClass(retrievescu);
140        } catch (IOException e) {
141            throw new RuntimeException(e);
142        }
143    }
144
145    public void retrieve(String testDescription) throws IOException, InterruptedException,
146            IncompatibleConnectionException, GeneralSecurityException,
147            FileNotFoundException, IOException {
148
149        retrievescu.getAAssociateRQ().setCalledAET(aeTitle);
150        retrievescu.getRemoteConnection().setHostname(host);
151        retrievescu.getRemoteConnection().setPort(port);
152
153        //ensure secure connection
154        retrievescu.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites());
155        retrievescu.getRemoteConnection().setTlsProtocols(conn.tlsProtocols());
156
157        retrievescu.setStorageDirectory(retrieveDir);
158
159        registerSCPservice(retrieveDir);
160        
161        // add retrieve attrs
162
163        // create executor
164        ExecutorService executorService = Executors.newCachedThreadPool();
165        ScheduledExecutorService scheduledExecutorService = Executors
166                .newSingleThreadScheduledExecutor();
167        retrievescu.getDevice().setExecutor(executorService);
168        retrievescu.getDevice().setScheduledExecutor(scheduledExecutorService);
169
170        retrievescu.addLevel(retrieveLevel);
171        retrievescu.getKeys().addAll(retrieveatts);
172        
173        long timeStart = System.currentTimeMillis();
174
175        // open, send and wait for response
176        try {
177            retrievescu.open();
178            retrievescu.retrieve(getResponseHandler());
179        } catch (Exception exception){
180            LOG.error("Error while retrieving", exception);
181        } finally {
182            retrievescu.close();
183            executorService.shutdown();
184            scheduledExecutorService.shutdown();
185        }
186
187        long timeEnd = System.currentTimeMillis();
188        
189        if (this.expectedMatches >= 0) {
190            assertTrue("test[" + testDescription
191                    + "] not returned expected result:" + this.expectedMatches
192                    + " but:" + numCStores, numCStores == this.expectedMatches);
193        }
194
195        assertTrue("test[" + testDescription + "] had failed c-get responses: " + numCGetFailed, numCGetFailed == 0 );
196
197        assertTrue("test[" + testDescription + "] had failed c-store responses: " + numCStoreFailed, numCStoreFailed == 0);
198        
199        init(new RetrieveResult(testDescription, expectedMatches, numCStores,
200                numCStoreSuccess, numCStoreFailed,
201                (timeEnd - timeStart), response));
202    }
203
204    private DimseRSPHandler getResponseHandler() {
205        return new DimseRSPHandler(retrievescu.getAssociation().nextMessageID()) {
206            @Override
207            public void onDimseRSP(Association as, Attributes cmd, Attributes data) {
208                super.onDimseRSP(as, cmd, data);
209
210                onCGetReponse(as, cmd, data);
211            }
212        };
213    }
214
215    private void onCGetReponse(Association as, Attributes cmd, Attributes data) {
216        int status = cmd.getInt(Tag.Status, -1);
217        if(!Status.isPending(status)) {
218            numCGetSuccess = cmd.getInt(Tag.NumberOfCompletedSuboperations,0);
219            numCGetFailed = cmd.getInt(Tag.NumberOfFailedSuboperations,0);
220            numCGetWarning = cmd.getInt(Tag.NumberOfWarningSuboperations,0);
221        }
222    }
223
224    public void addOfferedStorageSOPClass(String cuid, String... tsuids) {
225        retrievescu.addOfferedStorageSOPClass(cuid, tsuids);
226    }
227
228    public void addTag(int tag, String... values) {
229        VR vr = ElementDictionary.vrOf(tag, null); 
230        retrieveatts.setString(tag, vr, values);
231    }
232
233    public void addAll(Attributes attrs) {
234        retrieveatts.addAll(attrs);
235    }
236
237    public void clearTags() {
238        retrieveatts.clear();
239    }
240
241    public void setExpectedMatches(int expectedResult) {
242        this.expectedMatches = expectedResult;
243    }
244
245    private void registerSCPservice(final File storeDir) {
246            DicomServiceRegistry serviceReg = new DicomServiceRegistry();
247            serviceReg.addDicomService( new BasicCStoreSCP("*") {
248
249                @Override
250                protected void store(Association as, PresentationContext pc, Attributes rq,
251                        PDVInputStream data, Attributes rsp) throws DicomServiceException {
252
253                    if (storeDir == null)
254                        return;
255
256                    String iuid = rq.getString(Tag.AffectedSOPInstanceUID);
257                    String cuid = rq.getString(Tag.AffectedSOPClassUID);
258                    String tsuid = pc.getTransferSyntax();
259                    File file = new File(storeDir, iuid );
260                    try {
261                        GetSCU.storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid),
262                                data, file);
263                    } catch (Exception e) {
264                        throw new DicomServiceException(Status.ProcessingFailure, e);
265                    }
266                    //check returned result
267                    try{
268                    DicomInputStream din = new DicomInputStream(file);
269                    Attributes attrs = din.readDataset(-1, -1);
270                    din.close();
271                    onCStoreReq(rq,attrs);
272                    }
273                    catch (Exception e) {
274                        e.printStackTrace();
275                    }
276                }
277            });
278            device.setDimseRQHandler(serviceReg);
279    }
280
281    protected void onCStoreReq(Attributes cmd, Attributes data) {
282        int status = cmd.getInt(Tag.Status, -1);
283        if (data != null && !data.isEmpty())
284            ++numCStoreSuccess;
285        else
286            ++numCStoreFailed;
287
288        if (rememberResultAttributes)
289            response.add(data);
290
291        ++numCStores;
292    }
293
294    private static void configureServiceClass(GetSCU main)
295            throws FileNotFoundException, IOException {
296
297        URL defaultConfig = main.getClass().getResource(
298                "/retrieve/store-tcs.properties");
299        Properties props = new Properties();
300        props.load(new FileInputStream(new File(defaultConfig.getFile())));
301
302        Set<Entry<Object, Object>> entrySet = props.entrySet();
303        for (Entry<Object, Object> entry : entrySet)
304            configureStorageSOPClass(main, (String) entry.getKey(),
305                    (String) entry.getValue());
306    }
307
308    private static void configureStorageSOPClass(GetSCU main, String cuid,
309            String tsuids0) {
310        String[] tsuids1 = StringUtils.split(tsuids0, ';');
311        for (String tsuids2 : tsuids1) {
312            main.addOfferedStorageSOPClass(CLIUtils.toUID(cuid),
313                    CLIUtils.toUID(tsuids2));
314        }
315    }
316
317    @Override
318    public void init(TestResult resultIn) {
319        this.result = resultIn;
320    }
321
322    @Override
323    public TestResult getResult() {
324        return this.result;
325    }
326
327    public Path getRetrieveDir() {
328        return retrieveDir.toPath();
329    }
330
331    /**
332     * If set to false, does not keep the attributes retrieved. Useful for large performance tests to avoid VM crashes.
333     * @param rememberResultAttributes
334     */
335    public void setRememberResultAttributes(boolean rememberResultAttributes) {
336        this.rememberResultAttributes = rememberResultAttributes;
337    }
338}