package org.weasis.dicom.send;
/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutorService;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.dcm4che3.data.Tag;
import org.dcm4che3.net.Status;
import org.dcm4che3.util.UIDUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.explorer.ObservableEvent;
import org.weasis.core.api.gui.task.CircularProgressBar;
import org.weasis.core.api.gui.util.AbstractItemDialogPage;
import org.weasis.core.api.gui.util.AppProperties;
import org.weasis.core.api.gui.util.GuiExecutor;
import org.weasis.core.api.gui.util.JMVUtils;
import org.weasis.core.api.media.data.MediaElement;
import org.weasis.core.api.media.data.MediaSeries;
import org.weasis.core.api.media.data.Series;
import org.weasis.core.api.media.data.TagW;
import org.weasis.core.api.service.BundleTools;
import org.weasis.core.api.util.FileUtil;
import org.weasis.core.api.util.StringUtil;
import org.weasis.core.api.util.ThreadUtil;
import org.weasis.core.ui.model.GraphicModel;
import org.weasis.dicom.codec.DicomImageElement;
import org.weasis.dicom.codec.TagD;
import org.weasis.dicom.explorer.CheckTreeModel;
import org.weasis.dicom.explorer.DicomModel;
import org.weasis.dicom.explorer.ExplorerTask;
import org.weasis.dicom.explorer.ExportDicom;
import org.weasis.dicom.explorer.ExportTree;
import org.weasis.dicom.explorer.LocalExport;
import org.weasis.dicom.explorer.pref.node.AbstractDicomNode;
import org.weasis.dicom.explorer.pref.node.AbstractDicomNode.UsageType;
import org.weasis.dicom.explorer.pref.node.DefaultDicomNode;
import org.weasis.dicom.explorer.pref.node.DicomWebNode;
import org.weasis.dicom.op.CStore;
import org.weasis.dicom.param.AdvancedParams;
import org.weasis.dicom.param.ConnectOptions;
import org.weasis.dicom.param.DicomNode;
import org.weasis.dicom.param.DicomProgress;
import org.weasis.dicom.param.DicomState;
public class SendDicomView extends AbstractItemDialogPage implements ExportDicom {
private static final Logger LOGGER = LoggerFactory.getLogger(SendDicomView.class);
private static final String LAST_SEL_NODE = "lastSelNode"; //$NON-NLS-1$
private static final String STOW_BOUNDARY = "mimeTypeBoundary"; //$NON-NLS-1$
private static final String STOW_SEG = "--"; //$NON-NLS-1$
private static final String RETURN = "\r\n"; //$NON-NLS-1$
private final DicomModel dicomModel;
private final ExportTree exportTree;
private final ExecutorService executor = ThreadUtil.buildNewFixedThreadExecutor(3, "Dicom Send task"); //$NON-NLS-1$
private final JPanel panel = new JPanel();
private final JComboBox<AbstractDicomNode> comboNode = new JComboBox<>();
public SendDicomView(DicomModel dicomModel, CheckTreeModel treeModel) {
super(Messages.getString("SendDicomView.title")); //$NON-NLS-1$
this.dicomModel = dicomModel;
this.exportTree = new ExportTree(treeModel);
initGUI();
initialize(true);
}
public void initGUI() {
setLayout(new BorderLayout());
FlowLayout flowLayout = (FlowLayout) panel.getLayout();
flowLayout.setAlignment(FlowLayout.LEFT);
final JLabel lblDest = new JLabel(Messages.getString("SendDicomView.destination") + StringUtil.COLON); //$NON-NLS-1$
panel.add(lblDest);
AbstractDicomNode.addTooltipToComboList(comboNode);
panel.add(comboNode);
add(panel, BorderLayout.NORTH);
add(exportTree, BorderLayout.CENTER);
}
protected void initialize(boolean afirst) {
if (afirst) {
AbstractDicomNode.loadDicomNodes(comboNode, AbstractDicomNode.Type.DICOM, UsageType.STORAGE);
AbstractDicomNode.loadDicomNodes(comboNode, AbstractDicomNode.Type.WEB, UsageType.STORAGE);
String desc = SendDicomFactory.EXPORT_PERSISTENCE.getProperty(LAST_SEL_NODE);
if (StringUtil.hasText(desc)) {
ComboBoxModel<AbstractDicomNode> model = comboNode.getModel();
for (int i = 0; i < model.getSize(); i++) {
if (desc.equals(model.getElementAt(i).getDescription())) {
model.setSelectedItem(model.getElementAt(i));
break;
}
}
}
}
}
public void resetSettingsToDefault() {
initialize(false);
}
public void applyChange() {
final AbstractDicomNode node = (AbstractDicomNode) comboNode.getSelectedItem();
if (node != null) {
SendDicomFactory.EXPORT_PERSISTENCE.setProperty(LAST_SEL_NODE, node.getDescription());
}
}
protected void updateChanges() {
}
@Override
public void closeAdditionalWindow() {
applyChange();
executor.shutdown();
}
@Override
public void resetoDefaultValues() {
}
@Override
public void exportDICOM(final CheckTreeModel model, JProgressBar info) throws IOException {
ExplorerTask<Boolean, String> task = new ExplorerTask<Boolean, String>(getTitle(), false) {
@Override
protected Boolean doInBackground() throws Exception {
return sendDicomFiles(model, this);
}
@Override
protected void done() {
dicomModel.firePropertyChange(
new ObservableEvent(ObservableEvent.BasicAction.LOADING_STOP, dicomModel, null, this));
}
};
executor.execute(task);
}
private boolean sendDicomFiles(final CheckTreeModel model, final ExplorerTask<Boolean, String> t) throws IOException {
dicomModel
.firePropertyChange(new ObservableEvent(ObservableEvent.BasicAction.LOADING_START, dicomModel, null, t));
File exportDir = FileUtil.createTempDir(AppProperties.buildAccessibleTempDirectory("tmp", "send")); //$NON-NLS-1$ //$NON-NLS-2$
try {
writeDicom(t, exportDir, model);
if (t.isCancelled()) {
return false;
}
String weasisAet = BundleTools.SYSTEM_PREFERENCES.getProperty("weasis.aet", "WEASIS_AE"); //$NON-NLS-1$ //$NON-NLS-2$
List<String> files = new ArrayList<>();
files.add(exportDir.getAbsolutePath());
final CircularProgressBar progressBar = t.getBar();
DicomProgress dicomProgress = new DicomProgress();
dicomProgress.addProgressListener(p -> {
GuiExecutor.instance().execute(() -> {
int c = p.getNumberOfCompletedSuboperations() + p.getNumberOfFailedSuboperations();
int r = p.getNumberOfRemainingSuboperations();
progressBar.setValue((c * 100) / (c + r));
});
});
t.addCancelListener(dicomProgress);
Object selectedItem = comboNode.getSelectedItem();
if (selectedItem instanceof DefaultDicomNode) {
final DefaultDicomNode node = (DefaultDicomNode) selectedItem;
AdvancedParams params = new AdvancedParams();
ConnectOptions connectOptions = new ConnectOptions();
connectOptions.setConnectTimeout(3000);
connectOptions.setAcceptTimeout(5000);
params.setConnectOptions(connectOptions);
final DicomState state =
CStore.process(params, new DicomNode(weasisAet), node.getDicomNode(), files, dicomProgress);
if (state.getStatus() != Status.Success && state.getStatus() != Status.Cancel) {
LOGGER.error("Dicom send error: {}", state.getMessage()); //$NON-NLS-1$
GuiExecutor.instance().execute(() -> JOptionPane.showMessageDialog(exportTree, state.getMessage(),
getTitle(), JOptionPane.ERROR_MESSAGE));
}
} else if (selectedItem instanceof DicomWebNode) {
postDicom((DicomWebNode) selectedItem, files);
}
} finally {
FileUtil.recursiveDelete(exportDir);
}
return true;
}
private void writeDicom(ExplorerTask<Boolean, String> task, File writeDir, CheckTreeModel model) throws IOException {
synchronized (this) {
ArrayList<String> uids = new ArrayList<>();
TreePath[] paths = model.getCheckingPaths();
for (TreePath treePath : paths) {
if (task.isCancelled()) {
return;
}
DefaultMutableTreeNode node = (DefaultMutableTreeNode) treePath.getLastPathComponent();
if (node.getUserObject() instanceof DicomImageElement) {
DicomImageElement img = (DicomImageElement) node.getUserObject();
String iuid = TagD.getTagValue(img, Tag.SOPInstanceUID, String.class);
int index = uids.indexOf(iuid);
if (index == -1) {
uids.add(iuid);
} else {
// Write only once the file for multiframes
continue;
}
String path = LocalExport.buildPath(img, false, false, false, node);
File destinationDir = new File(writeDir, path);
destinationDir.mkdirs();
File destinationFile = new File(destinationDir, iuid);
if (!img.saveToFile(destinationFile)) {
LOGGER.error("Cannot export DICOM file: {}", img.getFile()); //$NON-NLS-1$
}
} else if (node.getUserObject() instanceof MediaElement) {
MediaElement dcm = (MediaElement) node.getUserObject();
String iuid = TagD.getTagValue(dcm, Tag.SOPInstanceUID, String.class);
String path = LocalExport.buildPath(dcm, false, false, false, node);
File destinationDir = new File(writeDir, path);
destinationDir.mkdirs();
dcm.saveToFile(new File(destinationDir, iuid));
} else if (node.getUserObject() instanceof Series) {
MediaSeries<?> s = (MediaSeries<?>) node.getUserObject();
if (JMVUtils.getNULLtoFalse(s.getTagValue(TagW.ObjectToSave))) {
Series<?> series = (Series<?>) s.getTagValue(CheckTreeModel.SourceSeriesForPR);
if (series != null) {
String seriesInstanceUID = UIDUtils.createUID();
for (MediaElement dcm : series.getMedias(null, null)) {
GraphicModel grModel = (GraphicModel) dcm.getTagValue(TagW.PresentationModel);
if (grModel != null && grModel.hasSerializableGraphics()) {
String path = LocalExport.buildPath(dcm, false, false, false, node);
LocalExport.buildAndWritePR(dcm, false, new File(writeDir, path), null, node,
seriesInstanceUID);
}
}
}
}
}
}
}
}
private static void postDicom(DicomWebNode destination, List<String> files) {
HttpURLConnection httpPost = null;
try {
httpPost = (HttpURLConnection) destination.getUrl().openConnection();
httpPost.setDoOutput(true);
httpPost.setDoInput(true);
httpPost.setRequestMethod("POST"); //$NON-NLS-1$
httpPost.setRequestProperty("Content-Type", //$NON-NLS-1$
"multipart/related; type=application/dicom; boundary=" + STOW_BOUNDARY); //$NON-NLS-1$
httpPost.setUseCaches(false);
DataOutputStream out = new DataOutputStream(httpPost.getOutputStream());
for (String entry : files) {
File file = new File(entry);
if (file.isDirectory()) {
List<File> fileList = new ArrayList<>();
FileUtil.getAllFilesInDirectory(file, fileList);
for (File f : fileList) {
postDicomStream(f, out);
}
} else {
postDicomStream(file, out);
}
}
// Final part segment
out.writeBytes(RETURN);
out.writeBytes(STOW_SEG);
out.writeBytes(STOW_BOUNDARY);
out.writeBytes(STOW_SEG);
out.flush();
out.close();
String response = httpPost.getResponseMessage();
LOGGER.info("STOWRS: server response: {}", response); //$NON-NLS-1$
} catch (Exception e) {
LOGGER.error("STOWRS: error when posting data", e); //$NON-NLS-1$
} finally {
Optional.ofNullable(httpPost).ifPresent(HttpURLConnection::disconnect);
}
}
private static void postDicomStream(File file, DataOutputStream out) throws IOException {
// Segment for a part
out.writeBytes(RETURN);
out.writeBytes(STOW_SEG);
out.writeBytes(STOW_BOUNDARY);
out.writeBytes(RETURN);
out.writeBytes("Content-Type: application/dicom\r\n\r\n"); //$NON-NLS-1$
// write dicom binary file
writeStream(new FileInputStream(file), out);
}
private static void writeStream(InputStream inputStream, OutputStream out) throws IOException {
try {
byte[] buf = new byte[FileUtil.FILE_BUFFER];
int offset;
while ((offset = inputStream.read(buf)) > 0) {
out.write(buf, 0, offset);
}
out.flush();
} finally {
FileUtil.safeClose(inputStream);
}
}
}