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