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