001//
002/* ***** BEGIN LICENSE BLOCK *****
003 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
004 *
005 * The contents of this file are subject to the Mozilla Public License Version
006 * 1.1 (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 * http://www.mozilla.org/MPL/
009 *
010 * Software distributed under the License is distributed on an "AS IS" basis,
011 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
012 * for the specific language governing rights and limitations under the
013 * License.
014 *
015 * The Original Code is part of dcm4che, an implementation of DICOM(TM) in
016 * Java(TM), hosted at https://github.com/gunterze/dcm4che.
017 *
018 * The Initial Developer of the Original Code is
019 * Agfa Healthcare.
020 * Portions created by the Initial Developer are Copyright (C) 2011
021 * the Initial Developer. All Rights Reserved.
022 *
023 * Contributor(s):
024 * See @authors listed below
025 *
026 * Alternatively, the contents of this file may be used under the terms of
027 * either the GNU General Public License Version 2 or later (the "GPL"), or
028 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
029 * in which case the provisions of the GPL or the LGPL are applicable instead
030 * of those above. If you wish to allow use of your version of this file only
031 * under the terms of either the GPL or the LGPL, and not to allow others to
032 * use your version of this file under the terms of the MPL, indicate your
033 * decision by deleting the provisions above and replace them with the notice
034 * and other provisions required by the GPL or the LGPL. If you do not delete
035 * the provisions above, a recipient may use your version of this file under
036 * the terms of any one of the MPL, the GPL or the LGPL.
037 *
038 * ***** END LICENSE BLOCK ***** */
039
040package org.dcm4che3.tool.dcmqrscp;
041
042import java.io.IOException;
043import java.util.HashMap;
044import java.util.LinkedHashMap;
045import java.util.Map;
046import java.util.Map.Entry;
047import java.util.concurrent.Executor;
048
049import org.dcm4che3.data.Attributes;
050import org.dcm4che3.data.Sequence;
051import org.dcm4che3.data.Tag;
052import org.dcm4che3.data.UID;
053import org.dcm4che3.data.VR;
054import org.dcm4che3.media.DicomDirReader;
055import org.dcm4che3.net.Association;
056import org.dcm4che3.net.AssociationStateException;
057import org.dcm4che3.net.Commands;
058import org.dcm4che3.net.Connection;
059import org.dcm4che3.net.Dimse;
060import org.dcm4che3.net.Status;
061import org.dcm4che3.net.pdu.PresentationContext;
062import org.dcm4che3.net.service.AbstractDicomService;
063import org.dcm4che3.net.service.DicomServiceException;
064
065public class StgCmtSCPImpl extends AbstractDicomService {
066    private final DicomDirReader dicomDirReader;
067    private final Map<String,Connection> remoteConnections;
068    private final boolean stgCmtOnSameAssoc;
069    private final Executor executor;
070    
071    public StgCmtSCPImpl(DicomDirReader dicomDirReader, Map<String,Connection> remoteConnections, 
072            boolean stgCmtOnSameAssoc, Executor executor) {
073        super(UID.StorageCommitmentPushModelSOPClass);
074        this.dicomDirReader = dicomDirReader;
075        this.remoteConnections = remoteConnections;
076        this.stgCmtOnSameAssoc = stgCmtOnSameAssoc;
077        this.executor = executor;
078    }
079
080    @Override
081    protected void onDimseRQ(Association as, PresentationContext pc,
082            Dimse dimse, Attributes rq, Attributes actionInfo)
083            throws IOException {
084        if (dimse != Dimse.N_ACTION_RQ)
085            throw new DicomServiceException(Status.UnrecognizedOperation);
086
087        int actionTypeID = rq.getInt(Tag.ActionTypeID, 0);
088        if (actionTypeID != 1)
089            throw new DicomServiceException(Status.NoSuchActionType)
090                    .setActionTypeID(actionTypeID);
091
092        Attributes rsp = Commands.mkNActionRSP(rq, Status.Success);
093        String callingAET = as.getCallingAET();
094        String calledAET = as.getCalledAET();
095        Connection remoteConnection = remoteConnections.get(callingAET);
096        if (remoteConnection == null)
097            throw new DicomServiceException(Status.ProcessingFailure, "Unknown Calling AET: " + callingAET);
098        Attributes eventInfo = calculateStorageCommitmentResult(calledAET, actionInfo);
099        try {
100            as.writeDimseRSP(pc, rsp, null);
101            executor.execute(new SendStgCmtResult(as, eventInfo, stgCmtOnSameAssoc, remoteConnection));
102        } catch (AssociationStateException e) {
103            DcmQRSCP.LOG.warn("{} << N-ACTION-RSP failed: {}", as, e.getMessage());
104        }
105    }
106    
107    private Attributes calculateStorageCommitmentResult(String calledAET,
108            Attributes actionInfo) throws DicomServiceException {
109        Sequence requestSeq = actionInfo.getSequence(Tag.ReferencedSOPSequence);
110        int size = requestSeq.size();
111        String[] sopIUIDs = new String[size];
112        Attributes eventInfo = new Attributes(6);
113        eventInfo.setString(Tag.RetrieveAETitle, VR.AE, calledAET);
114        eventInfo.setString(Tag.StorageMediaFileSetID, VR.SH,
115                dicomDirReader.getFileSetID());
116        eventInfo.setString(Tag.StorageMediaFileSetUID, VR.SH,
117                dicomDirReader.getFileSetUID());
118        eventInfo.setString(Tag.TransactionUID, VR.UI,
119                actionInfo.getString(Tag.TransactionUID));
120        Sequence successSeq = eventInfo.newSequence(Tag.ReferencedSOPSequence,
121                size);
122        Sequence failedSeq = eventInfo.newSequence(Tag.FailedSOPSequence, size);
123        LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(
124                size * 4 / 3);
125        for (int i = 0; i < sopIUIDs.length; i++) {
126            Attributes item = requestSeq.get(i);
127            map.put(sopIUIDs[i] = item.getString(Tag.ReferencedSOPInstanceUID),
128                    item.getString(Tag.ReferencedSOPClassUID));
129        }
130        
131        Map<String,Integer> instanceStatusMap = calculateMatches(map);
132        
133        for(Entry<String,Integer> entry : instanceStatusMap.entrySet()) {
134            String iuid = entry.getKey();
135            int status = entry.getValue();
136            if(status == Status.Success) {
137                successSeq.add(refSOP(iuid, map.get(iuid), status));
138            } else {
139                failedSeq.add(refSOP(iuid, map.get(iuid), status));
140            }
141        }
142        
143        if (failedSeq.isEmpty()) {
144            eventInfo.remove(Tag.FailedSOPSequence);
145        }
146        
147        return eventInfo;
148    }
149    
150    protected Map<String,Integer> calculateMatches(Map<String,String> requestMap) throws DicomServiceException {
151        Map<String, String> requestMapCopy = new HashMap<String, String>(requestMap);
152        Map<String, Integer> instanceStatusMap = new HashMap<String, Integer>();
153        
154        String[] sopIUIDs = requestMap.keySet().toArray(new String[requestMap.keySet().size()]);
155        
156        DicomDirReader ddr = dicomDirReader;
157        try {
158            Attributes patRec = ddr.findPatientRecord();
159            while (patRec != null) {
160                Attributes studyRec = ddr.findStudyRecord(patRec);
161                while (studyRec != null) {
162                    Attributes seriesRec = ddr.findSeriesRecord(studyRec);
163                    while (seriesRec != null) {
164                        Attributes instRec = ddr.findLowerInstanceRecord(
165                                seriesRec, true, sopIUIDs);
166                        while (instRec != null) {
167                            String iuid = instRec.getString(Tag.ReferencedSOPInstanceUIDInFile);
168                            String cuid = requestMapCopy.remove(iuid);
169                            if (cuid.equals(instRec.getString(Tag.ReferencedSOPClassUIDInFile))) {
170                                instanceStatusMap.put(iuid, Status.Success);
171                            }
172                            else {
173                                instanceStatusMap.put(iuid, Status.ClassInstanceConflict);
174                            }
175                               
176                            instRec = ddr.findNextInstanceRecord(instRec, true,
177                                    sopIUIDs);
178                        }
179                        seriesRec = ddr.findNextSeriesRecord(seriesRec);
180                    }
181                    studyRec = ddr.findNextStudyRecord(studyRec);
182                }
183                patRec = ddr.findNextPatientRecord(patRec);
184            }
185        } catch (IOException e) {
186            DcmQRSCP.LOG.info("Failed to M-READ " + ddr.getFile(), e);
187            throw new DicomServiceException(Status.ProcessingFailure, e);
188        }
189        
190        for (Map.Entry<String, String> entry : requestMapCopy.entrySet()) {
191            instanceStatusMap.put(entry.getKey(), Status.NoSuchObjectInstance);
192        }
193        
194        return instanceStatusMap;
195    }
196    
197    private static Attributes refSOP(String iuid, String cuid, int failureReason) {
198        Attributes attrs = new Attributes(3);
199        attrs.setString(Tag.ReferencedSOPClassUID, VR.UI, cuid);
200        attrs.setString(Tag.ReferencedSOPInstanceUID, VR.UI, iuid);
201        if (failureReason != Status.Success)
202            attrs.setInt(Tag.FailureReason, VR.US, failureReason);
203        return attrs;
204    }
205    
206}