/******************************************************************************* * 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.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.dcm4che3.data.Attributes; import org.dcm4che3.data.Tag; import org.dcm4che3.data.UID; import org.dcm4che3.imageio.codec.Decompressor; import org.dcm4che3.io.DicomInputStream; import org.dcm4che3.io.DicomInputStream.IncludeBulkData; import org.dcm4che3.net.Association; import org.dcm4che3.net.DataWriter; import org.dcm4che3.net.DataWriterAdapter; import org.dcm4che3.net.InputStreamDataWriter; import org.dcm4che3.net.PDVInputStream; import org.dcm4che3.net.Status; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.weasis.core.util.FileUtil; import org.weasis.dicom.param.AttributeEditor; import org.weasis.dicom.param.AttributeEditorContext; import org.weasis.dicom.param.AttributeEditorContext.Abort; import org.weasis.dicom.param.DicomForwardDestination; import org.weasis.dicom.param.DicomNode; import org.weasis.dicom.param.ForwardDestination; import org.weasis.dicom.param.ForwardDicomNode; import org.weasis.dicom.util.ServiceUtil.ProgressStatus; import org.weasis.dicom.web.UploadSingleFile; import org.weasis.dicom.web.WebForwardDestination; public class ForwardUtil { private static final String ERROR_WHEN_FORWARDING = "Error when forwarding to the final destination"; private static final Logger LOGGER = LoggerFactory.getLogger(ForwardUtil.class); public static final class Params { private final String iuid; private final String cuid; private final String tsuid; private final InputStream data; private final Association as; private final int priority; private String outputTsuid; public Params(String iuid, String cuid, String tsuid, int priority, InputStream data, Association as) { super(); this.iuid = iuid; this.cuid = cuid; this.tsuid = tsuid; this.priority = priority; this.as = as; this.data = data; this.outputTsuid = tsuid; } public String getIuid() { return iuid; } public String getCuid() { return cuid; } public String getTsuid() { return tsuid; } public int getPriority() { return priority; } public Association getAs() { return as; } public InputStream getData() { return data; } public String getOutputTsuid() { return outputTsuid; } public void setOutputTsuid(String outputTsuid) { this.outputTsuid = outputTsuid; } } private static final class AbortException extends IllegalStateException { private static final long serialVersionUID = 3993065212756372490L; public AbortException(String s) { super(s); } public AbortException(String string, Exception e) { super(string, e); } @Override public String toString() { return getMessage(); } } private ForwardUtil() { } public static void storeMulitpleDestination(ForwardDicomNode fwdNode, List<ForwardDestination> destList, Params p) throws Exception { if (destList == null || destList.isEmpty()) { throw new IllegalStateException("Cannot find the DICOM destination from " + fwdNode.toString()); } // Exclude DICOMDIR if ("1.2.840.10008.1.3.10".equals(p.cuid)) { LOGGER.warn("Cannot send DICOMDIR {}", p.iuid); return; } if (destList.size() == 1) { storeOneDestination(fwdNode, destList.get(0), p); } else { List<ForwardDestination> destConList = new ArrayList<>(); for (ForwardDestination fwDest : destList) { try { if (fwDest instanceof DicomForwardDestination) { prepareTransfer((DicomForwardDestination) fwDest, p.getCuid(), p.getTsuid()); } destConList.add(fwDest); } catch (Exception e) { LOGGER.error("Cannot connect to the final destination", e); } } if (destConList.isEmpty()) { return; } else if (destConList.size() == 1) { storeOneDestination(fwdNode, destConList.get(0), p); } else { List<File> files = null; try { Attributes attributes = new Attributes(); ForwardDestination fistDest = destConList.get(0); if (fistDest instanceof DicomForwardDestination) { files = transfer(fwdNode, (DicomForwardDestination) fistDest, attributes, p); } else if (fistDest instanceof WebForwardDestination) { files = transfer(fwdNode, (WebForwardDestination) fistDest, null, p); } if (!attributes.isEmpty()) { for (int i = 1; i < destConList.size(); i++) { ForwardDestination dest = destConList.get(i); if (dest instanceof DicomForwardDestination) { transferOther(fwdNode, (DicomForwardDestination) dest, attributes, p); } else if (dest instanceof WebForwardDestination) { transferOther(fwdNode, (WebForwardDestination) dest, attributes, p); } } } } finally { if (files != null) { // Force to clean if tmp bulk files for (File file : files) { FileUtil.delete(file); } } } } } } public static void storeOneDestination(ForwardDicomNode fwdNode, ForwardDestination destination, Params p) throws Exception { if (destination instanceof DicomForwardDestination) { DicomForwardDestination dest = (DicomForwardDestination) destination; prepareTransfer(dest, p.getCuid(), p.getTsuid()); transfer(fwdNode, dest, null, p); } else if (destination instanceof WebForwardDestination) { transfer(fwdNode, (WebForwardDestination) destination, null, p); } } public static StoreFromStreamSCU prepareTransfer(DicomForwardDestination destination, String cuid, String tsuid) throws Exception { StoreFromStreamSCU streamSCU = destination.getStreamSCU(); if (streamSCU.getAssociation() == null) { destination.getStreamSCUService().start(); // Add Presentation Context for the association streamSCU.addData(cuid, tsuid); streamSCU.open(); } else { // Handle dynamically new SOPClassUID Set<String> tss = streamSCU.getAssociation().getTransferSyntaxesFor(cuid); if (!tss.contains(tsuid)) { streamSCU.close(); } // Add Presentation Context for the association streamSCU.addData(cuid, tsuid); if (!streamSCU.getAssociation().isReadyForDataTransfer()) { // If connection has been closed just reopen streamSCU.open(); } } return streamSCU; } public static List<File> transfer(ForwardDicomNode sourceNode, DicomForwardDestination destination, Attributes copy, Params p) { StoreFromStreamSCU streamSCU = destination.getStreamSCU(); DicomInputStream in = null; List<File> files = null; try { if (!streamSCU.getAssociation().isReadyForDataTransfer()) { throw new IllegalStateException("Association not ready for transfer."); } DataWriter dataWriter; String tsuid = p.getTsuid(); String iuid = p.getIuid(); String supportedTsuid = selectTransferSyntax(streamSCU.getAssociation(), p.getCuid(), tsuid); AttributeEditor editor = destination.getAttributesEditor(); if (copy == null && editor == null && supportedTsuid.equals(tsuid)) { dataWriter = new InputStreamDataWriter(p.getData()); } else { AttributeEditorContext context = new AttributeEditorContext(tsuid, sourceNode, DicomNode.buildRemoteDicomNode(streamSCU.getAssociation())); in = new DicomInputStream(p.getData(), tsuid); in.setIncludeBulkData(IncludeBulkData.URI); Attributes attributes = in.readDataset(-1, -1); if (editor != null && editor.apply(attributes, context)) { iuid = attributes.getString(Tag.SOPInstanceUID); } if (context.getAbort() == Abort.FILE_EXCEPTION) { if (p.getData() instanceof PDVInputStream) { ((PDVInputStream) p.getData()).skipAll(); } throw new IllegalStateException(context.getAbortMessage()); } else if (context.getAbort() == Abort.CONNECTION_EXCEPTION) { if (p.getAs() != null) { p.getAs().abort(); } throw new AbortException("DICOM association abort: " + context.getAbortMessage()); } if (copy != null) { copy.addAll(attributes); } if (!supportedTsuid.equals(tsuid)) { Decompressor.decompress(attributes, tsuid); } dataWriter = new DataWriterAdapter(attributes); } streamSCU.getAssociation().cstore(p.getCuid(), iuid, p.getPriority(), dataWriter, supportedTsuid, streamSCU.getRspHandlerFactory().createDimseRSPHandler()); } catch (AbortException e) { ServiceUtil.notifyProgession(streamSCU.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, streamSCU.getNumberOfSuboperations()); throw e; } catch (Exception e) { LOGGER.error(ERROR_WHEN_FORWARDING, e); ServiceUtil.notifyProgession(streamSCU.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, streamSCU.getNumberOfSuboperations()); throw new AbortException("StoreSCU abort: " + e.getMessage(), e); } finally { files = cleanOrGetBulkDataFiles(in, copy == null); } return files; } private static List<File> cleanOrGetBulkDataFiles(DicomInputStream in, boolean clean) { FileUtil.safeClose(in); if (clean) { // Force to clean if tmp bulk files ServiceUtil.safeClose(in); } else if (in != null) { // Return tmp bulk files return in.getBulkDataFiles(); } return null; } public static void transferOther(ForwardDicomNode fwdNode, DicomForwardDestination destination, Attributes copy, Params p) { StoreFromStreamSCU streamSCU = destination.getStreamSCU(); try { if (!streamSCU.getAssociation().isReadyForDataTransfer()) { throw new IllegalStateException("Association not ready for transfer."); } DataWriter dataWriter; String tsuid = p.getTsuid(); String iuid = p.getIuid(); String supportedTsuid = selectTransferSyntax(streamSCU.getAssociation(), p.getCuid(), tsuid); AttributeEditor editor = destination.getAttributesEditor(); if (editor == null && supportedTsuid.equals(tsuid)) { dataWriter = new DataWriterAdapter(copy); } else { AttributeEditorContext context = new AttributeEditorContext(tsuid, fwdNode, DicomNode.buildRemoteDicomNode(streamSCU.getAssociation())); Attributes attributes = new Attributes(copy); if (editor != null && editor.apply(attributes, context)) { iuid = attributes.getString(Tag.SOPInstanceUID); } if (context.getAbort() == Abort.FILE_EXCEPTION) { throw new IllegalStateException(context.getAbortMessage()); } else if (context.getAbort() == Abort.CONNECTION_EXCEPTION) { throw new AbortException("DICOM associtation abort. " + context.getAbortMessage()); } if (!supportedTsuid.equals(tsuid)) { Decompressor.decompress(attributes, tsuid); } dataWriter = new DataWriterAdapter(attributes); } streamSCU.getAssociation().cstore(p.getCuid(), iuid, p.getPriority(), dataWriter, supportedTsuid, streamSCU.getRspHandlerFactory().createDimseRSPHandler()); } catch (AbortException e) { ServiceUtil.notifyProgession(streamSCU.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, streamSCU.getNumberOfSuboperations()); throw e; } catch (Exception e) { LOGGER.error(ERROR_WHEN_FORWARDING, e); ServiceUtil.notifyProgession(streamSCU.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, streamSCU.getNumberOfSuboperations()); throw new AbortException("StoreSCU abort: " + e.getMessage(), e); } } public static List<File> transfer(ForwardDicomNode fwdNode, WebForwardDestination destination, Attributes copy, Params p) { DicomInputStream in = null; List<File> files = null; try { UploadSingleFile stow = destination.getStowrsSingleFile(); String tsuid = p.getTsuid(); boolean originalTsuid = true; if(UID.ImplicitVRLittleEndian.equals(tsuid) || UID.ExplicitVRBigEndianRetired.equals(tsuid)) { p.setOutputTsuid(UID.ExplicitVRLittleEndian); originalTsuid = false; } if (originalTsuid && copy == null && destination.getAttributesEditor() == null) { Attributes fmi = Attributes.createFileMetaInformation(p.getIuid(), p.getCuid(), p.getOutputTsuid()); try (InputStream stream = p.getData()) { stow.uploadDicom(p.getData(), fmi, p.getOutputTsuid(), p.getIuid()); } } else { AttributeEditorContext context = new AttributeEditorContext(p.getOutputTsuid(), fwdNode, null); in = new DicomInputStream(p.getData(), tsuid); in.setIncludeBulkData(IncludeBulkData.URI); Attributes attributes = in.readDataset(-1, -1); destination.getAttributesEditor().apply(attributes, context); if (context.getAbort() == Abort.FILE_EXCEPTION) { if (p.getData() instanceof PDVInputStream) { ((PDVInputStream) p.getData()).skipAll(); } throw new IllegalStateException(context.getAbortMessage()); } else if (context.getAbort() == Abort.CONNECTION_EXCEPTION) { if (p.getAs() != null) { p.getAs().abort(); } throw new AbortException("STOWRS abort: " + context.getAbortMessage()); } if (copy != null) { copy.addAll(attributes); } stow.uploadDicom(attributes, p.getOutputTsuid()); ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.Success, ProgressStatus.COMPLETED, 0); } } catch (AbortException e) { ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, 0); throw e; } catch (Exception e) { LOGGER.error(ERROR_WHEN_FORWARDING, e); ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, 0); throw new AbortException("STOWRS abort: " + e.getMessage(), e); } finally { files = cleanOrGetBulkDataFiles(in, copy == null); } return files; } public static void transferOther(ForwardDicomNode fwdNode, WebForwardDestination destination, Attributes copy, Params p) { try { UploadSingleFile stow = destination.getStowrsSingleFile(); String tsuid = p.getOutputTsuid(); if (destination.getAttributesEditor() == null) { stow.uploadDicom(copy, tsuid); } else { AttributeEditorContext context = new AttributeEditorContext(tsuid, fwdNode, null); Attributes attributes = new Attributes(copy); destination.getAttributesEditor().apply(attributes, context); if (context.getAbort() == Abort.FILE_EXCEPTION) { throw new IllegalStateException(context.getAbortMessage()); } else if (context.getAbort() == Abort.CONNECTION_EXCEPTION) { throw new AbortException("DICOM associtation abort. " + context.getAbortMessage()); } stow.uploadDicom(attributes, tsuid); ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.Success, ProgressStatus.COMPLETED, 0); } } catch (AbortException e) { ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, 0); throw e; } catch (Exception e) { LOGGER.error(ERROR_WHEN_FORWARDING, e); ServiceUtil.notifyProgession(destination.getState(), p.getIuid(), p.getCuid(), Status.ProcessingFailure, ProgressStatus.FAILED, 0); throw new AbortException("STOWRS abort: " + e.getMessage(), e); } } public static String selectTransferSyntax(Association as, String cuid, String filets) { Set<String> tss = as.getTransferSyntaxesFor(cuid); if (tss.contains(filets)) { return filets; } if (tss.contains(UID.ExplicitVRLittleEndian)) { return UID.ExplicitVRLittleEndian; } return UID.ImplicitVRLittleEndian; } }