/* ***** 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) 2012
* 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.probetc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.MissingOptionException;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.dcm4che3.conf.api.ConfigurationException;
import org.dcm4che3.conf.ldap.LdapDicomConfiguration;
import org.dcm4che3.conf.prefs.PreferencesDicomConfiguration;
import org.dcm4che3.net.ApplicationEntity;
import org.dcm4che3.net.Association;
import org.dcm4che3.net.Connection;
import org.dcm4che3.net.Device;
import org.dcm4che3.net.IncompatibleConnectionException;
import org.dcm4che3.net.TransferCapability;
import org.dcm4che3.net.TransferCapability.Role;
import org.dcm4che3.net.pdu.AAssociateRQ;
import org.dcm4che3.net.pdu.PresentationContext;
import org.dcm4che3.tool.common.CLIUtils;
import org.dcm4che3.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Hesham Elbadawi <bsdreko@gmail.com>
*
*/
public class ProbeTC {
private String destinationAET;
private static String probedAET;
private String sourceAET;
private ApplicationEntity probedAE;
private File ldapConfigurationFile;
private String configurationType;
private String configType;
private String callingAET;
static final Logger LOG = LoggerFactory.getLogger(ProbeTC.class);
private static Options opts;
private static ResourceBundle rb = ResourceBundle
.getBundle("org.dcm4che3.tool.probetc.messages");
private Connection destination;
private AAssociateRQ rq = new AAssociateRQ();
ProbeTC() {
}
public ProbeTC(String probedae, String destinationaet, String sourceaet,
String callingaet, String configurationType,
File ldapConfigurationFile) throws ParseException {
if (probedae == null || destinationaet == null || sourceaet == null
|| callingaet == null || configurationType == null) {
LOG.error("null initialization parameter");
throw new NullPointerException();
}
this.setConfigType(configurationType);
this.setDestinationAET(destinationaet);
if (ldapConfigurationFile != null
&& configurationType.compareToIgnoreCase("ldap") == 0)
this.setLdapConfigurationFile(ldapConfigurationFile);
else if (configurationType.compareToIgnoreCase("ldap") == 0) {
LOG.error("Ldap properties file has to be a valid file");
throw new NullPointerException();
}
this.setSourceAET(sourceaet);
String tmpOption = probedae;
String aeTitle = tmpOption.split("@")[0];
if (tmpOption.split("@")[1] == null)
throw new ParseException(rb.getString("invalid-probed-ae"));
String host = tmpOption.split("@")[1].split(":")[0];
int port = Integer.parseInt(tmpOption.split("@")[1].split(":")[1]);
probedAET = aeTitle;
Connection conn = new Connection();
conn.setHostname(host);
conn.setPort(port);
conn.setInstalled(true);
setDestination(conn);
this.setProbedAE(new ApplicationEntity(aeTitle));
this.getProbedAE().addConnection(conn);
this.setCallingAET(callingaet);
}
@SuppressWarnings("static-access")
private static CommandLine parseComandLine(String[] args)
throws ParseException {
opts = new Options();
opts.addOption(OptionBuilder.hasArg().withArgName("aet@host:port")
.withDescription(rb.getString("connection"))
.withLongOpt("connection").create("c"));
opts.addOption("d", "destination-aet", true,
rb.getString("destination-aet"));
opts.addOption("s", "source-aet", true, rb.getString("source-aet"));
opts.addOption("b", "broadcastTitle", true, rb.getString("broadcastTitle"));
opts.addOption("ldap", "ldap", true,
rb.getString("ldap"));
opts.addOption("prefs", "prefs", false,
rb.getString("prefs"));
CLIUtils.addCommonOptions(opts);
return CLIUtils.parseComandLine(args, opts, rb, ProbeTC.class);
}
public void probeAndSet() {
ProbeTC instance = this;
Device device = new Device(instance.getCallingAET());
Connection conn = new Connection();
device.addConnection(conn);
ApplicationEntity ae = new ApplicationEntity(instance.getCallingAET());
device.addApplicationEntity(ae);
ae.addConnection(conn);
conn.setReceivePDULength(Connection.DEF_MAX_PDU_LENGTH);
conn.setSendPDULength(Connection.DEF_MAX_PDU_LENGTH);
conn.setMaxOpsInvoked(0);
conn.setMaxOpsPerformed(0);
conn.setPackPDV(true);
conn.setConnectTimeout(0);
conn.setRequestTimeout(0);
conn.setAcceptTimeout(0);
conn.setReleaseTimeout(0);
conn.setResponseTimeout(0);
conn.setRetrieveTimeout(0);
conn.setIdleTimeout(0);
conn.setSocketCloseDelay(Connection.DEF_SOCKETDELAY);
conn.setSendBufferSize(0);
conn.setReceiveBufferSize(0);
conn.setTcpNoDelay(true);
// no tls in this implementation (for tls use command line tool)
if (instance.getConfigType().compareToIgnoreCase("ldap") == 0) {
InputStream is = null;
try {
is = new FileInputStream(instance.getLdapConfigurationFile());
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Properties p = new Properties();
try {
p.load(is);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
LdapDicomConfiguration conf = new LdapDicomConfiguration(p);
LOG.info("Started Loading LDAP configuration");
ApplicationEntity sourceAE = conf
.findApplicationEntity(instance.sourceAET);
ArrayList<TransferCapability> tcs = (ArrayList<TransferCapability>) sourceAE
.getTransferCapabilities();
ArrayList<PresentationContext> pcs = addChunkedPCsandSend(ae,
device, instance, tcs);
// print accepted ones
ArrayList<PresentationContext> acceptedPCs = new ArrayList<PresentationContext>();
for (PresentationContext pc : pcs)
if (pc.isAccepted())
acceptedPCs.add(pc);
ApplicationEntity destinationAE = conf
.findApplicationEntity(instance.destinationAET);
Device toStore = destinationAE.getDevice();
TransferCapability[] TCs = mergeTCs(acceptedPCs);
for (TransferCapability tc : TCs)
toStore.getApplicationEntity(instance.destinationAET)
.addTransferCapability(tc);
conf.merge(toStore);
conf.close();
return;
} catch (ConfigurationException e) {
LOG.error("Configuration backend error - {}", e);
}
} else {
try {
PreferencesDicomConfiguration conf = new PreferencesDicomConfiguration();
LOG.info("Started Loading LDAP configuration");
ApplicationEntity sourceAE = conf
.findApplicationEntity(instance.sourceAET);
ArrayList<TransferCapability> tcs = (ArrayList<TransferCapability>) sourceAE
.getTransferCapabilities();
ArrayList<PresentationContext> pcs = addChunkedPCsandSend(ae,
device, instance, tcs);
// print accepted ones
ArrayList<PresentationContext> acceptedPCs = new ArrayList<PresentationContext>();
for (PresentationContext pc : pcs)
if (pc.isAccepted())
acceptedPCs.add(pc);
ApplicationEntity destinationAE = conf
.findApplicationEntity(instance.destinationAET);
Device toStore = destinationAE.getDevice();
TransferCapability[] TCs = mergeTCs(acceptedPCs);
for (TransferCapability tc : TCs)
toStore.getApplicationEntity(instance.destinationAET)
.addTransferCapability(tc);
conf.merge(toStore);
conf.close();
return;
} catch (ConfigurationException e) {
LOG.error("Configuration backend error - {}", e);
}
}
}
public static void main(String[] args) throws ParseException, IOException,
InterruptedException, IncompatibleConnectionException,
GeneralSecurityException, IllegalAccessException {
CommandLine cl = null;
cl = parseComandLine(args);
ProbeTC instance = new ProbeTC();
Device device = null;
if (cl.hasOption("b")) {
device = new Device(cl.getOptionValue("b").toLowerCase());
} else {
LOG.error("Missing broadcast AETitle");
throw new IllegalAccessException("missing broadcast AETitle");
}
Connection conn = new Connection();
device.addConnection(conn);
ApplicationEntity ae = new ApplicationEntity(cl.getOptionValue("b")
.toUpperCase());
if (cl.hasOption("d")) {
instance.setDestinationAET(cl.getOptionValue("d"));
}
if (cl.hasOption("s")) {
instance.setSourceAET(cl.getOptionValue("s"));
}
device.addApplicationEntity(ae);
ae.addConnection(conn);
instance.destination = new Connection();
configureConnect(instance.destination, instance.rq, cl, ae);
CLIUtils.configure(conn, cl);
// instance.sourceAE = new
// ApplicationEntity(cl.getOptionValue("s").split(":")[0]);
// here load the TCs
if (cl.hasOption("ldap")) {
try {
InputStream is = null;
Properties p = new Properties();
if (!cl.getOptionValue("ldap").isEmpty()) {
is = new FileInputStream(new File(
cl.getOptionValue("ldap")));
} else {
LOG.error("Missing ldap properties file");
throw new IllegalAccessException("missing ldap properties file");
}
p.load(is);
LdapDicomConfiguration conf = new LdapDicomConfiguration(p);
LOG.info("Started Loading LDAP configuration");
ArrayList<TransferCapability> tcs = null;
if(cl.hasOption("s")){
ApplicationEntity sourceAE = conf
.findApplicationEntity(instance.sourceAET);
tcs = (ArrayList<TransferCapability>) sourceAE
.getTransferCapabilities();
}
else
{
tcs = loadTCFile();
}
ArrayList<PresentationContext> pcs = addChunkedPCsandSend(
ae, device, instance, tcs);
// print accepted ones
ArrayList<PresentationContext> acceptedPCs = new ArrayList<PresentationContext>();
for (PresentationContext pc : pcs)
if (pc.isAccepted())
acceptedPCs.add(pc);
LOG.info("Probed the source ae and found the following accepted presentation contexts");
for (PresentationContext pc : acceptedPCs) {
LOG.info("PC[" + pc.getPCID() + "]\tAbstractSyntax:"
+ pc.getAbstractSyntax() + "\n with "
+ " the following Transfer-Syntax:["
+ pc.getTransferSyntax() + "]");
}
LOG.info("finished probing TCs");
if(instance.destinationAET!=null){
LOG.info("Adding Accepted TCs to configuration backend");
ApplicationEntity destinationAE = conf
.findApplicationEntity(instance.destinationAET);
Device toStore = destinationAE.getDevice();
TransferCapability[] TCs = mergeTCs(acceptedPCs);
for (TransferCapability tc : TCs)
toStore.getApplicationEntity(instance.destinationAET)
.addTransferCapability(tc);
conf.merge(toStore);
logAddedTCs(TCs, destinationAE);
conf.close();
}
System.exit(1);
} catch (ConfigurationException e) {
LOG.error("Configuration backend error - {}", e);
}
} else if (cl.hasOption("prefs")) {
// prefs
try {
PreferencesDicomConfiguration conf = new PreferencesDicomConfiguration();
LOG.info("Started Loading LDAP configuration");
ArrayList<TransferCapability> tcs = null;
if(cl.hasOption("s")){
ApplicationEntity sourceAE = conf
.findApplicationEntity(instance.sourceAET);
tcs = (ArrayList<TransferCapability>) sourceAE
.getTransferCapabilities();
}
else
{
tcs = loadTCFile();
}
ArrayList<PresentationContext> pcs = addChunkedPCsandSend(
ae, device, instance, tcs);
// print accepted ones
ArrayList<PresentationContext> acceptedPCs = new ArrayList<PresentationContext>();
for (PresentationContext pc : pcs)
if (pc.isAccepted())
acceptedPCs.add(pc);
LOG.info("Probed the source ae and found the following accepted presentation contexts");
for (PresentationContext pc : acceptedPCs) {
LOG.info("PC[" + pc.getPCID() + "]\tAbstractSyntax:"
+ pc.getAbstractSyntax() + "\n with "
+ " the following Transfer-Syntax:["
+ pc.getTransferSyntax() + "]");
}
LOG.info("finished probing TCs");
if(instance.destinationAET!=null){
LOG.info("Adding Accepted TCs to configuration backend");
ApplicationEntity destinationAE = conf
.findApplicationEntity(instance.destinationAET);
Device toStore = destinationAE.getDevice();
TransferCapability[] TCs = mergeTCs(acceptedPCs);
for (TransferCapability tc : TCs)
toStore.getApplicationEntity(instance.destinationAET)
.addTransferCapability(tc);
conf.merge(toStore);
logAddedTCs(TCs, destinationAE);
conf.close();
}
System.exit(1);
} catch (ConfigurationException e) {
LOG.error("Configuration backend error - {}", e);
}
}
else
{
LOG.info("Started Loading TCS from file no configuration set or get");
ArrayList<TransferCapability> tcs = null;
tcs = loadTCFile();
LOG.info("added the following presentation contexts: " +tcs.get(0).getSopClass());
ArrayList<PresentationContext> pcs = addChunkedPCsandSend(
ae, device, instance, tcs);
// print accepted ones
ArrayList<PresentationContext> acceptedPCs = new ArrayList<PresentationContext>();
for (PresentationContext pc : pcs)
if (pc.isAccepted())
acceptedPCs.add(pc);
LOG.info("Probed the source ae and found the following accepted presentation contexts");
for (PresentationContext pc : acceptedPCs) {
LOG.info("PC[" + pc.getPCID() + "]\tAbstractSyntax:"
+ pc.getAbstractSyntax() + "\n with "
+ " the following Transfer-Syntax:["
+ pc.getTransferSyntax() + "]");
}
LOG.info("finished probing TCs");
System.exit(1);
}
}
private static ArrayList<TransferCapability> loadTCFile() {
ArrayList<TransferCapability> tcs = new ArrayList<TransferCapability> ();
Properties p=null;
try {
p = CLIUtils.loadProperties(
"resource:sampleTCFile.properties",
null);
} catch (IOException e) {
LOG.error("unable to load sop-classes properties file");
}
for (String cuid : p.stringPropertyNames()) {
String ts = p.getProperty(cuid);
LOG.info(ts);
tcs.add(
new TransferCapability(null, ts.split(":")[0],
TransferCapability.Role.SCP,
StringUtils.split(ts.split(":")[1], ',')));
}
return tcs;
}
public static void configureConnect(Connection conn, AAssociateRQ rq,
CommandLine cl, ApplicationEntity ae) throws ParseException,
IOException {
if (!cl.hasOption("c"))
throw new MissingOptionException(rb.getString("missing-probed-ae"));
String tmpOption = cl.getOptionValue("c");
String aeTitle = tmpOption.split("@")[0];
if (tmpOption.split("@")[1] == null)
throw new ParseException(rb.getString("invalid-probed-ae"));
String host = tmpOption.split("@")[1].split(":")[0];
int port = Integer.parseInt(tmpOption.split("@")[1].split(":")[1]);
probedAET = aeTitle;
conn.setHostname(host);
conn.setPort(port);
conn.setInstalled(true);
ae = new ApplicationEntity(aeTitle);
ae.addConnection(conn);
}
public static Association openAssociation(ApplicationEntity ae,
AAssociateRQ rq, Connection remote) throws IOException,
InterruptedException, IncompatibleConnectionException,
GeneralSecurityException {
Association as = ae.connect(remote, rq);
return as;
}
private static void logAddedTCs(TransferCapability[] tCs,
ApplicationEntity destinationAE) {
File log = new File("probe-tc-log-[ae=" + probedAET + "]");
FileWriter logWriter = null;
try {
logWriter = new FileWriter(log);
} catch (IOException e) {
LOG.error("Unable to get output stream for log file - {}", e);
}
try {
for (TransferCapability tc : tCs)
logWriter.write(tc.toString());
} catch (IOException e) {
LOG.error("Error writing log for transfer capabilities set - {}", e);
} finally {
try {
logWriter.close();
} catch (IOException e) {
LOG.error("Unable to close log File - {} - {}", log.getName(),
e);
}
}
}
private static TransferCapability[] mergeTCs(
ArrayList<PresentationContext> acceptedPCs) {
ArrayList<TransferCapability> tmpTCs = new ArrayList<TransferCapability>();
for (PresentationContext pc : acceptedPCs) {
String abstractSyntax = pc.getAbstractSyntax();
if (containsAbstractSyntax(tmpTCs, abstractSyntax)) {
continue;
}
TransferCapability tmpTC = new TransferCapability();
tmpTC.setRole(Role.SCP);
ArrayList<String> tmpTS = new ArrayList<String>();
tmpTC.setSopClass(abstractSyntax);
for (PresentationContext tmp : acceptedPCs) {
if (tmp.getAbstractSyntax().compareToIgnoreCase(abstractSyntax) == 0) {
if (!tmpTS.contains(tmp.getTransferSyntax())) {
tmpTS.add(tmp.getTransferSyntax());
}
}
}
String[] tmpTSStr = new String[tmpTS.size()];
tmpTS.toArray(tmpTSStr);
tmpTC.setTransferSyntaxes(tmpTSStr);
tmpTCs.add(tmpTC);
}
TransferCapability[] TCs = new TransferCapability[tmpTCs.size()];
tmpTCs.toArray(TCs);
return TCs;
}
private static boolean containsAbstractSyntax(
ArrayList<TransferCapability> tmpTCs, String abstractSyntax) {
for (TransferCapability tc : tmpTCs) {
if (tc.getSopClass().compareToIgnoreCase(abstractSyntax) == 0) {
return true;
}
}
return false;
}
private static ArrayList<PresentationContext> addChunkedPCsandSend(
ApplicationEntity ae, Device device, ProbeTC instance,
ArrayList<TransferCapability> tcs) {
initThreads(device);
int pcID = 1;
ArrayList<ArrayList<PresentationContext>> lst = new ArrayList<ArrayList<PresentationContext>>();
ArrayList<PresentationContext> fullListSingleTS = new ArrayList<PresentationContext>();
ArrayList<PresentationContext> allACPCs = new ArrayList<PresentationContext>();
for (TransferCapability tc : tcs)
if(tcs.size()>127)
for (String ts : tc.getTransferSyntaxes()) {
fullListSingleTS.add(new PresentationContext(pcID, tc
.getSopClass(), ts));
pcID++;
if (fullListSingleTS.size() > 127) {
lst.add(fullListSingleTS);
pcID = 1;
fullListSingleTS = new ArrayList<PresentationContext>();
}
}
else{
for (String ts : tc.getTransferSyntaxes()) {
fullListSingleTS.add(new PresentationContext(pcID, tc
.getSopClass(), ts));
pcID++;
}
lst.add(fullListSingleTS);
}
instance.rq.setCallingAET(ae.getAETitle());
instance.rq.setCalledAET(probedAET);
// now start sending 128 each
for (ArrayList<PresentationContext> subList : lst) {
instance.rq = new AAssociateRQ();
instance.rq.setCallingAET(ae.getAETitle());
instance.rq.setCalledAET(probedAET);
for (PresentationContext pc : subList)
instance.rq.addPresentationContext(pc);
try {
// send
Association as = openAssociation(ae, instance.rq,
instance.destination);
// cache the pcs
for (PresentationContext pcAC : as.getAAssociateAC()
.getPresentationContexts()) {
if (pcAC.isAccepted())
allACPCs.add(instance.rq.getPresentationContext(pcAC
.getPCID()));
}
as.release();
} catch (Exception e) {
e.printStackTrace();
// LOG.info("destination rejected the association for the following reason:\n"
// + as.getException());
System.exit(1);
}
}
return allACPCs;
}
private static void initThreads(Device device) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
ScheduledExecutorService scheduledExecutorService = Executors
.newSingleThreadScheduledExecutor();
device.setExecutor(executorService);
device.setScheduledExecutor(scheduledExecutorService);
}
public String getDestinationAET() {
return destinationAET;
}
public void setDestinationAET(String destinationAET) {
this.destinationAET = destinationAET;
}
public String getSourceAET() {
return sourceAET;
}
public void setSourceAET(String sourceAET) {
this.sourceAET = sourceAET;
}
public ApplicationEntity getProbedAE() {
return probedAE;
}
public void setProbedAE(ApplicationEntity probedAE) {
this.probedAE = probedAE;
}
public File getLdapConfigurationFile() {
return ldapConfigurationFile;
}
public void setLdapConfigurationFile(File ldapConfigurationFile) {
this.ldapConfigurationFile = ldapConfigurationFile;
}
public String getConfigurationType() {
return configurationType;
}
public void setConfigurationType(String configurationType) {
this.configurationType = configurationType;
}
public String getConfigType() {
return configType;
}
public void setConfigType(String configType) {
this.configType = configType;
}
public Connection getDestination() {
return destination;
}
public void setDestination(Connection destination) {
this.destination = destination;
}
public AAssociateRQ getRq() {
return rq;
}
public void setRq(AAssociateRQ rq) {
this.rq = rq;
}
public String getCallingAET() {
return callingAET;
}
public void setCallingAET(String callingAET) {
this.callingAET = callingAET;
}
}