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.media; 040 041import java.io.IOException; 042import java.util.Arrays; 043import java.util.EnumMap; 044import java.util.EnumSet; 045import java.util.HashMap; 046 047import javax.xml.parsers.ParserConfigurationException; 048import javax.xml.parsers.SAXParser; 049import javax.xml.parsers.SAXParserFactory; 050 051import org.dcm4che3.data.Tag; 052import org.dcm4che3.data.Attributes; 053import org.dcm4che3.data.Sequence; 054import org.dcm4che3.data.VR; 055import org.dcm4che3.io.ContentHandlerAdapter; 056import org.dcm4che3.util.ResourceLocator; 057import org.xml.sax.SAXException; 058 059/** 060 * @author Gunter Zeilinger <gunterze@gmail.com> 061 */ 062public class RecordFactory { 063 064 private static final int IN_USE = 0xffff; 065 066 private EnumMap<RecordType, int[]> recordKeys; 067 068 private HashMap<String, RecordType> recordTypes; 069 070 private HashMap<String, String> privateRecordUIDs; 071 072 private HashMap<String, int[]> privateRecordKeys; 073 074 private void lazyLoadDefaultConfiguration() { 075 if (recordTypes == null) 076 loadDefaultConfiguration(); 077 } 078 079 public void loadDefaultConfiguration() { 080 try { 081 loadConfiguration(ResourceLocator.getResource( 082 "org/dcm4che3/media/RecordFactory.xml", this.getClass())); 083 } catch (Exception e) { 084 throw new RuntimeException(e); 085 } 086 } 087 088 public void loadConfiguration(String uri) 089 throws ParserConfigurationException, SAXException, IOException { 090 Attributes attrs = parseXML(uri); 091 Sequence sq = attrs.getSequence(Tag.DirectoryRecordSequence); 092 if (sq == null) 093 throw new IllegalArgumentException( 094 "Missing Directory Record Sequence in " + uri); 095 096 EnumMap<RecordType, int[]> recordKeys = new EnumMap<RecordType, int[]>( 097 RecordType.class); 098 HashMap<String, RecordType> recordTypes = new HashMap<String, RecordType>( 099 134); 100 HashMap<String, String> privateRecordUIDs = new HashMap<String, String>(); 101 HashMap<String, int[]> privateRecordKeys = new HashMap<String, int[]>(); 102 for (Attributes item : sq) { 103 RecordType type = RecordType.forCode(item.getString( 104 Tag.DirectoryRecordType, null)); 105 String privuid = type == RecordType.PRIVATE ? item.getString( 106 Tag.PrivateRecordUID, null) : null; 107 String[] cuids = item.getStrings(Tag.ReferencedSOPClassUIDInFile); 108 if (cuids != null) { 109 if (type != RecordType.PRIVATE) { 110 for (String cuid : cuids) { 111 recordTypes.put(cuid, type); 112 } 113 } else if (privuid != null) { 114 for (String cuid : cuids) { 115 privateRecordUIDs.put(cuid, privuid); 116 } 117 } 118 } 119 item.remove(Tag.DirectoryRecordType); 120 item.remove(Tag.PrivateRecordUID); 121 item.remove(Tag.ReferencedSOPClassUIDInFile); 122 int[] keys = item.tags(); 123 if (privuid != null) { 124 if (privateRecordKeys.put(privuid, keys) != null) 125 throw new IllegalArgumentException( 126 "Duplicate Private Record UID: " + privuid); 127 } else { 128 if (recordKeys.put(type, keys) != null) 129 throw new IllegalArgumentException( 130 "Duplicate Record Type: " + type); 131 } 132 } 133 EnumSet<RecordType> missingTypes = EnumSet.allOf(RecordType.class); 134 missingTypes.removeAll(recordKeys.keySet()); 135 if (!missingTypes.isEmpty()) 136 throw new IllegalArgumentException("Missing Record Types: " 137 + missingTypes); 138 this.recordTypes = recordTypes; 139 this.recordKeys = recordKeys; 140 this.privateRecordUIDs = privateRecordUIDs; 141 this.privateRecordKeys = privateRecordKeys; 142 } 143 144 private Attributes parseXML(String uri) 145 throws ParserConfigurationException, SAXException, IOException { 146 Attributes attrs = new Attributes(); 147 SAXParserFactory f = SAXParserFactory.newInstance(); 148 SAXParser parser = f.newSAXParser(); 149 parser.parse(uri, new ContentHandlerAdapter(attrs)); 150 return attrs; 151 } 152 153 public RecordType getRecordType(String cuid) { 154 if (cuid == null) 155 throw new NullPointerException(); 156 lazyLoadDefaultConfiguration(); 157 RecordType recordType = recordTypes.get(cuid); 158 return recordType != null ? recordType : RecordType.PRIVATE; 159 } 160 161 public RecordType setRecordType(String cuid, RecordType type) { 162 if (cuid == null || type == null) 163 throw new NullPointerException(); 164 lazyLoadDefaultConfiguration(); 165 return recordTypes.put(cuid, type); 166 } 167 168 public void setRecordKeys(RecordType type, int[] keys) { 169 if (type == null) 170 throw new NullPointerException(); 171 int[] tmp = keys.clone(); 172 Arrays.sort(tmp); 173 lazyLoadDefaultConfiguration(); 174 recordKeys.put(type, keys); 175 } 176 177 public String getPrivateRecordUID(String cuid) { 178 if (cuid == null) 179 throw new NullPointerException(); 180 181 lazyLoadDefaultConfiguration(); 182 String uid = privateRecordUIDs.get(cuid); 183 return uid != null ? uid : cuid; 184 } 185 186 public String setPrivateRecordUID(String cuid, String uid) { 187 if (cuid == null || uid == null) 188 throw new NullPointerException(); 189 190 lazyLoadDefaultConfiguration(); 191 return privateRecordUIDs.put(cuid, uid); 192 } 193 194 public int[] setPrivateRecordKeys(String uid, int[] keys) { 195 if (uid == null) 196 throw new NullPointerException(); 197 198 int[] tmp = keys.clone(); 199 Arrays.sort(tmp); 200 lazyLoadDefaultConfiguration(); 201 return privateRecordKeys.put(uid, tmp); 202 } 203 204 public Attributes createRecord(Attributes dataset, Attributes fmi, 205 String[] fileIDs) { 206 String cuid = fmi.getString(Tag.MediaStorageSOPClassUID, null); 207 RecordType type = getRecordType(cuid); 208 return createRecord(type, 209 type == RecordType.PRIVATE ? getPrivateRecordUID(cuid) : null, 210 dataset, fmi, fileIDs); 211 } 212 213 public Attributes createRecord(RecordType type, String privRecUID, 214 Attributes dataset, Attributes fmi, String[] fileIDs) { 215 if (type == null) 216 throw new NullPointerException("type"); 217 if (dataset == null) 218 throw new NullPointerException("dataset"); 219 220 lazyLoadDefaultConfiguration(); 221 int[] keys = null; 222 if (type == RecordType.PRIVATE) { 223 if (privRecUID == null) 224 throw new NullPointerException( 225 "privRecUID must not be null for type = PRIVATE"); 226 keys = privateRecordKeys.get(privRecUID); 227 } else { 228 if (privRecUID != null) 229 throw new IllegalArgumentException( 230 "privRecUID must be null for type != PRIVATE"); 231 } 232 if (keys == null) 233 keys = recordKeys.get(type); 234 Attributes rec = new Attributes(keys.length + (fileIDs != null ? 9 : 5)); 235 rec.setInt(Tag.OffsetOfTheNextDirectoryRecord, VR.UL, 0); 236 rec.setInt(Tag.RecordInUseFlag, VR.US, IN_USE); 237 rec.setInt(Tag.OffsetOfReferencedLowerLevelDirectoryEntity, VR.UL, 0); 238 rec.setString(Tag.DirectoryRecordType, VR.CS, type.code()); 239 if (privRecUID != null) 240 rec.setString(Tag.PrivateRecordUID, VR.UI, privRecUID); 241 if (fileIDs != null) { 242 rec.setString(Tag.ReferencedFileID, VR.CS, fileIDs); 243 rec.setString(Tag.ReferencedSOPClassUIDInFile, VR.UI, 244 fmi.getString(Tag.MediaStorageSOPClassUID, null)); 245 rec.setString(Tag.ReferencedSOPInstanceUIDInFile, VR.UI, 246 fmi.getString(Tag.MediaStorageSOPInstanceUID, null)); 247 rec.setString(Tag.ReferencedTransferSyntaxUIDInFile, VR.UI, 248 fmi.getString(Tag.TransferSyntaxUID, null)); 249 } 250 rec.addSelected(dataset, keys, 0, keys.length); 251 Sequence contentSeq = dataset.getSequence(Tag.ContentSequence); 252 if (contentSeq != null) 253 copyConceptMod(contentSeq, rec); 254 return rec; 255 } 256 257 private void copyConceptMod(Sequence srcSeq, Attributes rec) { 258 Sequence dstSeq = null; 259 for (Attributes item : srcSeq) { 260 if ("HAS CONCEPT MOD".equals(item.getString(Tag.RelationshipType, 261 null))) { 262 if (dstSeq == null) 263 dstSeq = rec.newSequence(Tag.ContentSequence, 1); 264 dstSeq.add(new Attributes(item, false)); 265 } 266 } 267 } 268}