View Javadoc

1   package ca.uhn.hl7v2.util;
2   
3   import java.io.BufferedReader;
4   import java.io.File;
5   import java.io.FileFilter;
6   import java.io.FileReader;
7   import java.io.FilenameFilter;
8   import java.io.IOException;
9   import java.util.HashMap;
10  import java.util.Map;
11  import java.util.StringTokenizer;
12  
13  import org.slf4j.Logger;
14  import org.slf4j.LoggerFactory;
15  
16  import ca.uhn.hl7v2.ErrorCode;
17  import ca.uhn.hl7v2.HL7Exception;
18  
19  /**
20   * <p>Implements CodeMapper using files to store code values.  Files are arranged
21   * in the following directory structure.  The base directory is called "codemap".
22   * This should be created under the hapi.home directory (see Home class; defaults to .).
23   * Under the base directory, there should be one directory for each interface, and
24   * each of these directories should be named after the interface.  For example if you
25   * had interfaces to pharmacy and lab systems you might have the following directories:</p>
26   * <p> <hapi.home>/codemap/pharmacy<br>
27   * <hapi.home>/codemap/lab</p>
28   * <p>Each directory should contain two files per HL7 table, named after the table numbers as
29   * follows: "hl7nnnn.li" and "hl7nnnn.il", where nnnn is the 4 digit table number.  The .il
30   * file contains maps from interface codes to local codes, and the .li file contains maps from
31   * local codes to interface codes (these unfortunately may not be symmetrical).</p>
32   * <p>Each line of a file contains a single code map, with the "from" value and the "to" value
33   * separated by a tab.  For example, the file <hapi.home>/lab/HL70001.li (to map local codes to interface
34   * codes for the HL7 admnistrative sex table in your lab system interface) might contain the
35   * following line: </p>
36   * <p>male&tab;M</p>
37   * <p>This means that the local code "male" maps to the interface code "M".</p>
38   * <p>Lines that start with "//" are treated as comments.</p>
39   * @author Bryan Tripp
40   */
41  public class FileCodeMapper extends CodeMapper {
42  
43      private static final Logger log = LoggerFactory.getLogger(FileCodeMapper.class);
44  
45      private boolean throwIfNoMatch = false;
46      File baseDir;
47      private Map<String, Map<String, Map<String, String>>> interfaceToLocal;
48      private Map<String, Map<String, Map<String, String>>> localToInterface;
49  
50      /**
51       * Creates a new instance of FileCodeMapper.  You should probably not
52       * construct a FileCodeMapper directly.  Use CodeMapper.getInstance()
53       * instead ... this will ensure that only a single instance is created,
54       * which is important for performance because code maps are loaded from
55       * disk every time this constructor is called.
56       */
57      public FileCodeMapper() throws HL7Exception {
58          baseDir = new File(Home.getHomeDirectory().getAbsolutePath() + "/codemap");
59          refreshCache();
60      }
61  
62      /**
63       * If values are cached in such a way that they are not guaranteed to be current, a call
64       * to this method refreshes the values.
65       */
66      public void refreshCache() throws HL7Exception {
67          localToInterface = new HashMap<String, Map<String, Map<String, String>>>(10);
68          interfaceToLocal = new HashMap<String, Map<String, Map<String, String>>>(10);
69  
70          log.info("Refreshing cache");
71  
72          try {
73              //get list of child directories
74              File[] interfaceDirs = this.baseDir.listFiles(new FileFilter() {
75                  public boolean accept(File pathname) {
76                      boolean acc = false;
77                      if (pathname.isDirectory())
78                          acc = true;
79                      return acc;
80                  }
81              });
82  
83              //loop through directories and set up maps
84              for (int i = 0; i < interfaceDirs.length; i++) {
85  
86                  log.info(
87                      "Checking directory {} for interface code maps.", interfaceDirs[i].getName());
88  
89                  //get list of .li (local -> interface) and .il (interface -> local) files
90                  File[] mapFiles = interfaceDirs[i].listFiles(new FilenameFilter() {
91                      public boolean accept(File dir, String name) {
92                          boolean acc = false;
93                          if (name.toUpperCase().startsWith("HL7")) {
94                              if (name.substring(name.lastIndexOf('.')).equals(".li")
95                                  || name.substring(name.lastIndexOf('.')).equals(".il"))
96                                  acc = true;
97                          }
98                          return acc;
99                      }
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 }