001 package ca.uhn.hl7v2.util;
002
003 import java.io.BufferedReader;
004 import java.io.File;
005 import java.io.FileFilter;
006 import java.io.FileReader;
007 import java.io.FilenameFilter;
008 import java.io.IOException;
009 import java.util.HashMap;
010 import java.util.StringTokenizer;
011
012 import org.slf4j.Logger;
013 import org.slf4j.LoggerFactory;
014
015 import 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 */
039 public 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 }