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.findscu.test;
040
041import org.dcm4che3.data.Attributes;
042import org.dcm4che3.data.ElementDictionary;
043import org.dcm4che3.data.Tag;
044import org.dcm4che3.data.UID;
045import org.dcm4che3.data.VR;
046import org.dcm4che3.net.ApplicationEntity;
047import org.dcm4che3.net.Association;
048import org.dcm4che3.net.Connection;
049import org.dcm4che3.net.Device;
050import org.dcm4che3.net.DimseRSPHandler;
051import org.dcm4che3.net.IncompatibleConnectionException;
052import org.dcm4che3.net.QueryOption;
053import org.dcm4che3.net.Status;
054import org.dcm4che3.tool.common.test.TestResult;
055import org.dcm4che3.tool.common.test.TestTool;
056import org.dcm4che3.tool.findscu.FindSCU;
057import org.dcm4che3.tool.findscu.FindSCU.InformationModel;
058import org.slf4j.Logger;
059import org.slf4j.LoggerFactory;
060
061import java.io.IOException;
062import java.security.GeneralSecurityException;
063import java.util.ArrayList;
064import java.util.EnumSet;
065import java.util.List;
066import java.util.concurrent.ExecutorService;
067import java.util.concurrent.Executors;
068import java.util.concurrent.ScheduledExecutorService;
069
070import static org.junit.Assert.assertTrue;
071
072/**
073 * Tool for using {@link FindSCU} within tests.
074 * 
075 * @author Umberto Cappellini <umberto.cappellini@agfa.com>
076 * @author Hesham elbadawi <bsdreko@gmail.com>
077 */
078public class QueryTool implements TestTool {
079
080    private static final Logger LOG = LoggerFactory.getLogger(QueryTool.class);
081
082    private final String host;
083    private final int port;
084    private String aeTitle;
085    private final Device device;
086    private final Connection conn;
087    private final String sourceAETitle;
088    private final List<Attributes> response = new ArrayList<Attributes>();
089    private TestResult result;
090    private String queryLevel;
091    private final InformationModel queryModel;
092    private final boolean relational;
093    private int numMatches;
094    private static String[] IVR_LE_FIRST = { UID.ImplicitVRLittleEndian,
095            UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired };
096    private Attributes queryatts = new Attributes();
097    private int expectedMatches = Integer.MIN_VALUE;
098    
099    private long timeFirst=0;
100
101    public QueryTool(String host, int port, String aeTitle, String queryLevel, String queryModel
102            , boolean relational, Device device, String sourceAETitle, Connection conn) {
103        super();
104        this.host = host;
105        this.port = port;
106        this.aeTitle = aeTitle;
107        this.device = device;
108        this.sourceAETitle = sourceAETitle;
109        this.conn = conn;
110        this.queryLevel = queryLevel;
111
112        InformationModel queryModelResolved = InformationModel.StudyRoot;
113        try {
114            queryModelResolved = InformationModel.valueOf(queryModel);
115        } catch (IllegalArgumentException ignored) {
116            LOG.warn("Query model {} does not exist, using StudyRoot", queryModelResolved);
117        }
118
119        this.queryModel = queryModelResolved;
120        this.relational = relational;
121    }
122
123    /**
124     * Query method setting parameter fuzzy = false and dataTimeCombined = false
125     *
126     * @param testDescription The purpose of the query
127     *
128     * @throws IOException
129     * @throws InterruptedException
130     * @throws IncompatibleConnectionException
131     * @throws GeneralSecurityException
132     */
133    public void query(String testDescription) throws IOException,
134            InterruptedException, IncompatibleConnectionException,
135            GeneralSecurityException {
136        doQuery(testDescription, false, false);
137    }
138
139    public void query(String testDescription, boolean fuzzy, boolean dataTimeCombined) throws IOException,
140    InterruptedException, IncompatibleConnectionException,
141    GeneralSecurityException {
142        doQuery(testDescription, fuzzy, dataTimeCombined);
143    }
144
145    
146    private void doQuery(String testDescription, boolean fuzzy, boolean combined) throws IOException,
147            InterruptedException, IncompatibleConnectionException,
148            GeneralSecurityException {
149        device.setInstalled(true);
150        ApplicationEntity ae = new ApplicationEntity(sourceAETitle);
151        device.addApplicationEntity(ae);
152        ae.addConnection(conn);
153        FindSCU main = new FindSCU(ae);
154        main.getAAssociateRQ().setCalledAET(aeTitle);
155        main.getRemoteConnection().setHostname(host);
156        main.getRemoteConnection().setPort(port);
157        //ensure secure connection
158        main.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites());
159        main.getRemoteConnection().setTlsProtocols(conn.tlsProtocols());
160        ExecutorService executorService = Executors.newSingleThreadExecutor();
161        ScheduledExecutorService scheduledExecutorService = Executors
162                .newSingleThreadScheduledExecutor();
163        main.getDevice().setExecutor(executorService);
164        main.getDevice().setScheduledExecutor(scheduledExecutorService);
165
166        EnumSet<QueryOption> queryOptions = EnumSet.noneOf(QueryOption.class);
167        if (fuzzy) queryOptions.add(QueryOption.FUZZY);
168        
169        if (combined) queryOptions.add(QueryOption.DATETIME);
170        
171        if(relational)
172            queryOptions.add(QueryOption.RELATIONAL);
173        
174        main.setInformationModel(queryModel, IVR_LE_FIRST, queryOptions);
175        main.addLevel(queryLevel);
176//        if (relational) {
177//            main.getAAssociateRQ()
178//            .addExtendedNegotiation(new ExtendedNegotiation(queryModel.getCuid(), new byte[]{1}));
179//        }
180        main.getKeys().addAll(queryatts);
181
182        long timeStart = System.currentTimeMillis();
183
184        try {
185
186            main.open();
187            main.query(getDimseRSPHandler(main.getAssociation().nextMessageID()));
188
189        } finally {
190            main.close(); // is waiting for all the responses to be complete
191            executorService.shutdown();
192            scheduledExecutorService.shutdown();
193
194        }
195
196        long timeEnd = System.currentTimeMillis();
197
198        validateMatches(testDescription);
199
200        init(new QueryResult(testDescription, expectedMatches, numMatches,
201                (timeEnd - timeStart), (timeFirst-timeStart), response ));
202    }
203
204    private void validateMatches(String testDescription) {
205        if (this.expectedMatches >= 0)
206            assertTrue("test[" + testDescription
207                    + "] not returned expected result:" + this.expectedMatches
208                    + " but:" + numMatches, numMatches == this.expectedMatches);
209    }
210
211    public void addQueryTag(int tag, String value) throws Exception {
212        VR vr = ElementDictionary.vrOf(tag, null);
213        queryatts.setString(tag, vr, value);
214    }
215
216    /**
217     * adds a new private query tag. For convenience, the VR of the tag is passed as well
218     * (so that it's not necessary to have the private dictionary on client side)
219     */
220    public void addQueryTag(String privateCreator, int tag, VR vr, String value) {
221        queryatts.setString(privateCreator, tag, vr, value);
222    }
223
224    public void clearQueryKeys() {
225        this.queryatts = new Attributes();
226    }
227    public void addAll(Attributes attrs) {
228        queryatts.addAll(attrs);
229    }
230
231    public void addReturnTag(int tag) throws Exception {
232        VR vr = ElementDictionary.vrOf(tag, null);
233        queryatts.setNull(tag, vr);
234    }
235
236    public void addReturnTag(String privateCreator, int tag, VR vr) throws Exception {
237        queryatts.setNull(privateCreator, tag, vr);
238    }
239
240    public void setExpectedMatches(int matches) {
241        this.expectedMatches = matches;
242    }
243
244    private DimseRSPHandler getDimseRSPHandler(int messageID) {
245
246        return new DimseRSPHandler(messageID) {
247
248            @Override
249            public void onDimseRSP(Association as, Attributes cmd,
250                    Attributes data) {
251                super.onDimseRSP(as, cmd, data);
252                onCFindResponse(cmd, data);
253            }
254        };
255    }
256
257    protected void onCFindResponse(Attributes cmd, Attributes data) {
258        if (numMatches==0) timeFirst = System.currentTimeMillis();
259        int status = cmd.getInt(Tag.Status, -1);
260        if (Status.isPending(status)) {
261            response.add(data);
262            ++numMatches;
263        }
264    }
265    @Override
266    public void init(TestResult result) {
267        this.result = result;
268    }
269
270    @Override
271    public TestResult getResult() {
272        return this.result;
273    }
274
275    public void setAeTitle(String aeTitle) {
276        this.aeTitle = aeTitle;
277    }
278
279    public String getQueryLevel() {
280        return queryLevel;
281    }
282    
283    public void setQueryLevel(String level) {
284        queryLevel = level;
285    }
286
287}