Java tutorial
/* ***** 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.dcm4che.tool.stgcmtscu; import java.io.File; import java.io.IOException; import java.security.GeneralSecurityException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; 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.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.dcm4che.data.Attributes; import org.dcm4che.data.Sequence; import org.dcm4che.data.Tag; import org.dcm4che.data.UID; import org.dcm4che.data.VR; import org.dcm4che.io.DicomOutputStream; import org.dcm4che.net.ApplicationEntity; import org.dcm4che.net.Association; import org.dcm4che.net.AssociationStateException; import org.dcm4che.net.Commands; import org.dcm4che.net.Connection; import org.dcm4che.net.Device; import org.dcm4che.net.Dimse; import org.dcm4che.net.DimseRSPHandler; import org.dcm4che.net.IncompatibleConnectionException; import org.dcm4che.net.Status; import org.dcm4che.net.TransferCapability; import org.dcm4che.net.pdu.AAssociateRQ; import org.dcm4che.net.pdu.PresentationContext; import org.dcm4che.net.service.AbstractDicomService; import org.dcm4che.net.service.BasicCEchoSCP; import org.dcm4che.net.service.DicomService; import org.dcm4che.net.service.DicomServiceException; import org.dcm4che.net.service.DicomServiceRegistry; import org.dcm4che.tool.common.CLIUtils; import org.dcm4che.tool.common.DicomFiles; import org.dcm4che.util.SafeClose; import org.dcm4che.util.UIDUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Gunter Zeilinger <gunterze@gmail.com> * @author Michael Backhaus <michael.backhaus@agfa.com> */ public class StgCmtSCU { private static ResourceBundle rb = ResourceBundle.getBundle("org.dcm4che.tool.stgcmtscu.messages"); private static final Logger LOG = LoggerFactory.getLogger(StgCmtSCU.class); private final ApplicationEntity ae; private final Connection remote; private final AAssociateRQ rq = new AAssociateRQ(); private Attributes attrs; private String uidSuffix; private File storageDir; private boolean keepAlive; private int splitTag; private int status; private HashMap<String, List<String>> map = new HashMap<String, List<String>>(); private Association as; private final HashSet<String> outstandingResults = new HashSet<String>(2); private final DicomService stgcmtResultHandler = new AbstractDicomService( UID.StorageCommitmentPushModelSOPClass) { @Override public void onDimseRQ(Association as, PresentationContext pc, Dimse dimse, Attributes cmd, Attributes data) throws IOException { if (dimse != Dimse.N_EVENT_REPORT_RQ) throw new DicomServiceException(Status.UnrecognizedOperation); int eventTypeID = cmd.getInt(Tag.EventTypeID, 0); if (eventTypeID != 1 && eventTypeID != 2) throw new DicomServiceException(Status.NoSuchEventType).setEventTypeID(eventTypeID); String tuid = data.getString(Tag.TransactionUID); try { Attributes rsp = Commands.mkNEventReportRSP(cmd, status); Attributes rspAttrs = StgCmtSCU.this.eventRecord(as, cmd, data); as.writeDimseRSP(pc, rsp, rspAttrs); } catch (AssociationStateException e) { LOG.warn("{} << N-EVENT-RECORD-RSP failed: {}", as, e.getMessage()); } finally { removeOutstandingResult(tuid); } } }; public StgCmtSCU(ApplicationEntity ae) throws IOException { this.remote = new Connection(); this.ae = ae; DicomServiceRegistry serviceRegistry = new DicomServiceRegistry(); serviceRegistry.addDicomService(new BasicCEchoSCP()); serviceRegistry.addDicomService(stgcmtResultHandler); ae.setDimseRQHandler(serviceRegistry); } public Connection getRemoteConnection() { return remote; } public AAssociateRQ getAAssociateRQ() { return rq; } public void setStorageDirectory(File storageDir) { if (storageDir != null) storageDir.mkdirs(); this.storageDir = storageDir; } public File getStorageDirectory() { return storageDir; } public final void setUIDSuffix(String uidSuffix) { this.uidSuffix = uidSuffix; } public void setAttributes(Attributes attrs) { this.attrs = attrs; } @SuppressWarnings("unchecked") public static void main(String[] args) { try { CommandLine cl = parseComandLine(args); Device device = new Device("stgcmtscu"); Connection conn = new Connection(); device.addConnection(conn); ApplicationEntity ae = new ApplicationEntity("STGCMTSCU"); device.addApplicationEntity(ae); ae.addConnection(conn); final StgCmtSCU stgcmtscu = new StgCmtSCU(ae); CLIUtils.configureConnect(stgcmtscu.remote, stgcmtscu.rq, cl); CLIUtils.configureBind(conn, stgcmtscu.ae, cl); CLIUtils.configure(conn, cl); stgcmtscu.remote.setTlsProtocols(conn.getTlsProtocols()); stgcmtscu.remote.setTlsCipherSuites(conn.getTlsCipherSuites()); stgcmtscu.setTransferSyntaxes(CLIUtils.transferSyntaxesOf(cl)); stgcmtscu.setStatus(CLIUtils.getIntOption(cl, "status", 0)); stgcmtscu.setSplitTag(getSplitTag(cl)); stgcmtscu.setKeepAlive(cl.hasOption("keep-alive")); stgcmtscu.setStorageDirectory(getStorageDirectory(cl)); stgcmtscu.setAttributes(new Attributes()); CLIUtils.addAttributes(stgcmtscu.attrs, cl.getOptionValues("s")); stgcmtscu.setUIDSuffix(cl.getOptionValue("uid-suffix")); List<String> argList = cl.getArgList(); boolean echo = argList.isEmpty(); if (!echo) { LOG.info(rb.getString("scanning")); DicomFiles.scan(argList, new DicomFiles.Callback() { @Override public boolean dicomFile(File f, Attributes fmi, long dsPos, Attributes ds) { return stgcmtscu.addInstance(ds); } }); } ExecutorService executorService = Executors.newCachedThreadPool(); ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(); device.setExecutor(executorService); device.setScheduledExecutor(scheduledExecutorService); device.bindConnections(); try { stgcmtscu.open(); if (echo) stgcmtscu.echo(); else stgcmtscu.sendRequests(); } finally { stgcmtscu.close(); if (conn.isListening()) { device.waitForNoOpenConnections(); device.unbindConnections(); } executorService.shutdown(); scheduledExecutorService.shutdown(); } } catch (ParseException e) { System.err.println("stgcmtscu: " + e.getMessage()); System.err.println(rb.getString("try")); System.exit(2); } catch (Exception e) { System.err.println("stgcmtscu: " + e.getMessage()); e.printStackTrace(); System.exit(2); } } public static File getStorageDirectory(CommandLine cl) { return cl.hasOption("ignore") ? null : new File(cl.getOptionValue("directory", ".")); } public static int getSplitTag(CommandLine cl) { return cl.hasOption("one-by-study") ? Tag.StudyInstanceUID : cl.hasOption("one-by-series") ? Tag.SeriesInstanceUID : 0; } public void setSplitTag(int splitTag) { this.splitTag = splitTag; } public void setKeepAlive(boolean keepAlive) { this.keepAlive = keepAlive; } public void setStatus(int status) { this.status = status; } public void setTransferSyntaxes(String[] tss) { rq.addPresentationContext(new PresentationContext(1, UID.VerificationSOPClass, UID.ImplicitVRLittleEndian)); rq.addPresentationContext(new PresentationContext(2, UID.StorageCommitmentPushModelSOPClass, tss)); ae.addTransferCapability(new TransferCapability(null, UID.VerificationSOPClass, TransferCapability.Role.SCP, UID.ImplicitVRLittleEndian)); ae.addTransferCapability(new TransferCapability(null, UID.StorageCommitmentPushModelSOPClass, TransferCapability.Role.SCU, tss)); } public boolean addInstance(Attributes inst) { CLIUtils.updateAttributes(inst, attrs, uidSuffix); String cuid = inst.getString(Tag.SOPClassUID); String iuid = inst.getString(Tag.SOPInstanceUID); String splitkey = splitTag != 0 ? inst.getString(splitTag) : ""; if (cuid == null || iuid == null || splitkey == null) return false; List<String> refSOPs = map.get(splitkey); if (refSOPs == null) map.put(splitkey, refSOPs = new ArrayList<String>()); refSOPs.add(cuid); refSOPs.add(iuid); return true; } private static CommandLine parseComandLine(String[] args) throws ParseException { Options opts = new Options(); CLIUtils.addTransferSyntaxOptions(opts); CLIUtils.addConnectOption(opts); CLIUtils.addBindOption(opts, "STGCMTSCU"); CLIUtils.addRequestTimeoutOption(opts); CLIUtils.addAEOptions(opts); CLIUtils.addResponseTimeoutOption(opts); CLIUtils.addCommonOptions(opts); addStgCmtOptions(opts); return CLIUtils.parseComandLine(args, opts, rb, StgCmtSCU.class); } @SuppressWarnings("static-access") public static void addStgCmtOptions(Options opts) { opts.addOption(null, "ignore", false, rb.getString("ignore")); opts.addOption(OptionBuilder.hasArg().withArgName("path").withDescription(rb.getString("directory")) .withLongOpt("directory").create(null)); opts.addOption(OptionBuilder.hasArg().withArgName("code").withDescription(rb.getString("status")) .withLongOpt("status").create(null)); opts.addOption(null, "keep-alive", false, rb.getString("keep-alive")); opts.addOption(null, "one-per-study", false, rb.getString("one-per-study")); opts.addOption(null, "one-per-series", false, rb.getString("one-per-series")); opts.addOption(OptionBuilder.hasArgs().withArgName("[seq/]attr=value").withValueSeparator('=') .withDescription(rb.getString("set")).create("s")); opts.addOption(OptionBuilder.hasArg().withArgName("suffix").withDescription(rb.getString("uid-suffix")) .withLongOpt("uid-suffix").create(null)); } public void open() throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException { as = ae.connect(remote, rq); } public void echo() throws IOException, InterruptedException { as.cecho().next(); } public void close() throws IOException, InterruptedException { if (as != null) { if (as.isReadyForDataTransfer()) { as.waitForOutstandingRSP(); if (keepAlive) waitForOutstandingResults(); as.release(); } as.waitForSocketClose(); } waitForOutstandingResults(); } private void addOutstandingResult(String tuid) { synchronized (outstandingResults) { outstandingResults.add(tuid); } } private void removeOutstandingResult(String tuid) { synchronized (outstandingResults) { outstandingResults.remove(tuid); outstandingResults.notify(); } } private void waitForOutstandingResults() throws InterruptedException { synchronized (outstandingResults) { while (!outstandingResults.isEmpty()) { System.out .println(MessageFormat.format(rb.getString("wait-for-results"), outstandingResults.size())); outstandingResults.wait(); } } } public Attributes makeActionInfo(List<String> refSOPs) { Attributes actionInfo = new Attributes(2); actionInfo.setString(Tag.TransactionUID, VR.UI, UIDUtils.createUID()); int n = refSOPs.size() / 2; Sequence refSOPSeq = actionInfo.newSequence(Tag.ReferencedSOPSequence, n); for (int i = 0, j = 0; j < n; j++) { Attributes refSOP = new Attributes(2); refSOP.setString(Tag.ReferencedSOPClassUID, VR.UI, refSOPs.get(i++)); refSOP.setString(Tag.ReferencedSOPInstanceUID, VR.UI, refSOPs.get(i++)); refSOPSeq.add(refSOP); } return actionInfo; } public void sendRequests() throws IOException, InterruptedException { for (List<String> refSOPs : map.values()) sendRequest(makeActionInfo(refSOPs)); } private void sendRequest(Attributes actionInfo) throws IOException, InterruptedException { final String tuid = actionInfo.getString(Tag.TransactionUID); DimseRSPHandler rspHandler = new DimseRSPHandler(as.nextMessageID()) { @Override public void onDimseRSP(Association as, Attributes cmd, Attributes data) { if (cmd.getInt(Tag.Status, -1) != Status.Success) removeOutstandingResult(tuid); super.onDimseRSP(as, cmd, data); } }; as.naction(UID.StorageCommitmentPushModelSOPClass, UID.StorageCommitmentPushModelSOPInstance, 1, actionInfo, null, rspHandler); addOutstandingResult(tuid); } private Attributes eventRecord(Association as, Attributes cmd, Attributes eventInfo) throws DicomServiceException { if (storageDir == null) return null; String cuid = cmd.getString(Tag.AffectedSOPClassUID); String iuid = cmd.getString(Tag.AffectedSOPInstanceUID); String tuid = eventInfo.getString(Tag.TransactionUID); File file = new File(storageDir, tuid); DicomOutputStream out = null; LOG.info("{}: M-WRITE {}", as, file); try { out = new DicomOutputStream(file); out.writeDataset(Attributes.createFileMetaInformation(iuid, cuid, UID.ExplicitVRLittleEndian), eventInfo); } catch (IOException e) { LOG.warn(as + ": Failed to store Storage Commitment Result:", e); throw new DicomServiceException(Status.ProcessingFailure, e); } finally { SafeClose.close(out); } return null; } }