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      final 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<>(10);
68          interfaceToLocal = new HashMap<>(10);
69  
70          log.info("Refreshing cache");
71  
72          try {
73              //get list of child directories
74              File[] interfaceDirs = this.baseDir.listFiles(pathname -> {
75                  boolean acc = false;
76                  if (pathname.isDirectory())
77                      acc = true;
78                  return acc;
79              });
80  
81              //loop through directories and set up maps
82              for (File interfaceDir : interfaceDirs) {
83  
84                  log.info(
85                          "Checking directory {} for interface code maps.", interfaceDir.getName());
86  
87                  //get list of .li (local -> interface) and .il (interface -> local) files
88                  File[] mapFiles = interfaceDir.listFiles((dir, name) -> {
89                      boolean acc = false;
90                      if (name.toUpperCase().startsWith("HL7")) {
91                          if (name.substring(name.lastIndexOf('.')).equals(".li")
92                                  || name.substring(name.lastIndexOf('.')).equals(".il"))
93                              acc = true;
94                      }
95                      return acc;
96                  });
97  
98                  //read map entries from each file and add to hash maps for li and il codes
99                  HashMap<String, Map<String, String>> li = new HashMap<>(50);
100                 HashMap<String, Map<String, String>> il = new HashMap<>(50);
101                 for (File mapFile : mapFiles) {
102                     log.info("Reading map entries from file {}", mapFile);
103 
104                     String fName = mapFile.getName();
105                     String tableName = fName.substring(0, fName.lastIndexOf('.'));
106                     String mapDirection = fName.substring(fName.lastIndexOf('.') + 1);
107 
108                     //read values and store in HashMap
109                     Map<String, String> codeMap = new HashMap<>(25);
110                     try (BufferedReader in = new BufferedReader(new FileReader(mapFile))) {
111                         while (in.ready()) {
112                             String line = in.readLine();
113                             if (!line.startsWith("//")) {
114                                 StringTokenizer tok = new StringTokenizer(line, "\t", false);
115                                 String from = null;
116                                 String to = null;
117                                 if (tok.hasMoreTokens())
118                                     from = tok.nextToken();
119                                 if (tok.hasMoreTokens())
120                                     to = tok.nextToken();
121                                 if (from != null && to != null)
122                                     codeMap.put(from, to);
123                             }
124                         }
125                     }
126 
127                     //add to appropriate map for this interface
128                     if (mapDirection.equals("il")) {
129                         il.put(tableName.toUpperCase(), codeMap);
130                         log.debug("Adding {} codes to interface -> local map for {} in {} interface",
131                                 codeMap.size(), tableName, interfaceDir.getName());
132                     } else {
133                         li.put(tableName.toUpperCase(), codeMap);
134                         log.debug("Adding {} codes to local -> interface map for {} in {} interface",
135                                 codeMap.size(), tableName, interfaceDir.getName());
136                     }
137                 }
138 
139                 //add maps for this interface (this directory) to global list
140                 interfaceToLocal.put(interfaceDir.getName(), il);
141                 localToInterface.put(interfaceDir.getName(), li);
142             }
143 
144         }
145         catch (IOException e) {
146             throw new HL7Exception(
147                 "Can't read interface code maps from disk", e);
148         }
149     }
150 
151     /**
152      * Returns the local code for the given interface code as it appears in
153      * the given interface.
154      */
155     public String getLocalCode(String interfaceName, int hl7Table, String interfaceCode) throws HL7Exception {
156         String localCode = null;
157         try {
158             Map<String, Map<String, String>> interfaceMap = interfaceToLocal.get(interfaceName);
159             localCode = getCode(interfaceMap, hl7Table, interfaceCode);
160         }
161         catch (NullPointerException npe) {
162             if (this.throwIfNoMatch)
163                 throw new HL7Exception(
164                     "No local mapping for the interface code "
165                         + interfaceCode
166                         + " for HL7 table "
167                         + hl7Table
168                         + " for the interface '"
169                         + interfaceName
170                         + "'",
171                     ErrorCode.TABLE_VALUE_NOT_FOUND);
172         }
173         return localCode;
174     }
175 
176     /**
177      * Common code for getLocalcode and getInterfaceCode
178      */
179     private String getCode(Map<String, Map<String, String>> interfaceMap, int hl7Table, String code) {
180         String ret;
181 
182         //get map for the given table
183         StringBuilder tableName = new StringBuilder();
184         tableName.append("HL7");
185         if (hl7Table < 1000)
186             tableName.append("0");
187         if (hl7Table < 100)
188             tableName.append("0");
189         if (hl7Table < 10)
190             tableName.append("0");
191         tableName.append(hl7Table);
192         Map<String, String> tableMap = interfaceMap.get(tableName.toString());
193 
194         //get code
195         ret = tableMap.get(code);
196         return ret;
197     }
198 
199     /**
200      * Returns the interface code for the given local code, for use in the context
201      * of the given interface.
202      */
203     public String getInterfaceCode(String interfaceName, int hl7Table, String localCode) throws HL7Exception {
204         String interfaceCode = null;
205         try {
206             Map<String, Map<String, String>> interfaceMap = localToInterface.get(interfaceName);
207             interfaceCode = getCode(interfaceMap, hl7Table, localCode);
208         }
209         catch (NullPointerException npe) {
210             if (this.throwIfNoMatch)
211                 throw new HL7Exception(
212                     "No interface mapping for the local code "
213                         + localCode
214                         + " for HL7 table "
215                         + hl7Table
216                         + " for the interface '"
217                         + interfaceName
218                         + "'",
219                     ErrorCode.TABLE_VALUE_NOT_FOUND);
220         }
221         return interfaceCode;
222     }
223 
224     /**
225      * Determines what happens if no matching code is found during a lookup.  If set to true,
226      * an HL7Exception is thrown if there is no match.  If false, null is returned.  The default
227      * is false.
228      */
229     public void throwExceptionIfNoMatch(boolean throwException) {
230         this.throwIfNoMatch = throwException;
231     }
232 
233     /**
234      * Test harness.
235      */
236     public static void main(String[] args) {
237         try {
238             //FileCodeMapper mapper = new FileCodeMapper();
239             CodeMapper.getInstance().throwExceptionIfNoMatch(true);
240             System.out.println("Local code for M is " + CodeMapper.getLocal("test", 1, "M"));
241             System.out.println("Interface code for female is " + CodeMapper.getInt("test", 1, "female"));
242 
243         }
244         catch (HL7Exception e) {
245             e.printStackTrace();
246         }
247     }
248 
249 }