/* ***** 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.findscu.test;
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.net.ApplicationEntity;
import org.dcm4che3.net.Association;
import org.dcm4che3.net.Connection;
import org.dcm4che3.net.Device;
import org.dcm4che3.net.DimseRSPHandler;
import org.dcm4che3.net.IncompatibleConnectionException;
import org.dcm4che3.net.QueryOption;
import org.dcm4che3.net.Status;
import org.dcm4che3.tool.common.test.TestResult;
import org.dcm4che3.tool.common.test.TestTool;
import org.dcm4che3.tool.findscu.FindSCU;
import org.dcm4che3.tool.findscu.FindSCU.InformationModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import static org.junit.Assert.assertTrue;
/**
* Tool for using {@link FindSCU} within tests.
*
* @author Umberto Cappellini <umberto.cappellini@agfa.com>
* @author Hesham elbadawi <bsdreko@gmail.com>
*/
public class QueryTool implements TestTool {
private static final Logger LOG = LoggerFactory.getLogger(QueryTool.class);
private final String host;
private final int port;
private String aeTitle;
private final Device device;
private final Connection conn;
private final String sourceAETitle;
private final List<Attributes> response = new ArrayList<Attributes>();
private TestResult result;
private String queryLevel;
private final InformationModel queryModel;
private final boolean relational;
private int numMatches;
private static String[] IVR_LE_FIRST = { UID.ImplicitVRLittleEndian,
UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired };
private Attributes queryatts = new Attributes();
private int expectedMatches = Integer.MIN_VALUE;
private long timeFirst=0;
public QueryTool(String host, int port, String aeTitle, String queryLevel, String queryModel
, boolean relational, Device device, String sourceAETitle, Connection conn) {
super();
this.host = host;
this.port = port;
this.aeTitle = aeTitle;
this.device = device;
this.sourceAETitle = sourceAETitle;
this.conn = conn;
this.queryLevel = queryLevel;
InformationModel queryModelResolved = InformationModel.StudyRoot;
try {
queryModelResolved = InformationModel.valueOf(queryModel);
} catch (IllegalArgumentException ignored) {
LOG.warn("Query model {} does not exist, using StudyRoot", queryModelResolved);
}
this.queryModel = queryModelResolved;
this.relational = relational;
}
/**
* Query method setting parameter fuzzy = false and dataTimeCombined = false
*
* @param testDescription The purpose of the query
*
* @throws IOException
* @throws InterruptedException
* @throws IncompatibleConnectionException
* @throws GeneralSecurityException
*/
public void query(String testDescription) throws IOException,
InterruptedException, IncompatibleConnectionException,
GeneralSecurityException {
doQuery(testDescription, false, false);
}
public void query(String testDescription, boolean fuzzy, boolean dataTimeCombined) throws IOException,
InterruptedException, IncompatibleConnectionException,
GeneralSecurityException {
doQuery(testDescription, fuzzy, dataTimeCombined);
}
private void doQuery(String testDescription, boolean fuzzy, boolean combined) throws IOException,
InterruptedException, IncompatibleConnectionException,
GeneralSecurityException {
device.setInstalled(true);
ApplicationEntity ae = new ApplicationEntity(sourceAETitle);
device.addApplicationEntity(ae);
ae.addConnection(conn);
FindSCU main = new FindSCU(ae);
main.getAAssociateRQ().setCalledAET(aeTitle);
main.getRemoteConnection().setHostname(host);
main.getRemoteConnection().setPort(port);
//ensure secure connection
main.getRemoteConnection().setTlsCipherSuites(conn.getTlsCipherSuites());
main.getRemoteConnection().setTlsProtocols(conn.tlsProtocols());
ExecutorService executorService = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledExecutorService = Executors
.newSingleThreadScheduledExecutor();
main.getDevice().setExecutor(executorService);
main.getDevice().setScheduledExecutor(scheduledExecutorService);
EnumSet<QueryOption> queryOptions = EnumSet.noneOf(QueryOption.class);
if (fuzzy) queryOptions.add(QueryOption.FUZZY);
if (combined) queryOptions.add(QueryOption.DATETIME);
if(relational)
queryOptions.add(QueryOption.RELATIONAL);
main.setInformationModel(queryModel, IVR_LE_FIRST, queryOptions);
main.addLevel(queryLevel);
// if (relational) {
// main.getAAssociateRQ()
// .addExtendedNegotiation(new ExtendedNegotiation(queryModel.getCuid(), new byte[]{1}));
// }
main.getKeys().addAll(queryatts);
long timeStart = System.currentTimeMillis();
try {
main.open();
main.query(getDimseRSPHandler(main.getAssociation().nextMessageID()));
} finally {
main.close(); // is waiting for all the responses to be complete
executorService.shutdown();
scheduledExecutorService.shutdown();
}
long timeEnd = System.currentTimeMillis();
validateMatches(testDescription);
init(new QueryResult(testDescription, expectedMatches, numMatches,
(timeEnd - timeStart), (timeFirst-timeStart), response ));
}
private void validateMatches(String testDescription) {
if (this.expectedMatches >= 0)
assertTrue("test[" + testDescription
+ "] not returned expected result:" + this.expectedMatches
+ " but:" + numMatches, numMatches == this.expectedMatches);
}
public void addQueryTag(int tag, String value) throws Exception {
VR vr = ElementDictionary.vrOf(tag, null);
queryatts.setString(tag, vr, value);
}
/**
* adds a new private query tag. For convenience, the VR of the tag is passed as well
* (so that it's not necessary to have the private dictionary on client side)
*/
public void addQueryTag(String privateCreator, int tag, VR vr, String value) {
queryatts.setString(privateCreator, tag, vr, value);
}
public void clearQueryKeys() {
this.queryatts = new Attributes();
}
public void addAll(Attributes attrs) {
queryatts.addAll(attrs);
}
public void addReturnTag(int tag) throws Exception {
VR vr = ElementDictionary.vrOf(tag, null);
queryatts.setNull(tag, vr);
}
public void addReturnTag(String privateCreator, int tag, VR vr) throws Exception {
queryatts.setNull(privateCreator, tag, vr);
}
public void setExpectedMatches(int matches) {
this.expectedMatches = matches;
}
private DimseRSPHandler getDimseRSPHandler(int messageID) {
return new DimseRSPHandler(messageID) {
@Override
public void onDimseRSP(Association as, Attributes cmd,
Attributes data) {
super.onDimseRSP(as, cmd, data);
onCFindResponse(cmd, data);
}
};
}
protected void onCFindResponse(Attributes cmd, Attributes data) {
if (numMatches==0) timeFirst = System.currentTimeMillis();
int status = cmd.getInt(Tag.Status, -1);
if (Status.isPending(status)) {
response.add(data);
++numMatches;
}
}
@Override
public void init(TestResult result) {
this.result = result;
}
@Override
public TestResult getResult() {
return this.result;
}
public void setAeTitle(String aeTitle) {
this.aeTitle = aeTitle;
}
public String getQueryLevel() {
return queryLevel;
}
public void setQueryLevel(String level) {
queryLevel = level;
}
}