/******************************************************************************* * Copyright (c) 2009-2019 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v20.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.dicom.util; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Objects; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; 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.Status; import org.dcm4che3.net.pdu.AAssociateRQ; import org.dcm4che3.net.pdu.PresentationContext; import org.dcm4che3.tool.storescu.RelatedGeneralSOPClasses; import org.dcm4che3.util.TagUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.dicom.param.AdvancedParams; import org.weasis.dicom.param.DicomNode; import org.weasis.dicom.param.DicomProgress; import org.weasis.dicom.param.DicomState; import org.weasis.dicom.util.ServiceUtil.ProgressStatus; public class StoreFromStreamSCU { private static Logger LOGGER = LoggerFactory.getLogger(StoreFromStreamSCU.class); @FunctionalInterface public interface RSPHandlerFactory { DimseRSPHandler createDimseRSPHandler(); } private final ApplicationEntity ae; private final Connection remote; private final AAssociateRQ rq = new AAssociateRQ(); public final RelatedGeneralSOPClasses relSOPClasses = new RelatedGeneralSOPClasses(); private Attributes attrs; private boolean relExtNeg; private Association as; private final Device device; private final Connection conn ; private int lastStatusCode = Integer.MIN_VALUE; private int nbStatusLog = 0; private int numberOfSuboperations = 0; private final DicomState state; private final RSPHandlerFactory rspHandlerFactory = () -> new DimseRSPHandler(as.nextMessageID()) { @Override public void onDimseRSP(Association as, Attributes cmd, Attributes data) { super.onDimseRSP(as, cmd, data); onCStoreRSP(cmd); DicomProgress progress = state.getProgress(); if (progress != null) { progress.setAttributes(cmd); } } /** * @see <a * href="ERROR CODE STATUS">https://github.com/dcm4che/dcm4che/blob/master/dcm4che-net/src/main/java/org/dcm4che3/net/Status.java</a> */ private void onCStoreRSP(Attributes cmd) { int status = cmd.getInt(Tag.Status, -1); state.setStatus(status); ProgressStatus ps; switch (status) { case Status.Success: ps = ProgressStatus.COMPLETED; break; case Status.CoercionOfDataElements: case Status.ElementsDiscarded: case Status.DataSetDoesNotMatchSOPClassWarning: ps = ProgressStatus.WARNING; if (lastStatusCode != status && nbStatusLog < 3) { nbStatusLog++; lastStatusCode = status; if (LOGGER.isDebugEnabled()) { LOGGER.warn("Received C-STORE-RSP with Status {}H{}", TagUtils.shortToHexString(status), "\r\n" + cmd.toString()); } else { LOGGER.warn("Received C-STORE-RSP with Status {}H", TagUtils.shortToHexString(status)); } } break; default: ps = ProgressStatus.FAILED; if (lastStatusCode != status && nbStatusLog < 3) { nbStatusLog++; lastStatusCode = status; if (LOGGER.isDebugEnabled()) { LOGGER.error("Received C-STORE-RSP with Status {}H{}", TagUtils.shortToHexString(status), "\r\n" + cmd.toString()); } else { LOGGER.error("Received C-STORE-RSP with Status {}H", TagUtils.shortToHexString(status)); } } } ServiceUtil.notifyProgession(state.getProgress(), cmd, ps, numberOfSuboperations); } }; public StoreFromStreamSCU(DicomNode callingNode, DicomNode calledNode) throws IOException { this(null, callingNode, calledNode, null); } public StoreFromStreamSCU(AdvancedParams params, DicomNode callingNode, DicomNode calledNode) throws IOException { this(params, callingNode, calledNode, null); } public StoreFromStreamSCU(AdvancedParams params, DicomNode callingNode, DicomNode calledNode, DicomProgress progress) throws IOException { Objects.requireNonNull(callingNode); Objects.requireNonNull(calledNode); AdvancedParams options = params == null ? new AdvancedParams() : params; this.state = new DicomState(progress); this.device = new Device("storescu"); this.conn = new Connection(); device.addConnection(conn); this.ae = new ApplicationEntity(callingNode.getAet()); device.addApplicationEntity(ae); ae.addConnection(conn); this.remote = new Connection(); rq.addPresentationContext(new PresentationContext(1, UID.VerificationSOPClass, UID.ImplicitVRLittleEndian)); options.configureConnect(rq, remote, calledNode); options.configureBind(ae, conn, callingNode); // configure options.configure(conn); options.configureTLS(conn, remote); setAttributes(new Attributes()); } public DicomNode getCallingNode() { return new DicomNode(ae.getAETitle(), conn.getHostname(), conn.getPort()); } public DicomNode getCalledNode() { return new DicomNode(rq.getCalledAET(), remote.getHostname(), remote.getPort()); } public Device getDevice() { return device; } public AAssociateRQ getAAssociateRQ() { return rq; } public Connection getRemoteConnection() { return remote; } public Attributes getAttributes() { return attrs; } public void setAttributes(Attributes attrs) { this.attrs = attrs; } public final void enableSOPClassRelationshipExtNeg(boolean enable) { relExtNeg = enable; } public boolean addData(String cuid, String tsuid) { if (cuid == null || tsuid == null) { return false; } if (rq.containsPresentationContextFor(cuid, tsuid)) { return true; } if (!rq.containsPresentationContextFor(cuid)) { if (relExtNeg) { rq.addCommonExtendedNegotiation(relSOPClasses.getCommonExtendedNegotiation(cuid)); } if (!tsuid.equals(UID.ExplicitVRLittleEndian)) { rq.addPresentationContext(new PresentationContext(rq.getNumberOfPresentationContexts() * 2 + 1, cuid, UID.ExplicitVRLittleEndian)); } if (!tsuid.equals(UID.ImplicitVRLittleEndian)) { rq.addPresentationContext(new PresentationContext(rq.getNumberOfPresentationContexts() * 2 + 1, cuid, UID.ImplicitVRLittleEndian)); } } rq.addPresentationContext(new PresentationContext(rq.getNumberOfPresentationContexts() * 2 + 1, cuid, tsuid)); return true; } public void close() throws IOException, InterruptedException { if (as != null) { if (as.isReadyForDataTransfer()) { as.release(); } as.waitForSocketClose(); } } public void open() throws IOException, InterruptedException, IncompatibleConnectionException, GeneralSecurityException { as = ae.connect(remote, rq); // TODO check inactivity of 30 sec and close } public Association getAssociation() { return as; } public int getNumberOfSuboperations() { return numberOfSuboperations; } public void setNumberOfSuboperations(int numberOfSuboperations) { this.numberOfSuboperations = numberOfSuboperations; } public DicomState getState() { return state; } public RSPHandlerFactory getRspHandlerFactory() { return rspHandlerFactory; } }