001package ca.uhn.hl7v2.util; 002 003import java.io.BufferedReader; 004import java.io.File; 005import java.io.FileFilter; 006import java.io.FileReader; 007import java.io.FilenameFilter; 008import java.io.IOException; 009import java.util.HashMap; 010import java.util.StringTokenizer; 011 012import org.slf4j.Logger; 013import org.slf4j.LoggerFactory; 014 015import ca.uhn.hl7v2.HL7Exception; 016 017/** 018 * <p>Implements CodeMapper using files to store code values. Files are arranged 019 * in the following directory structure. The base directory is called "codemap". 020 * This should be created under the hapi.home directory (see Home class; defaults to .). 021 * Under the base directory, there should be one directory for each interface, and 022 * each of these directories should be named after the interface. For example if you 023 * had interfaces to pharmacy and lab systems you might have the following directories:</p> 024 * <p> <hapi.home>/codemap/pharmacy<br> 025 * <hapi.home>/codemap/lab</p> 026 * <p>Each directory should contain two files per HL7 table, named after the table numbers as 027 * follows: "hl7nnnn.li" and "hl7nnnn.il", where nnnn is the 4 digit table number. The .il 028 * file contains maps from interface codes to local codes, and the .li file contains maps from 029 * local codes to interface codes (these unfortunately may not be symmetrical).</p> 030 * <p>Each line of a file contains a single code map, with the "from" value and the "to" value 031 * separated by a tab. For example, the file <hapi.home>/lab/HL70001.li (to map local codes to interface 032 * codes for the HL7 admnistrative sex table in your lab system interface) might contain the 033 * following line: </p> 034 * <p>male&tab;M</p> 035 * <p>This means that the local code "male" maps to the interface code "M".</p> 036 * <p>Lines that start with "//" are treated as comments.</p> 037 * @author Bryan Tripp 038 */ 039public class FileCodeMapper extends CodeMapper { 040 041 private static final Logger log = LoggerFactory.getLogger(FileCodeMapper.class); 042 043 private boolean throwIfNoMatch = false; 044 File baseDir; 045 private HashMap<String, HashMap<String, HashMap<String, String>>> interfaceToLocal; 046 private HashMap<String, HashMap<String, HashMap<String, String>>> localToInterface; 047 048 /** 049 * Creates a new instance of FileCodeMapper. You should probably not 050 * construct a FileCodeMapper directly. Use CodeMapper.getInstance() 051 * instead ... this will ensure that only a single instance is created, 052 * which is important for performance because code maps are loaded from 053 * disk every time this constructor is called. 054 */ 055 public FileCodeMapper() throws HL7Exception { 056 baseDir = new File(Home.getHomeDirectory().getAbsolutePath() + "/codemap"); 057 refreshCache(); 058 } 059 060 /** 061 * If values are cached in such a way that they are not guaranteed to be current, a call 062 * to this method refreshes the values. 063 */ 064 public void refreshCache() throws HL7Exception { 065 localToInterface = new HashMap<String, HashMap<String, HashMap<String, String>>>(10); 066 interfaceToLocal = new HashMap<String, HashMap<String, HashMap<String, String>>>(10); 067 068 log.info("Refreshing cache"); 069 070 try { 071 //get list of child directories 072 File[] interfaceDirs = this.baseDir.listFiles(new FileFilter() { 073 public boolean accept(File pathname) { 074 boolean acc = false; 075 if (pathname.isDirectory()) 076 acc = true; 077 return acc; 078 } 079 }); 080 081 //loop through directories and set up maps 082 for (int i = 0; i < interfaceDirs.length; i++) { 083 084 log.info( 085 "Checking directory {} for interface code maps.", interfaceDirs[i].getName()); 086 087 //get list of .li (local -> interface) and .il (interface -> local) files 088 File[] mapFiles = interfaceDirs[i].listFiles(new FilenameFilter() { 089 public boolean accept(File dir, String name) { 090 boolean acc = false; 091 if (name.toUpperCase().startsWith("HL7")) { 092 if (name.substring(name.lastIndexOf('.')).equals(".li") 093 || name.substring(name.lastIndexOf('.')).equals(".il")) 094 acc = true; 095 } 096 return acc; 097 } 098 }); 099 100 //read map entries from each file and add to hash maps for li and il codes 101 HashMap<String, HashMap<String, String>> li = new HashMap<String, HashMap<String, String>>(50); 102 HashMap<String, HashMap<String, String>> il = new HashMap<String, HashMap<String, String>>(50); 103 for (int j = 0; j < mapFiles.length; j++) { 104 log.info("Reading map entries from file {}", mapFiles[j]); 105 106 String fName = mapFiles[j].getName(); 107 String tableName = fName.substring(0, fName.lastIndexOf('.')); 108 String mapDirection = fName.substring(fName.lastIndexOf('.') + 1); 109 110 //read values and store in HashMap 111 BufferedReader in = new BufferedReader(new FileReader(mapFiles[j])); 112 HashMap<String, String> codeMap = new HashMap<String, String>(25); 113 while (in.ready()) { 114 String line = in.readLine(); 115 if (!line.startsWith("//")) { 116 StringTokenizer tok = new StringTokenizer(line, "\t", false); 117 String from = null; 118 String to = null; 119 if (tok.hasMoreTokens()) 120 from = tok.nextToken(); 121 if (tok.hasMoreTokens()) 122 to = tok.nextToken(); 123 if (from != null && to != null) 124 codeMap.put(from, to); 125 } 126 } 127 128 //add to appropriate map for this interface 129 if (mapDirection.equals("il")) { 130 il.put(tableName.toUpperCase(), codeMap); 131 log.debug("Adding {} codes to interface -> local map for {} in {} interface", 132 new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()}); 133 } 134 else { 135 li.put(tableName.toUpperCase(), codeMap); 136 log.debug("Adding {} codes to local -> interface map for {} in {} interface", 137 new Object[] {codeMap.size(), tableName, interfaceDirs[i].getName()}); 138 } 139 } 140 141 //add maps for this interface (this directory) to global list 142 interfaceToLocal.put(interfaceDirs[i].getName(), il); 143 localToInterface.put(interfaceDirs[i].getName(), li); 144 } 145 146 } 147 catch (IOException e) { 148 throw new HL7Exception( 149 "Can't read interface code maps from disk", 150 HL7Exception.APPLICATION_INTERNAL_ERROR, 151 e); 152 } 153 } 154 155 /** 156 * Returns the local code for the given interface code as it appears in 157 * the given interface. 158 */ 159 public String getLocalCode(String interfaceName, int hl7Table, String interfaceCode) throws HL7Exception { 160 String localCode = null; 161 try { 162 HashMap<String, HashMap<String, String>> interfaceMap = interfaceToLocal.get(interfaceName); 163 localCode = getCode(interfaceMap, hl7Table, interfaceCode); 164 } 165 catch (NullPointerException npe) { 166 if (this.throwIfNoMatch) 167 throw new HL7Exception( 168 "No local mapping for the interface code " 169 + interfaceCode 170 + " for HL7 table " 171 + hl7Table 172 + " for the interface '" 173 + interfaceName 174 + "'", 175 HL7Exception.TABLE_VALUE_NOT_FOUND); 176 } 177 return localCode; 178 } 179 180 /** 181 * Common code for getLocalcode and getInterfaceCode 182 */ 183 private String getCode(HashMap<String, HashMap<String, String>> interfaceMap, int hl7Table, String code) { 184 String ret = null; 185 186 //get map for the given table 187 StringBuffer tableName = new StringBuffer(); 188 tableName.append("HL7"); 189 if (hl7Table < 1000) 190 tableName.append("0"); 191 if (hl7Table < 100) 192 tableName.append("0"); 193 if (hl7Table < 10) 194 tableName.append("0"); 195 tableName.append(hl7Table); 196 HashMap<String, String> tableMap = interfaceMap.get(tableName.toString()); 197 198 //get code 199 ret = tableMap.get(code).toString(); 200 return ret; 201 } 202 203 /** 204 * Returns the interface code for the given local code, for use in the context 205 * of the given interface. 206 */ 207 public String getInterfaceCode(String interfaceName, int hl7Table, String localCode) throws HL7Exception { 208 String interfaceCode = null; 209 try { 210 HashMap<String, HashMap<String, String>> interfaceMap = localToInterface.get(interfaceName); 211 interfaceCode = getCode(interfaceMap, hl7Table, localCode); 212 } 213 catch (NullPointerException npe) { 214 if (this.throwIfNoMatch) 215 throw new HL7Exception( 216 "No interface mapping for the local code " 217 + localCode 218 + " for HL7 table " 219 + hl7Table 220 + " for the interface '" 221 + interfaceName 222 + "'", 223 HL7Exception.TABLE_VALUE_NOT_FOUND); 224 } 225 return interfaceCode; 226 } 227 228 /** 229 * Determines what happens if no matching code is found during a lookup. If set to true, 230 * an HL7Exception is thrown if there is no match. If false, null is returned. The default 231 * is false. 232 */ 233 public void throwExceptionIfNoMatch(boolean throwException) { 234 this.throwIfNoMatch = throwException; 235 } 236 237 /** 238 * Test harness. 239 */ 240 public static void main(String args[]) { 241 try { 242 //FileCodeMapper mapper = new FileCodeMapper(); 243 CodeMapper.getInstance().throwExceptionIfNoMatch(true); 244 System.out.println("Local code for M is " + CodeMapper.getLocal("test", 1, "M")); 245 System.out.println("Interface code for female is " + CodeMapper.getInt("test", 1, "female")); 246 247 } 248 catch (HL7Exception e) { 249 e.printStackTrace(); 250 } 251 } 252 253}