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}