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.storescp.test;
040
041import java.io.File;
042import java.io.IOException;
043import java.nio.file.Path;
044import java.security.GeneralSecurityException;
045import java.util.ArrayList;
046import java.util.Iterator;
047import java.util.List;
048import java.util.concurrent.ExecutorService;
049import java.util.concurrent.Executors;
050import java.util.concurrent.ScheduledExecutorService;
051
052import org.dcm4che3.data.Attributes;
053import org.dcm4che3.data.Tag;
054import org.dcm4che3.data.VR;
055import org.dcm4che3.io.DicomOutputStream;
056import org.dcm4che3.net.ApplicationEntity;
057import org.dcm4che3.net.Association;
058import org.dcm4che3.net.Connection;
059import org.dcm4che3.net.Device;
060import org.dcm4che3.net.PDVInputStream;
061import org.dcm4che3.net.Status;
062import org.dcm4che3.net.TransferCapability;
063import org.dcm4che3.net.pdu.PresentationContext;
064import org.dcm4che3.net.service.BasicCEchoSCP;
065import org.dcm4che3.net.service.BasicCStoreSCP;
066import org.dcm4che3.net.service.DicomServiceException;
067import org.dcm4che3.net.service.DicomServiceRegistry;
068import org.dcm4che3.tool.common.test.TestResult;
069import org.dcm4che3.tool.common.test.TestTool;
070import org.dcm4che3.tool.storescp.StoreSCP;
071import org.dcm4che3.util.SafeClose;
072import org.slf4j.Logger;
073import org.slf4j.LoggerFactory;
074
075/**
076 * @author Hesham Elbadawi <bsdreko@gmail.com>
077 */
078public class StoreSCPTool implements TestTool {
079
080    private final File storageDirectory;
081
082    String aeTitle;
083
084    Device device;
085
086    StoreSCP storeSCP ;
087
088    private final String sourceAETitle;
089
090    private TestResult result;
091
092    Connection bound;
093
094    long totalTime;
095
096    int status;
097
098    long t1=-1,t2=-1;
099
100    private boolean first=true;
101
102    private final boolean noStore;
103
104    private int fileReceived=0;
105
106    private final List<Attributes> rqCMDs = new ArrayList<Attributes>();
107
108    private final List<String> instanceLocations = new ArrayList<String>();
109
110    private final List<File> instanceFiles = new ArrayList<File>();
111
112    private static final Logger LOG = LoggerFactory.getLogger(StoreSCPTool.class);
113
114    private String testDescription;
115
116    private List<String> sopIUIDs;
117
118    private boolean started = false;
119
120    private final BasicCStoreSCP cstoreSCP = new BasicCStoreSCP("*") {
121
122        @Override
123        protected void store(Association as, PresentationContext pc,
124                Attributes rq, PDVInputStream data, Attributes rsp)
125                throws IOException {
126            if(first)
127                t1=System.currentTimeMillis();
128                first = false;
129            rsp.setInt(Tag.Status, VR.US, status);
130            if (noStore) {
131                fileReceived++;
132                return;
133            }
134            String cuid = rq.getString(Tag.AffectedSOPClassUID);
135            String iuid = rq.getString(Tag.AffectedSOPInstanceUID);
136            addSopUID(iuid);
137            String tsuid = pc.getTransferSyntax();
138            File file = new File(storageDirectory, iuid);
139            try {
140                LOG.info("{}: M-WRITE {}", as, file);
141                file.getParentFile().mkdirs();
142                DicomOutputStream out = new DicomOutputStream(file);
143                try {
144                    out.writeFileMetaInformation(as.createFileMetaInformation(iuid, cuid, tsuid));
145                    data.copyTo(out);
146                } finally {
147                    SafeClose.close(out);
148                    fileReceived++;
149                    rqCMDs.add(rq);
150                    instanceFiles.add(file);
151                    instanceLocations.add(file.getAbsolutePath());
152                }
153                
154            } catch (Exception e) {
155                throw new DicomServiceException(Status.ProcessingFailure, e);
156            }
157        }
158
159    };
160
161    public StoreSCPTool( File storageDirectory, Device device,
162            String sourceAETitle, Connection conn, boolean noStore) {
163        this.device = device;
164        this.sourceAETitle = sourceAETitle;
165        this.storageDirectory = storageDirectory;
166        this.bound = conn;
167        this.noStore = noStore;
168    }
169
170    public void start(String testDescriptionIn) throws InterruptedException {
171        this.start(testDescriptionIn, "*");
172    }
173
174
175    public void start(String testDescriptionIn, String... transferSyntaxes) throws InterruptedException {
176
177        started = true;
178
179        this.testDescription = testDescriptionIn;
180        ApplicationEntity ae = new ApplicationEntity(sourceAETitle);
181        device.setDimseRQHandler(createServiceRegistry());
182        device.addApplicationEntity(ae);
183        ae.addConnection(bound);
184
185        for(Iterator<Connection> iterator=device.getConnections().iterator(); iterator.hasNext();) {
186            Connection next = iterator.next();
187            if(!next.getCommonName().equalsIgnoreCase(bound.getCommonName()))
188                iterator.remove();
189        }
190        ae.setAssociationAcceptor(true);
191        //accept all
192        ae.addTransferCapability(
193                new TransferCapability(null, 
194                        "*",
195                        TransferCapability.Role.SCP,
196                        transferSyntaxes));
197        ExecutorService executorService = Executors.newCachedThreadPool();
198        ScheduledExecutorService scheduledExecutorService = 
199                Executors.newSingleThreadScheduledExecutor();
200        device.setScheduledExecutor(scheduledExecutorService);
201        device.setExecutor(executorService);
202        try {
203            device.bindConnections();
204        } catch (IOException e) {
205            LOG.error("Error binding connection for storescp , {}", e);
206        } catch (GeneralSecurityException e) {
207            LOG.error("Error binding connection for storescp , {}", e);
208        }
209    }
210
211    public void stop() {
212
213        if (!started)
214            return;
215        started = false;
216
217        t2 = System.currentTimeMillis();
218        device.unbindConnections();
219        ((ExecutorService) device.getExecutor()).shutdown();
220        device.getScheduledExecutor().shutdown();
221
222        //very quick fix to block for listening connection
223        while (device.getConnections().get(0).isListening())
224        {
225            try {
226                Thread.sleep(10);
227            } catch (InterruptedException e) {
228                // ignore
229            }
230        }
231
232        init(new StoreSCPResult(this.testDescription, t2 - t1, getfilesReceived(), getCmdRQList(), this.sopIUIDs, this.instanceLocations));
233    }
234    private List<Attributes> getCmdRQList() {
235        return rqCMDs;
236    }
237
238    private int getfilesReceived() {
239        return fileReceived;
240    }
241
242    private DicomServiceRegistry createServiceRegistry() {
243        DicomServiceRegistry serviceRegistry = new DicomServiceRegistry();
244        serviceRegistry.addDicomService(new BasicCEchoSCP());
245        serviceRegistry.addDicomService(cstoreSCP);
246        return serviceRegistry;
247    }
248
249    @Override
250    public void init(TestResult resultIn) {
251        this.result = resultIn;
252    }
253
254    @Override
255    public TestResult getResult() {
256        return this.result;
257    }
258
259    private void addSopUID(String uid) {
260        if(this.sopIUIDs == null)
261            this.sopIUIDs = new ArrayList<String>();
262        this.sopIUIDs.add(uid);
263    }
264
265    public List<String> getInstanceLocations() {
266        return instanceLocations;
267    }
268
269    public List<File> getInstanceFiles() {
270        return instanceFiles;
271    }
272    public Path getStorageDirectory() {
273        return storageDirectory.toPath();
274    }
275}