001/**
002 * Copyright (C) 2014  Universidade de Aveiro, DETI/IEETA, Bioinformatics Group - http://bioinformatics.ua.pt/
003 *
004 * This file is part of Dicoogle/dicoogle.
005 *
006 * Dicoogle/dicoogle is free software: you can redistribute it and/or modify
007 * it under the terms of the GNU General Public License as published by
008 * the Free Software Foundation, either version 3 of the License, or
009 * (at your option) any later version.
010 *
011 * Dicoogle/dicoogle is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014 * GNU General Public License for more details.
015 *
016 * You should have received a copy of the GNU General Public License
017 * along with Dicoogle.  If not, see <http://www.gnu.org/licenses/>.
018 */
019package pt.ua.dicoogle.server.queryretrieve;
020
021import java.io.*;
022import java.net.MalformedURLException;
023import java.net.URL;
024import java.security.GeneralSecurityException;
025import java.security.KeyStore;
026import java.util.ArrayList;
027import java.util.Collections;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034import java.util.concurrent.Executor;
035import java.util.zip.GZIPInputStream;
036
037
038import org.dcm4che2.data.BasicDicomObject;
039import org.dcm4che2.data.DicomElement;
040import org.dcm4che2.data.DicomObject;
041import org.dcm4che2.data.Tag;
042import org.dcm4che2.data.UID;
043import org.dcm4che2.data.UIDDictionary;
044import org.dcm4che2.data.VR;
045import org.dcm4che2.io.DicomInputStream;
046import org.dcm4che2.io.DicomOutputStream;
047import org.dcm4che2.io.StopTagInputHandler;
048import org.dcm4che2.io.TranscoderInputHandler;
049import org.dcm4che2.net.Association;
050import org.dcm4che2.net.CommandUtils;
051import org.dcm4che2.net.ConfigurationException;
052import org.dcm4che2.net.Device;
053import org.dcm4che2.net.DimseRSP;
054import org.dcm4che2.net.DimseRSPHandler;
055import org.dcm4che2.net.NetworkApplicationEntity;
056import org.dcm4che2.net.NetworkConnection;
057import org.dcm4che2.net.NewThreadExecutor;
058import org.dcm4che2.net.NoPresentationContextException;
059import org.dcm4che2.net.PDVOutputStream;
060import org.dcm4che2.net.TransferCapability;
061import org.dcm4che2.net.UserIdentity;
062import org.dcm4che2.net.service.StorageCommitmentService;
063import org.dcm4che2.util.CloseUtils;
064import org.dcm4che2.util.StringUtils;
065import org.dcm4che2.util.UIDUtils;
066import pt.ua.dicoogle.core.ServerSettings;
067
068/**
069 * @author gunter zeilinger(gunterze@gmail.com)
070 * @version $Revision: 10933 $ $Date: 2009-04-21 01:48:38 +0100 (Ter, 21 Abr 2009) $
071 * @since Oct 13, 2005
072 */
073public class DcmSnd extends StorageCommitmentService {
074
075    private static final int KB = 1024;
076
077    private static final int MB = KB * KB;
078
079    private static final int PEEK_LEN = 1024;
080
081    private static final String USAGE =
082        "dcmsnd [Options] <aet>[@<host>[:<port>]] <file>|<directory>...";
083
084    private static final String DESCRIPTION =
085        "\nLoad composite DICOM Object(s) from specified DICOM file(s) and send it "
086      + "to the specified remote Application Entity. If a directory is specified,"
087      + "DICOM Object in files under that directory and further sub-directories "
088      + "are sent. If <port> is not specified, DICOM default port 104 is assumed. "
089      + "If also no <host> is specified, localhost is assumed. Optionally, a "
090      + "Storage Commitment Request for successfully tranferred objects is sent "
091      + "to the remote Application Entity after the storage. The Storage Commitment "
092      + "result is accepted on the same association or - if a local port is "
093      + "specified by option -L - in a separate association initiated by the "
094      + "remote Application Entity\n"
095      + "OPTIONS:";
096
097    private static final String EXAMPLE =
098        "\nExample: dcmsnd -stgcmt -L DCMSND:11113 STORESCP@localhost:11112 image.dcm \n"
099      + "=> Start listening on local port 11113 for receiving Storage Commitment "
100      + "results, send DICOM object image.dcm to Application Entity STORESCP, "
101      + "listening on local port 11112, and request Storage Commitment in same association.";
102
103    private static String[] TLS1 = { "TLSv1" };
104
105    private static String[] SSL3 = { "SSLv3" };
106
107    private static String[] NO_TLS1 = { "SSLv3", "SSLv2Hello" };
108
109    private static String[] NO_SSL2 = { "TLSv1", "SSLv3" };
110
111    private static String[] NO_SSL3 = { "TLSv1", "SSLv2Hello" };
112
113    private static char[] SECRET = { 's', 'e', 'c', 'r', 'e', 't' };
114
115    private static final String[] ONLY_IVLE_TS = {
116        UID.ImplicitVRLittleEndian
117    };
118
119    private static final String[] IVLE_TS = {
120        UID.ImplicitVRLittleEndian,
121        UID.ExplicitVRLittleEndian,
122        UID.ExplicitVRBigEndian,
123    };
124
125    private static final String[] EVLE_TS = {
126        UID.ExplicitVRLittleEndian,
127        UID.ImplicitVRLittleEndian,
128        UID.ExplicitVRBigEndian,
129    };
130
131    private static final String[] EVBE_TS = {
132        UID.ExplicitVRBigEndian,
133        UID.ExplicitVRLittleEndian,
134        UID.ImplicitVRLittleEndian,
135    };
136
137    private static final int STG_CMT_ACTION_TYPE = 1;
138
139    /** TransferSyntax: DCM4CHE URI Referenced */
140    private static final String DCM4CHEE_URI_REFERENCED_TS_UID =
141            "1.2.40.0.13.1.1.2.4.94";
142
143    private Executor executor = new NewThreadExecutor("DCMSND");
144
145    private NetworkApplicationEntity remoteAE = new NetworkApplicationEntity();
146
147    private NetworkApplicationEntity remoteStgcmtAE;
148
149    private NetworkConnection remoteConn = new NetworkConnection();
150
151    private NetworkConnection remoteStgcmtConn = new NetworkConnection();
152
153    private Device device = new Device("DCMSND");
154
155    private NetworkApplicationEntity ae = new NetworkApplicationEntity();
156
157    private NetworkConnection conn = new NetworkConnection();
158
159    private Map<String, Set<String>> as2ts = new HashMap<String, Set<String>>();
160
161    private ArrayList<FileInfo> files = new ArrayList<FileInfo>();
162
163    private Association assoc;
164
165    private int priority = 0;
166
167    private int transcoderBufferSize = 1024;
168
169    private int filesSent = 0;
170
171    private long totalSize = 0L;
172
173    private boolean fileref = false;
174
175    private boolean stgcmt = false;
176
177    private long shutdownDelay = 1000L;
178
179    private DicomObject stgCmtResult;
180
181    private String keyStoreURL = "resource:tls/test_sys_1.p12";
182
183    private char[] keyStorePassword = SECRET;
184
185    private char[] keyPassword;
186
187    private String trustStoreURL = "resource:tls/mesa_certs.jks";
188
189    private char[] trustStorePassword = SECRET;
190    
191    private String MoveOriginatorMessageID = null;
192
193    private boolean gzip = ServerSettings.getInstance().isGzipStorage();
194    
195    public DcmSnd() {
196        remoteAE.setInstalled(true);
197        remoteAE.setAssociationAcceptor(true);
198        remoteAE.setNetworkConnection(new NetworkConnection[] { remoteConn });
199
200        device.setNetworkApplicationEntity(ae);
201        device.setNetworkConnection(conn);
202        ae.setNetworkConnection(conn);
203        ae.setAssociationInitiator(true);
204        ae.setAssociationAcceptor(true);
205        ae.register(this);
206        ae.setAETitle(ServerSettings.getInstance().getAE());
207    }
208
209    public final void setLocalHost(String hostname) {
210        conn.setHostname(hostname);
211    }
212
213    public final void setLocalPort(int port) {
214        conn.setPort(port);
215    }
216
217    public final void setRemoteHost(String hostname) {
218        remoteConn.setHostname(hostname);
219    }
220
221    public final void setRemotePort(int port) {
222        remoteConn.setPort(port);
223    }
224
225    public final void setRemoteStgcmtHost(String hostname) {
226        remoteStgcmtConn.setHostname(hostname);
227    }
228
229    public final void setRemoteStgcmtPort(int port) {
230        remoteStgcmtConn.setPort(port);
231    }
232
233    public final void setTlsProtocol(String[] tlsProtocol) {
234        conn.setTlsProtocol(tlsProtocol);
235    }
236
237    public final void setTlsWithoutEncyrption() {
238        conn.setTlsWithoutEncyrption();
239        remoteConn.setTlsWithoutEncyrption();
240        remoteStgcmtConn.setTlsWithoutEncyrption();
241    }
242
243    public final void setTls3DES_EDE_CBC() {
244        conn.setTls3DES_EDE_CBC();
245        remoteConn.setTls3DES_EDE_CBC();
246        remoteStgcmtConn.setTls3DES_EDE_CBC();
247    }
248
249    public final void setTlsAES_128_CBC() {
250        conn.setTlsAES_128_CBC();
251        remoteConn.setTlsAES_128_CBC();
252        remoteStgcmtConn.setTlsAES_128_CBC();
253    }
254
255    public final void setTlsNeedClientAuth(boolean needClientAuth) {
256        conn.setTlsNeedClientAuth(needClientAuth);
257    }
258
259    public final void setKeyStoreURL(String url) {
260        keyStoreURL = url;
261    }
262
263    public final void setKeyStorePassword(String pw) {
264        keyStorePassword = pw.toCharArray();
265    }
266
267    public final void setKeyPassword(String pw) {
268        keyPassword = pw.toCharArray();
269    }
270
271    public final void setTrustStorePassword(String pw) {
272        trustStorePassword = pw.toCharArray();
273    }
274
275    public final void setTrustStoreURL(String url) {
276        trustStoreURL = url;
277    }
278
279    public final void setCalledAET(String called) {
280        remoteAE.setAETitle(called);
281    }
282
283    public final void setCalling(String calling) {
284        ae.setAETitle(calling);
285    }
286
287    public final void setUserIdentity(UserIdentity userIdentity) {
288        ae.setUserIdentity(userIdentity);
289    }
290
291    public final void setOfferDefaultTransferSyntaxInSeparatePresentationContext(
292            boolean enable) {
293        ae.setOfferDefaultTransferSyntaxInSeparatePresentationContext(enable);
294    }
295
296    public final void setSendFileRef(boolean fileref) {
297        this.fileref = fileref;
298    }
299
300    public final void setStorageCommitment(boolean stgcmt) {
301        this.stgcmt = stgcmt;
302    }
303
304    public final boolean isStorageCommitment() {
305        return stgcmt;
306    }
307
308    public final void setStgcmtCalledAET(String called) {
309        remoteStgcmtAE = new NetworkApplicationEntity();
310        remoteStgcmtAE.setInstalled(true);
311        remoteStgcmtAE.setAssociationAcceptor(true);
312        remoteStgcmtAE.setNetworkConnection(
313                new NetworkConnection[] { remoteStgcmtConn });
314        remoteStgcmtAE.setAETitle(called);
315    }
316
317    public final void setShutdownDelay(int shutdownDelay) {
318        this.shutdownDelay = shutdownDelay;
319    }
320
321
322    public final void setConnectTimeout(int connectTimeout) {
323        conn.setConnectTimeout(connectTimeout);
324    }
325
326    public final void setMaxPDULengthReceive(int maxPDULength) {
327        ae.setMaxPDULengthReceive(maxPDULength);
328    }
329
330    public final void setMaxOpsInvoked(int maxOpsInvoked) {
331        ae.setMaxOpsInvoked(maxOpsInvoked);
332    }
333
334    public final void setPackPDV(boolean packPDV) {
335        ae.setPackPDV(packPDV);
336    }
337
338    public final void setAssociationReaperPeriod(int period) {
339        device.setAssociationReaperPeriod(period);
340    }
341
342    public final void setDimseRspTimeout(int timeout) {
343        ae.setDimseRspTimeout(timeout);
344    }
345
346    public final void setPriority(int priority) {
347        this.priority = priority;
348    }
349
350    public final void setTcpNoDelay(boolean tcpNoDelay) {
351        conn.setTcpNoDelay(tcpNoDelay);
352    }
353
354    public final void setAcceptTimeout(int timeout) {
355        conn.setAcceptTimeout(timeout);
356    }
357
358    public final void setReleaseTimeout(int timeout) {
359        conn.setReleaseTimeout(timeout);
360    }
361
362    public final void setSocketCloseDelay(int timeout) {
363        conn.setSocketCloseDelay(timeout);
364    }
365
366    public final void setMaxPDULengthSend(int maxPDULength) {
367        ae.setMaxPDULengthSend(maxPDULength);
368    }
369
370    public final void setReceiveBufferSize(int bufferSize) {
371        conn.setReceiveBufferSize(bufferSize);
372    }
373
374    public final void setSendBufferSize(int bufferSize) {
375        conn.setSendBufferSize(bufferSize);
376    }
377
378    public final void setTranscoderBufferSize(int transcoderBufferSize) {
379        this.transcoderBufferSize = transcoderBufferSize;
380    }
381
382    public final int getNumberOfFilesToSend() {
383        return files.size();
384    }
385
386    public final int getNumberOfFilesSent() {
387        return filesSent;
388    }
389
390    public final long getTotalSizeSent() {
391        return totalSize;
392    }
393
394    public List<FileInfo> getFileInfos() {
395        return files;
396    }
397
398
399    private static void promptStgCmt(DicomObject cmtrslt, float seconds) {
400        
401        DicomElement refSOPSq = cmtrslt.get(Tag.ReferencedSOPSequence);
402        System.out.print(refSOPSq.countItems());
403        System.out.println(" successful");
404        DicomElement failedSOPSq = cmtrslt.get(Tag.FailedSOPSequence);
405        if (failedSOPSq != null) {
406            System.out.print(failedSOPSq.countItems());
407            System.out.println(" FAILED!");
408        }
409    }
410
411    private synchronized DicomObject waitForStgCmtResult() throws InterruptedException {
412        while (stgCmtResult == null) wait();
413        return stgCmtResult;
414    }
415
416    private static void prompt(DcmSnd dcmsnd, float seconds) {
417        System.out.print("\nSent ");
418        System.out.print(dcmsnd.getNumberOfFilesSent());
419        System.out.print(" objects (=");
420        promptBytes(dcmsnd.getTotalSizeSent());
421        System.out.print(") in ");
422        System.out.print(seconds);
423        System.out.print("s (=");
424        promptBytes(dcmsnd.getTotalSizeSent() / seconds);
425        System.out.println("/s)");
426    }
427
428    private static void promptBytes(float totalSizeSent) {
429        if (totalSizeSent > MB) {
430            System.out.print(totalSizeSent / MB);
431            System.out.print("MB");
432        } else {
433            System.out.print(totalSizeSent / KB);
434            System.out.print("KB");
435        }
436    }
437
438    private static int toPort(String port) {
439        return port != null ? parseInt(port, "illegal port number", 1, 0xffff)
440                : 104;
441    }
442
443    private static String[] split(String s, char delim) {
444        String[] s2 = { s, null };
445        int pos = s.indexOf(delim);
446        if (pos != -1) {
447            s2[0] = s.substring(0, pos);
448            s2[1] = s.substring(pos + 1);
449        }
450        return s2;
451    }
452
453    private static void exit(String msg) {
454        System.err.println(msg);
455        System.err.println("Try 'dcmsnd -h' for more information.");
456        System.exit(1);
457    }
458
459    private static int parseInt(String s, String errPrompt, int min, int max) {
460        try {
461            int i = Integer.parseInt(s);
462            if (i >= min && i <= max)
463                return i;
464        } catch (NumberFormatException e) {
465            // parameter is not a valid integer; fall through to exit
466        }
467        exit(errPrompt);
468        throw new RuntimeException();
469    }
470
471    public void addFile(File f) {
472        if (f.isDirectory()) {
473            File[] fs = f.listFiles();
474            for (int i = 0; i < fs.length; i++)
475                addFile(fs[i]);
476            return;
477        }
478        FileInfo info = new FileInfo(f);
479        DicomObject dcmObj = new BasicDicomObject();
480        DicomInputStream in = null;
481        try {
482            if (f.getAbsolutePath().endsWith(".gz"))
483            {
484                in = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(f), 256)));
485            }
486            else
487            {
488                in = new DicomInputStream(f);
489            }
490            in.setHandler(new StopTagInputHandler(Tag.StudyDate));
491            in.readDicomObject(dcmObj, PEEK_LEN);
492            info.tsuid = in.getTransferSyntax().uid();
493            info.fmiEndPos = in.getEndOfFileMetaInfoPosition();
494        } catch (IOException e) {
495            e.printStackTrace();
496            System.err.println("WARNING: Failed to parse " + f + " - skipped.");
497            System.out.print('F');
498            return;
499        } finally {
500            CloseUtils.safeClose(in);
501        }
502        info.cuid = dcmObj.getString(Tag.SOPClassUID);
503        if (info.cuid == null) {
504            System.err.println("WARNING: Missing SOP Class UID in " + f
505                    + " - skipped.");
506            System.out.print('F');
507            return;
508        }
509        info.iuid = dcmObj.getString(Tag.SOPInstanceUID);
510        if (info.iuid == null) {
511            System.err.println("WARNING: Missing SOP Instance UID in " + f
512                    + " - skipped.");
513            System.out.print('F');
514            return;
515        }
516        addTransferCapability(info.cuid, info.tsuid);
517        files.add(info);
518        System.out.print('.');
519    }
520
521    public void addTransferCapability(String cuid, String tsuid) {
522        Set<String> ts = as2ts.get(cuid);
523        if (fileref) {
524            if (ts == null) {
525                as2ts.put(cuid,
526                        Collections.singleton(DCM4CHEE_URI_REFERENCED_TS_UID));
527            }
528        } else {
529            if (ts == null) {
530                ts = new HashSet<String>();
531                ts.add(UID.ImplicitVRLittleEndian);
532                as2ts.put(cuid, ts);
533            }
534            ts.add(tsuid);
535        }
536    }
537    
538
539
540    public void configureTransferCapability() {
541        int off = stgcmt || remoteStgcmtAE != null ? 1 : 0;
542        TransferCapability[] tc = new TransferCapability[off + as2ts.size()];
543        if (off > 0) {
544            tc[0] = new TransferCapability(
545                    UID.StorageCommitmentPushModelSOPClass,
546                    ONLY_IVLE_TS,
547                    TransferCapability.SCU);
548        }
549        Iterator<Map.Entry<String, Set<String>>> iter = as2ts.entrySet().iterator();
550        for (int i = off; i < tc.length; i++) {
551            Map.Entry<String, Set<String>> e = iter.next();
552            String cuid = e.getKey();
553            Set<String> ts = e.getValue();
554            tc[i] = new TransferCapability(cuid,
555                    ts.toArray(new String[ts.size()]),
556                    TransferCapability.SCU);
557        }
558        ae.setTransferCapability(tc);
559    }
560
561    public void start() throws IOException {
562        if (conn.isListening()) {
563            conn.bind(executor );
564            System.out.println("Start Server listening on port " + conn.getPort());
565        }
566    }
567
568    public void stop() {
569        if (conn.isListening()) {
570            try {
571                Thread.sleep(shutdownDelay);
572            } catch (InterruptedException e) {
573                // Should not happen
574                e.printStackTrace();
575            }
576            conn.unbind();
577        }
578    }
579
580    public void open() throws IOException, ConfigurationException,
581            InterruptedException {
582        assoc = ae.connect(remoteAE, executor);
583    }
584
585    public void openToStgcmtAE() throws IOException, ConfigurationException,
586            InterruptedException {
587        assoc = ae.connect(remoteStgcmtAE, executor);
588    }
589
590    public void send() {
591        for (int i = 0, n = files.size(); i < n; ++i) {
592            FileInfo info = files.get(i);
593            TransferCapability tc = assoc.getTransferCapabilityAsSCU(info.cuid);
594            if (tc == null) {
595                System.out.println();
596                System.out.println(UIDDictionary.getDictionary().prompt(
597                        info.cuid)
598                        + " not supported by " + remoteAE.getAETitle());
599                System.out.println("skip file " + info.f);
600                continue;
601            }
602            
603            
604            String tsuid = selectTransferSyntax(tc.getTransferSyntax(),
605                    fileref ? DCM4CHEE_URI_REFERENCED_TS_UID : info.tsuid);
606            if (tsuid == null) {
607                System.out.println();
608                System.out.println(UIDDictionary.getDictionary().prompt(
609                        info.cuid)
610                        + " with "
611                        + UIDDictionary.getDictionary().prompt(
612                                fileref ? DCM4CHEE_URI_REFERENCED_TS_UID
613                                        : info.tsuid)
614                        + " not supported by " + remoteAE.getAETitle());
615                System.out.println("skip file " + info.f);
616                continue;
617            }
618
619            try {
620                DimseRSPHandler rspHandler = new DimseRSPHandler() {
621                    @Override
622                    public void onDimseRSP(Association as, DicomObject cmd,
623                            DicomObject data) {
624                        DcmSnd.this.onDimseRSP(cmd);
625                    }
626                };
627                
628                if(MoveOriginatorMessageID!=null)
629                {
630                    int messageID = Integer.parseInt(MoveOriginatorMessageID);
631                    assoc.cstore(info.cuid, info.iuid, priority, assoc.getCallingAET(), messageID,
632                        new DataWriter(info), tsuid, rspHandler);
633                }
634                else
635                {
636                    assoc.cstore(info.cuid, info.iuid, priority, 
637                        new DataWriter(info), tsuid, rspHandler);
638                }
639                
640                
641                
642                //assoc.cstore(info.cuid, info.iuid, priority, assoc.getCallingAET(), priority, new DataWriter(info), tsuid, rspHandler);
643                
644            } catch (NoPresentationContextException e) {
645                System.err.println("WARNING: " + e.getMessage()
646                        + " - cannot send " + info.f);
647                System.out.print('F');
648            } catch (IOException e) {
649                e.printStackTrace();
650                System.err.println("ERROR: Failed to send - " + info.f + ": "
651                        + e.getMessage());
652                System.out.print('F');
653            } catch (InterruptedException e) {
654                // should not happen
655                e.printStackTrace();
656            }
657        }
658        try {
659            assoc.waitForDimseRSP();
660        } catch (InterruptedException e) {
661            // should not happen
662            e.printStackTrace();
663        }
664    }
665
666    public boolean commit() {
667        DicomObject actionInfo = new BasicDicomObject();
668        actionInfo.putString(Tag.TransactionUID, VR.UI, UIDUtils.createUID());
669        
670        DicomElement refSOPSq = actionInfo.putSequence(Tag.ReferencedSOPSequence);
671        for (int i = 0, n = files.size(); i < n; ++i) {
672            FileInfo info = files.get(i);
673            if (info.transferred) {
674                BasicDicomObject refSOP = new BasicDicomObject();
675                
676                refSOP.putString(Tag.ReferencedSOPClassUID, VR.UI, info.cuid);
677                refSOP.putString(Tag.ReferencedSOPInstanceUID, VR.UI, info.iuid);
678
679                refSOPSq.addDicomObject(refSOP);
680            }
681        }
682        try {
683            stgCmtResult = null;
684            DimseRSP rsp = assoc.naction(UID.StorageCommitmentPushModelSOPClass,
685                UID.StorageCommitmentPushModelSOPInstance, STG_CMT_ACTION_TYPE,
686                actionInfo, UID.ImplicitVRLittleEndian);
687            rsp.next();
688            DicomObject cmd = rsp.getCommand();
689            int status = cmd.getInt(Tag.Status);
690            if (status == 0) {
691                return true;
692            }
693            System.err.println(
694                    "WARNING: Storage Commitment request failed with status: "
695                    + StringUtils.shortToHex(status) + "H");
696            System.err.println(cmd.toString());
697        } catch (NoPresentationContextException e) {
698            System.err.println("WARNING: " + e.getMessage()
699                    + " - cannot request Storage Commitment");
700        } catch (IOException e) {
701            e.printStackTrace();
702            System.err.println(
703                    "ERROR: Failed to send Storage Commitment request: "
704                    + e.getMessage());
705        } catch (InterruptedException e) {
706            // should not happen
707            e.printStackTrace();
708        }
709        return false;
710    }
711
712    private String selectTransferSyntax(String[] available, String tsuid) {
713        if (tsuid.equals(UID.ImplicitVRLittleEndian))
714            return selectTransferSyntax(available, IVLE_TS);
715        if (tsuid.equals(UID.ExplicitVRLittleEndian))
716            return selectTransferSyntax(available, EVLE_TS);
717        if (tsuid.equals(UID.ExplicitVRBigEndian))
718            return selectTransferSyntax(available, EVBE_TS);
719        for (int j = 0; j < available.length; j++)
720            if (available[j].equals(tsuid))
721                return tsuid;
722        return null;
723    }
724
725    private String selectTransferSyntax(String[] available, String[] tsuids) {
726        for (int i = 0; i < tsuids.length; i++)
727            for (int j = 0; j < available.length; j++)
728                if (available[j].equals(tsuids[i]))
729                    return available[j];
730        return null;
731    }
732
733    public void close() {
734        try {
735            assoc.release(false);
736        } catch (InterruptedException e) {
737            e.printStackTrace();
738        }
739    }
740
741    /**
742     * @return the MoveOriginatorMessageID
743     */
744    public String getMoveOriginatorMessageID() {
745        return MoveOriginatorMessageID;
746    }
747
748    /**
749     * @param MoveOriginatorMessageID the MoveOriginatorMessageID to set
750     */
751    public void setMoveOriginatorMessageID(String MoveOriginatorMessageID) {
752        this.MoveOriginatorMessageID = MoveOriginatorMessageID;
753    }
754
755    public static final class FileInfo {
756        File f;
757
758        String cuid;
759
760        String iuid;
761
762        String tsuid;
763
764        long fmiEndPos;
765
766        long length;
767
768        boolean transferred;
769
770        int status;
771
772        public FileInfo(File f) {
773            this.f = f;
774            this.length = f.length();
775        }
776
777    }
778
779    
780    
781    private class DataWriter implements org.dcm4che2.net.DataWriter {
782
783        private FileInfo info;
784
785        public DataWriter(FileInfo info) {
786            this.info = info;
787        }
788
789        public void writeTo(PDVOutputStream out, String tsuid)
790                throws IOException {
791            if (tsuid.equals(info.tsuid)) {
792                InputStream fis = null;
793                if (info.f.getAbsolutePath().endsWith(".gz"))
794                    fis = new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256));
795                else
796                    fis = new FileInputStream(info.f);
797                
798                try {
799                    long skip = info.fmiEndPos;
800                    while (skip > 0)
801                        skip -= fis.skip(skip);
802                    out.copyFrom(fis);
803                } finally {
804                    fis.close();
805                }
806            } else if (tsuid.equals(DCM4CHEE_URI_REFERENCED_TS_UID)) {
807                DicomObject attrs;
808                DicomInputStream dis = null;
809                if (info.f.getAbsolutePath().endsWith(".gz"))
810                {
811                    dis = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256)));
812                }
813                else
814                {
815                    dis= new DicomInputStream(info.f);
816                }
817                
818                try {
819                    dis.setHandler(new StopTagInputHandler(Tag.PixelData));
820                    attrs = dis.readDicomObject();
821                } finally {
822                    dis.close();
823                }
824                DicomOutputStream dos = new DicomOutputStream(out);
825                attrs.putString(Tag.RetrieveURI, VR.UT, info.f.toURI().toString());
826                
827                dos.writeDataset(attrs, tsuid);
828             } else {
829                DicomInputStream dis = null;
830                if (info.f.getAbsolutePath().endsWith(".gz"))
831                {
832                     dis = new DicomInputStream(new GZIPInputStream(new BufferedInputStream(new FileInputStream(info.f), 256)));
833                }
834                else
835                {
836                    dis = new DicomInputStream(info.f);
837                }
838                try {
839                    DicomOutputStream dos = new DicomOutputStream(out);
840                    dos.setTransferSyntax(tsuid);
841                    TranscoderInputHandler h = new TranscoderInputHandler(dos,
842                            transcoderBufferSize);
843                    dis.setHandler(h);
844                    dis.readDicomObject();
845                } finally {
846                    dis.close();
847                }
848            }
849        }
850
851    }
852
853    private void promptErrRSP(String prefix, int status, FileInfo info,
854            DicomObject cmd) {
855        System.err.println(prefix + StringUtils.shortToHex(status) + "H for "
856                + info.f + ", cuid=" + info.cuid + ", tsuid=" + info.tsuid);
857        System.err.println(cmd.toString());
858    }
859
860    private void onDimseRSP(DicomObject cmd) {
861        int status = cmd.getInt(Tag.Status);
862        int msgId = cmd.getInt(Tag.MessageIDBeingRespondedTo);
863        FileInfo info = files.get(msgId - 1);
864        info.status = status;
865        switch (status) {
866        case 0:
867            info.transferred = true;
868            totalSize += info.length;
869            ++filesSent;
870            System.out.print('.');
871            break;
872        case 0xB000:
873        case 0xB006:
874        case 0xB007:
875            info.transferred = true;
876            totalSize += info.length;
877            ++filesSent;
878            promptErrRSP("WARNING: Received RSP with Status ", status, info,
879                    cmd);
880            System.out.print('W');
881            break;
882        default:
883            promptErrRSP("ERROR: Received RSP with Status ", status, info, cmd);
884            System.out.print('F');
885        }
886    }
887
888    @Override
889    protected synchronized void onNEventReportRSP(Association as, int pcid,
890            DicomObject rq, DicomObject info, DicomObject rsp) {
891        stgCmtResult = info;
892        notifyAll();
893    }
894
895    public void initTLS() throws GeneralSecurityException, IOException {
896        KeyStore keyStore = loadKeyStore(keyStoreURL, keyStorePassword);
897        KeyStore trustStore = loadKeyStore(trustStoreURL, trustStorePassword);
898        device.initTLS(keyStore,
899                keyPassword != null ? keyPassword : keyStorePassword,
900                trustStore);
901    }
902
903    private static KeyStore loadKeyStore(String url, char[] password)
904            throws GeneralSecurityException, IOException {
905        KeyStore key = KeyStore.getInstance(toKeyStoreType(url));
906        InputStream in = openFileOrURL(url);
907        try {
908            key.load(in, password);
909        } finally {
910            in.close();
911        }
912        return key;
913    }
914
915    private static InputStream openFileOrURL(String url) throws IOException {
916        if (url.startsWith("resource:")) {
917            return DcmSnd.class.getClassLoader().getResourceAsStream(
918                    url.substring(9));
919        }
920        try {
921            return new URL(url).openStream();
922        } catch (MalformedURLException e) {
923            return new FileInputStream(url);
924        }
925    }
926
927    private static String toKeyStoreType(String fname) {
928        return fname.endsWith(".p12") || fname.endsWith(".P12")
929                 ? "PKCS12" : "JKS";
930    }
931}