View Javadoc
1   /**
2   The contents of this file are subject to the Mozilla Public License Version 1.1 
3   (the "License"); you may not use this file except in compliance with the License. 
4   You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
5   Software distributed under the License is distributed on an "AS IS" basis, 
6   WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
7   specific language governing rights and limitations under the License. 
8   
9   The Original Code is "DataTypeGenerator.java".  Description: 
10  "Generates skeletal source code for Datatype classes based on the 
11    HL7 database" 
12  
13  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
14  2001.  All Rights Reserved. 
15  
16  Contributor(s):  Eric Poiseau. 
17  
18  Alternatively, the contents of this file may be used under the terms of the 
19  GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
20  applicable instead of those above.  If you wish to allow use of your version of this 
21  file only under the terms of the GPL and not to allow others to use your version 
22  of this file under the MPL, indicate your decision by deleting  the provisions above 
23  and replace  them with the notice and other provisions required by the GPL License.  
24  If you do not delete the provisions above, a recipient may use your version of 
25  this file under either the MPL or the GPL. 
26  
27  */
28  
29  package ca.uhn.hl7v2.sourcegen;
30  
31  import java.io.BufferedWriter;
32  import java.io.File;
33  import java.io.FileOutputStream;
34  import java.io.IOException;
35  import java.io.OutputStreamWriter;
36  import java.io.StringWriter;
37  import java.sql.Connection;
38  import java.sql.ResultSet;
39  import java.sql.SQLException;
40  import java.sql.Statement;
41  import java.util.ArrayList;
42  import java.util.Arrays;
43  import java.util.HashSet;
44  import java.util.Set;
45  
46  import org.apache.velocity.Template;
47  import org.apache.velocity.VelocityContext;
48  import org.apache.velocity.context.Context;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  
52  import ca.uhn.hl7v2.HL7Exception;
53  import ca.uhn.hl7v2.database.NormativeDatabase;
54  import ca.uhn.hl7v2.model.DataTypeException;
55  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
56  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
57  
58  
59  /**
60   * Generates skeletal source code for Datatype classes based on the 
61   * HL7 database.  
62   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
63   * @author Eric Poiseau
64   */
65  public class DataTypeGenerator {
66      
67      private static final Logger log = LoggerFactory.getLogger(DataTypeGenerator.class);
68      private static boolean ourMakeAll;
69      
70      /**
71       * <p>Creates skeletal source code (without correct data structure but no business
72       * logic) for all data types found in the normative database.  For versions > 2.2, Primitive data types
73       * are not generated, because they are coded manually (as of HAPI 0.3).  
74      * @param theTemplatePackage 
75       */
76      public static void makeAll(String baseDirectory, String version, String theTemplatePackage, String theFileExt) throws IOException, SQLException, HL7Exception {
77          //make base directory
78          if (!(baseDirectory.endsWith("\\") || baseDirectory.endsWith("/"))) { 
79              baseDirectory = baseDirectory + "/";
80          }
81          File targetDir = SourceGenerator.makeDirectory(baseDirectory + DefaultModelClassFactory.getVersionPackagePath(version) + "datatype"); 
82          
83          //get list of data types
84          
85          ArrayList<String> types = getDataTypes(version);
86          
87          System.out.println("Generating " + types.size() + " datatypes for version " + version);
88          if (types.size() == 0) { 
89              log.warn("No version {} data types found in database {}", 
90              		version, System.getProperty("ca.on.uhn.hl7.database.url"));
91          }
92  
93          for (String type : types) {
94              try {
95                  String basePackage = DefaultModelClassFactory.getVersionPackageName(version);
96  
97                  String hapiTestGenType = System.getProperty("hapi.test.gentype");
98                  if (hapiTestGenType != null && !hapiTestGenType.contains(type)) {
99                      continue;
100                 }
101 
102                 make(targetDir, type, version, basePackage, theTemplatePackage, theFileExt);
103             } catch (DataTypeException dte) {
104                 log.warn("{} - {}", dte.getClass().getName(), dte.getMessage());
105             } catch (Exception e) {
106                 log.error("Error creating source code for all data types", e);
107             }
108         }
109     }
110 
111     public static ArrayList<String> getDataTypes(String version) throws SQLException {
112         ArrayList<String> types = new ArrayList<>();
113         NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
114         Connection conn = normativeDatabase.getConnection();
115         Statement stmt = conn.createStatement();
116         //get normal data types ... 
117         ResultSet rs = stmt.executeQuery("select data_type_code from HL7DataTypes, HL7Versions where HL7Versions.version_id = HL7DataTypes.version_id and HL7Versions.hl7_version = '" + version + "'");
118         while (rs.next()) {
119             types.add(rs.getString(1));
120         }
121         
122         
123         //get CF, CK, CM, CN, CQ sub-types ... 
124  
125        rs = stmt.executeQuery("select data_structure from HL7DataStructures, HL7Versions where (" + 
126             "data_type_code  = 'CF' or " + 
127             "data_type_code  = 'CK' or " + 
128             "data_type_code  = 'CM' or " + 
129             "data_type_code  = 'CN' or " + 
130             "data_type_code  = 'CQ') and " +
131 	    "HL7Versions.version_id = HL7DataStructures.version_id and  HL7Versions.hl7_version = '" + version + "'");
132         while (rs.next()) {
133             String string = rs.getString(1);
134             if (string.equals("-")) {
135                 continue;
136             }
137             
138             types.add(string);
139         }
140         
141         stmt.close();
142         normativeDatabase.returnConnection(conn);
143         return types;
144     }
145     
146     /**
147      * Creates source code for a single data type in the HL7 normative
148      * database. 
149      * @param targetDirectory the directory into which the file will be written
150      * @param dataType the name (e.g. ST, ID, etc.) of the data type to be created
151      * @param version the HL7 version of the intended data type
152      * @param theFileExt 
153      */
154     public static void make(File targetDirectory, String dataType, String version, String basePackage, String theTemplatePackage, String theFileExt) throws Exception {
155         //make sure that targetDirectory is a directory ... 
156         if (!targetDirectory.isDirectory()) throw new IOException("Can't create file in " + 
157             targetDirectory.toString() + " - it is not a directory.");
158                 
159         //get any components for this data type
160         NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
161         Connection conn = normativeDatabase.getConnection();
162         Statement stmt = conn.createStatement();
163         //this query is adapted from the XML SIG informative document
164         //System.out.println(sql.toString());  //for debugging
165         String sql = "SELECT HL7DataStructures.data_structure, HL7DataStructureComponents.seq_no, HL7DataStructures.description, HL7DataStructureComponents.table_id,  " +
166                 "HL7Components.description, HL7Components.table_id, HL7Components.data_type_code, HL7Components.data_structure " +
167                 "FROM HL7Versions LEFT JOIN (HL7DataStructures LEFT JOIN (HL7DataStructureComponents LEFT JOIN HL7Components " +
168                 "ON HL7DataStructureComponents.comp_no = HL7Components.comp_no AND " +
169                 "HL7DataStructureComponents.version_id = HL7Components.version_id) " +
170                 "ON HL7DataStructures.version_id = HL7DataStructureComponents.version_id " +
171                 "AND HL7DataStructures.data_structure = HL7DataStructureComponents.data_structure) " +
172                 "ON HL7DataStructures.version_id = HL7Versions.version_id " +
173                 "WHERE HL7DataStructures.data_structure = '" +
174                 dataType +
175                 "' AND HL7Versions.hl7_version = '" +
176                 version +
177                 "' ORDER BY HL7DataStructureComponents.seq_no";
178         ResultSet rs = stmt.executeQuery(sql);
179         
180         ArrayList<String> dataTypes = new ArrayList<>(20);
181         ArrayList<String> descriptions = new ArrayList<>(20);
182         ArrayList<Integer> tables = new ArrayList<>(20);
183         String description = null;
184         while (rs.next()) {
185             if (description == null) description = rs.getString(3);
186 
187             String de = rs.getString(5);
188             String dt = rs.getString(8);
189             int ta = rs.getInt(4);
190             //trim all CE_x to CE
191             if (dt != null) if (dt.startsWith("CE")) dt = "CE";
192             //System.out.println("Component: " + de + "  Data Type: " + dt);  //for debugging
193 
194             // Prior to HL7 2.5, the first component of a TS was
195             // an undefined component HAPI knows as TSComponentOne, but the
196             // database knows it as an ST
197             if (dataType.equals("TS") && "ST".equals(dt) && dataTypes.isEmpty()) {
198                 dt = "TSComponentOne";
199             }
200 
201             dataTypes.add(dt);
202             descriptions.add(de);
203             tables.add(ta);
204         }
205         stmt.close();
206         normativeDatabase.returnConnection(conn);
207         
208         //if there is only one component make a Primitive, otherwise make a Composite
209         String source;
210         if (dataTypes.size() == 1) {
211             if (ourMakeAll || dataType.equals("FT") || dataType.equals("ST") || dataType.equals("TX") 
212                     || dataType.equals("NM") || dataType.equals("SI") || dataType.equals("TN")
213                     || dataType.equals("GTS")) { 
214                 source = makePrimitive(new DatatypeDef(dataType, description), version, basePackage, theTemplatePackage);                
215             } else {
216                 source = null; //note: IS, ID, DT, DTM, and TM are coded manually
217             }
218         } else if (dataTypes.size() > 1) {
219             int numComponents = dataTypes.size();
220             //copy data into arrays ... 
221             String[] type = new String[numComponents];
222             String[] desc = new String[numComponents];
223             int[] table = new int[numComponents];
224             DatatypeComponentDeftatypeComponentDef">DatatypeComponentDef[] componentDefs = new DatatypeComponentDef[numComponents];
225             Set<String> names = new HashSet<>();
226             for (int i = 0; i < numComponents; i++) {
227                 type[i] = dataTypes.get(i);
228                 String componentName = descriptions.get(i);
229                 
230                 if (names.contains(componentName)) {
231                	 for (int j = 2; ; j++) {
232                		 if (!names.contains(componentName + j)) {
233                			 componentName = componentName + j;
234                			 break;
235                		 }
236                	 }
237                 }
238                 names.add(componentName);
239                 
240                 desc[i] = componentName;
241                 table[i] = tables.get(i);
242                 
243                 String typeName = dataTypes.get(i);
244                 typeName = SourceGenerator.getAlternateType(typeName, version);
245                 
246                 componentDefs[i] = new DatatypeComponentDef(dataType, i, typeName, componentName, tables.get(i));
247             }
248             
249             source = makeComposite(dataType, description, componentDefs, type, desc, table, version, basePackage, theTemplatePackage);
250         } else {
251             if (dataType.equals("var")) {
252                 return; // Varies isn't actually a type
253             }
254             
255             //no components?  
256             throw new DataTypeException("The data type " + dataType + " could not be found");
257         }
258         
259         //System.out.println(source);
260         
261         //write to file ... 
262         if (source != null) {
263             String targetFile = targetDirectory.toString() + "/" + dataType + "." + theFileExt;
264             try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile, false), SourceGenerator.ENCODING))) {
265                 writer.write(source);
266                 writer.flush();
267             }
268         }
269     }
270 
271     
272     /**
273      * Returns a String containing the complete source code for a Primitive HL7 data
274      * type.  Note: this method is no longer used, as all Primitives are now coded manually.  
275      */
276     private static String makePrimitive(DatatypeDef theDatatype, String version, String basePackage, String theTemplatePackage) throws Exception {
277 
278             StringWriter out = new StringWriter();
279 
280             theTemplatePackage = theTemplatePackage.replace(".", "/");
281             Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/datatype_primitive.vsm");
282             Context ctx = new VelocityContext();
283             ctx.put("datatype", theDatatype);
284             ctx.put("version", version);
285             ctx.put("basePackageName", basePackage);
286             ctx.put("normalBasePackageName", DefaultModelClassFactory.getVersionPackageName(version));
287             
288             template.merge(ctx, out);
289             return out.toString();
290             
291     }
292     
293     /**
294      * Returns a String containing source code for a Composite data type. The 
295      * dataTypes array contains the data type names (e.g. ST) of each component. 
296      * The descriptions array contains the corresponding descriptions (e.g. string).
297      */
298     private static String makeComposite(String dataType, String description, DatatypeComponentDef[] componentDefs, String[] dataTypes, 
299             String[] descriptions, int[] tables, String version, String basePackage, String theTemplatePackage) throws Exception {
300         
301             StringWriter out = new StringWriter();
302 
303         for (DatatypeComponentDef componentDef : componentDefs) {
304             if (componentDef.getType().equals(dataType)) {
305                 log.warn("Datatype {} has a component child also of type {}! Can not recurse like this", dataType, dataType);
306                 componentDef.setType("ST");
307             }
308         }
309             
310             for (int i = 0; i < dataTypes.length; i++) {
311                if (dataTypes[i].equals(dataType)) {
312                   log.warn("Datatype {} has a child also of type {}! Can not recurse like this", dataType, dataType);
313                   dataTypes[i] = "ST";
314                }
315             }
316             
317             theTemplatePackage = theTemplatePackage.replace(".", "/");
318             Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/datatype_composite.vsm");
319             Context ctx = new VelocityContext();
320             ctx.put("datatypeName", dataType);
321             ctx.put("version", version);
322             ctx.put("basePackageName", basePackage);
323             ctx.put("components", Arrays.asList(componentDefs));
324             ctx.put("desc", description);
325             
326             template.merge(ctx, out);
327             return out.toString();
328             
329     }
330     
331     //test
332     public static void main(String[] args) {
333         //System.out.println(makePrimitive("ID", "identifier"));
334         try {
335             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
336             //System.setProperty("ca.on.uhn.hl7.database.url", "jdbc:odbc:hl7v25");        
337             //make(new File("c:/testsourcegen"), args[0], args[1]);
338             //make(new File("c:/testsourcegen"), "CE_0048", "2.3");
339             makeAll("c:/testsourcegen", "2.5", "", "java");
340         } catch (Exception e) {
341             e.printStackTrace();
342         }
343            
344         //test directory maker
345         /*try {
346             makeDirectory(args[0]);
347         } catch (IOException ioe) {
348             ioe.printStackTrace();
349         }*/
350     }
351 
352     public static void writeDatatype(String theFileName, String theVersion, DatatypeDef theFieldDef, String theBasePackage, String theTemplatePackage) throws Exception {
353         
354         String text;
355         if (theFieldDef.getSubComponentDefs().isEmpty()) {
356             text = makePrimitive(theFieldDef, theVersion, theBasePackage, theTemplatePackage);
357         } else {
358             text = makeComposite(theFieldDef.getType(), theFieldDef.getName(), theFieldDef.getSubComponentDefs().toArray(new DatatypeComponentDef[0]), null, null, null, theVersion, theBasePackage, theTemplatePackage);
359         }
360 
361         BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(theFileName, false), SourceGenerator.ENCODING));
362         writer.write(text);
363         writer.flush();
364         writer.close();
365         
366     }
367     
368     /**
369      * Set to true if all data types should be made, including types which are normally
370      * not generated because they are special cases. Defaults to false.
371      */
372     public static void setMakeAll(boolean theMakeAll) {
373         ourMakeAll = theMakeAll;
374     }
375     
376 }