001/* ***** BEGIN LICENSE BLOCK *****
002 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
003 *
004 * The contents of this file are subject to the Mozilla Public License Version
005 * 1.1 (the "License"); you may not use this file except in compliance with
006 * the License. You may obtain a copy of the License at
007 * http://www.mozilla.org/MPL/
008 *
009 * Software distributed under the License is distributed on an "AS IS" basis,
010 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
011 * for the specific language governing rights and limitations under the
012 * License.
013 *
014 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
015 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
016 *
017 * The Initial Developer of the Original Code is
018 * Agfa Healthcare.
019 * Portions created by the Initial Developer are Copyright (C) 2011
020 * the Initial Developer. All Rights Reserved.
021 *
022 * Contributor(s):
023 * See @authors listed below
024 *
025 * Alternatively, the contents of this file may be used under the terms of
026 * either the GNU General Public License Version 2 or later (the "GPL"), or
027 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
028 * in which case the provisions of the GPL or the LGPL are applicable instead
029 * of those above. If you wish to allow use of your version of this file only
030 * under the terms of either the GPL or the LGPL, and not to allow others to
031 * use your version of this file under the terms of the MPL, indicate your
032 * decision by deleting the provisions above and replace them with the notice
033 * and other provisions required by the GPL or the LGPL. If you do not delete
034 * the provisions above, a recipient may use your version of this file under
035 * the terms of any one of the MPL, the GPL or the LGPL.
036 *
037 * ***** END LICENSE BLOCK ***** */
038
039package org.dcm4che3.tool.stgcmtscu.test;
040
041import java.io.File;
042import java.io.FileNotFoundException;
043import java.io.IOException;
044import java.security.GeneralSecurityException;
045import java.util.Arrays;
046import java.util.Iterator;
047import java.util.concurrent.ExecutorService;
048import java.util.concurrent.Executors;
049import java.util.concurrent.ScheduledExecutorService;
050
051import org.dcm4che3.data.Attributes;
052import org.dcm4che3.data.Tag;
053import org.dcm4che3.data.UID;
054import org.dcm4che3.io.DicomOutputStream;
055import org.dcm4che3.net.ApplicationEntity;
056import org.dcm4che3.net.Association;
057import org.dcm4che3.net.AssociationStateException;
058import org.dcm4che3.net.Commands;
059import org.dcm4che3.net.Connection;
060import org.dcm4che3.net.Device;
061import org.dcm4che3.net.Dimse;
062import org.dcm4che3.net.IncompatibleConnectionException;
063import org.dcm4che3.net.Status;
064import org.dcm4che3.net.pdu.PresentationContext;
065import org.dcm4che3.net.service.AbstractDicomService;
066import org.dcm4che3.net.service.DicomService;
067import org.dcm4che3.net.service.DicomServiceException;
068import org.dcm4che3.tool.common.DicomFiles;
069import org.dcm4che3.tool.common.test.TestResult;
070import org.dcm4che3.tool.common.test.TestTool;
071import org.dcm4che3.tool.stgcmtscu.StgCmtSCU;
072import org.dcm4che3.util.SafeClose;
073import org.junit.Assert;
074
075/**
076 * @author Hesham Elbadawi <bsdreko@gmail.com>
077 */
078public class StgCmtTool implements TestTool {
079
080    private final String host;
081
082    private final int port;
083
084    private final File baseDirectory;
085
086    private final File storageDirectory;
087
088    String aeTitle;
089
090    Device device;
091
092    StgCmtSCU stgCmtSCU ;
093
094    private final String sourceAETitle;
095
096    private Attributes nEventReqData = new Attributes();
097
098    private int success;
099
100    private int fails;
101
102    private TestResult result;
103
104    Connection bound;
105
106
107    public StgCmtTool(String host, int port, String aeTitle,
108            File baseDir,File storageDirectory, Device device,
109            String sourceAETitle, Connection conn) {
110        this.host = host;
111        this.port = port;
112        this.aeTitle = aeTitle;
113        this.baseDirectory = baseDir;
114        this.device = device;
115        this.sourceAETitle = sourceAETitle;
116        this.storageDirectory = storageDirectory;
117        this.bound = conn;
118    }
119
120    private final DicomService stgcmtResultHandler =
121            new AbstractDicomService(UID.StorageCommitmentPushModelSOPClass) {
122
123                @Override
124                public void onDimseRQ(Association as, PresentationContext pc,
125                                      Dimse dimse, Attributes cmd, Attributes data)
126                        throws IOException {
127                    if (dimse != Dimse.N_EVENT_REPORT_RQ)
128                        throw new DicomServiceException(Status.UnrecognizedOperation);
129
130                    int eventTypeID = cmd.getInt(Tag.EventTypeID, 0);
131                    if (eventTypeID != 1 && eventTypeID != 2)
132                        throw new DicomServiceException(Status.NoSuchEventType)
133                                .setEventTypeID(eventTypeID);
134                    String tuid = data.getString(Tag.TransactionUID);
135                    try {
136                        Attributes rsp = Commands.mkNEventReportRSP(cmd, 0);
137                        Attributes rspAttrs =  writeResponse(as, cmd, data);
138                        nEventReqData = rspAttrs;
139                        as.writeDimseRSP(pc, rsp, null);
140
141                    } catch (AssociationStateException e) {
142                        System.out.println(as.toString() + " << N-EVENT-RECORD-RSP failed: " + e.getMessage());
143                    } finally {
144                        stgCmtSCU.removeOutstandingResult(tuid);
145                    }
146                }
147            };
148
149    private Attributes writeResponse(Association as, Attributes cmd, Attributes data) throws DicomServiceException {
150        setFailsandSuccess(data);
151        if(storageDirectory == null) {
152            return data;
153        }
154        else {
155            String cuid = cmd.getString(Tag.AffectedSOPClassUID);
156            String iuid = cmd.getString(Tag.AffectedSOPInstanceUID);
157            String tuid = data.getString(Tag.TransactionUID);
158            File file = new File(storageDirectory, tuid);
159            DicomOutputStream out = null;
160//            System.out.println(as + "{}: M-WRITE {}" + file);
161            try {
162                out = new DicomOutputStream(file);
163                out.writeDataset(
164                        Attributes.createFileMetaInformation(iuid, cuid,
165                                UID.ExplicitVRLittleEndian),
166                        data);
167            } catch (IOException e) {
168                //System.out.println(as + ": Failed to store Storage Commitment Result:" + e);
169                if(!(e instanceof FileNotFoundException))
170                throw new DicomServiceException(Status.ProcessingFailure, e);
171            } finally {
172                SafeClose.close(out);
173            }
174        }
175        return data;
176    }
177
178    private void setFailsandSuccess(Attributes data) {
179        success = data.getSequence(Tag.ReferencedSOPSequence).size();
180        if(data.contains(Tag.FailedSOPSequence))
181        fails = data.getSequence(Tag.FailedSOPSequence).size();
182    }
183
184    public void stgcmt(String description, String fileName) throws InterruptedException, IOException, GeneralSecurityException, IncompatibleConnectionException {
185
186        long t1, t2;
187
188        File file = new File(baseDirectory, fileName);
189
190        Assert.assertTrue(
191                "file or directory does not exists: " + file.getAbsolutePath(),
192                file.exists());
193
194        ApplicationEntity ae = new ApplicationEntity(sourceAETitle);
195        device.addApplicationEntity(ae);
196        ae.addConnection(bound);
197
198        for(Iterator<Connection> iterator=device.getConnections().iterator(); iterator.hasNext();) {
199            Connection next = iterator.next();
200            if(!next.getCommonName().equalsIgnoreCase(bound.getCommonName()))
201                iterator.remove();
202        }
203        
204        this.stgCmtSCU = new StgCmtSCU(ae, stgcmtResultHandler);
205
206        // configure
207        bound.setMaxOpsInvoked(0);
208        bound.setMaxOpsPerformed(0);
209
210        stgCmtSCU.getAAssociateRQ().setCalledAET(aeTitle);
211        stgCmtSCU.getRemoteConnection().setHostname(host);
212        stgCmtSCU.getRemoteConnection().setPort(port);
213        //ensure secure connection
214        stgCmtSCU.getRemoteConnection().setTlsCipherSuites(bound.getTlsCipherSuites());
215        stgCmtSCU.getRemoteConnection().setTlsProtocols(bound.tlsProtocols());
216        stgCmtSCU.setTransferSyntaxes(new String[]{UID.ImplicitVRLittleEndian, UID.ExplicitVRLittleEndian, UID.ExplicitVRBigEndianRetired});
217        stgCmtSCU.setAttributes(new Attributes());
218        stgCmtSCU.setStorageDirectory(storageDirectory);
219
220        // scan
221        t1 = System.currentTimeMillis();
222        DicomFiles.scan(Arrays.asList(file.getAbsolutePath()), new DicomFiles.Callback() {
223
224            @Override
225            public boolean dicomFile(File f, Attributes fmi, long dsPos,
226                                     Attributes ds) {
227                return stgCmtSCU.addInstance(ds);
228            }
229        });
230        t2 = System.currentTimeMillis();
231
232        // create executor
233        ExecutorService executorService =
234                Executors.newCachedThreadPool();
235        ScheduledExecutorService scheduledExecutorService =
236                Executors.newSingleThreadScheduledExecutor();
237        device.setExecutor(executorService);
238        device.setScheduledExecutor(scheduledExecutorService);
239        device.bindConnections();
240
241        // open, send and wait for response
242        try {
243            stgCmtSCU.open();
244            stgCmtSCU.sendRequests();
245        } finally {
246            stgCmtSCU.close();
247            if (bound.isListening()) {
248                device.waitForNoOpenConnections();
249                device.unbindConnections();
250            }
251            executorService.shutdown();
252            scheduledExecutorService.shutdown();
253        }
254        init(new StgCmtResult(description,t2-t1,success, fails, nEventReqData));
255    }
256    @Override
257    public void init(TestResult result) {
258        this.result = result;
259    }
260
261    @Override
262    public TestResult getResult() {
263        return this.result;
264    }
265
266    public File getStorageDirectory() {
267        return storageDirectory;
268    }
269
270}