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  
44  import org.apache.velocity.Template;
45  import org.apache.velocity.VelocityContext;
46  import org.apache.velocity.context.Context;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import ca.uhn.hl7v2.HL7Exception;
51  import ca.uhn.hl7v2.database.NormativeDatabase;
52  import ca.uhn.hl7v2.model.DataTypeException;
53  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
54  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
55  
56  
57  /**
58   * Generates skeletal source code for Datatype classes based on the 
59   * HL7 database.  
60   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
61   * @author Eric Poiseau
62   */
63  public class DataTypeGenerator extends Object {
64      
65      private static final Logger log = LoggerFactory.getLogger(DataTypeGenerator.class);
66      private static boolean ourMakeAll;
67      
68      /**
69       * <p>Creates skeletal source code (without correct data structure but no business
70       * logic) for all data types found in the normative database.  For versions > 2.2, Primitive data types
71       * are not generated, because they are coded manually (as of HAPI 0.3).  
72      * @param theTemplatePackage 
73       */
74      public static void makeAll(String baseDirectory, String version, String theTemplatePackage, String theFileExt) throws IOException, SQLException, HL7Exception {
75          //make base directory
76          if (!(baseDirectory.endsWith("\\") || baseDirectory.endsWith("/"))) { 
77              baseDirectory = baseDirectory + "/";
78          }
79          File targetDir = SourceGenerator.makeDirectory(baseDirectory + DefaultModelClassFactory.getVersionPackagePath(version) + "datatype"); 
80          
81          //get list of data types
82          
83          ArrayList<String> types = getDataTypes(version);
84          
85          System.out.println("Generating " + types.size() + " datatypes for version " + version);
86          if (types.size() == 0) { 
87              log.warn("No version {} data types found in database {}", 
88              		version, System.getProperty("ca.on.uhn.hl7.database.url"));
89          }
90                  
91          for (int i = 0; i < types.size(); i++) {
92              try {
93                  String basePackage = DefaultModelClassFactory.getVersionPackageName(version);
94                  String dataType = (String)types.get(i);
95                  
96  				String hapiTestGenType = System.getProperty("hapi.test.gentype");
97  				if (hapiTestGenType != null && !hapiTestGenType.contains(dataType)) {
98  					continue;
99  				}
100                 
101 				make(targetDir, dataType, version, basePackage, theTemplatePackage, theFileExt);
102             } catch (DataTypeException dte) {
103                 log.warn("{} - {}", dte.getClass().getName(), dte.getMessage());
104             } catch (Exception e) {
105                 log.error("Error creating source code for all data types", e);
106             } 
107         }
108     }
109 
110     public static ArrayList<String> getDataTypes(String version) throws SQLException {
111         ArrayList<String> types = new ArrayList<String>();
112         NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
113         Connection conn = normativeDatabase.getConnection();
114         Statement stmt = conn.createStatement();
115         //get normal data types ... 
116         ResultSet rs = stmt.executeQuery("select data_type_code from HL7DataTypes, HL7Versions where HL7Versions.version_id = HL7DataTypes.version_id and HL7Versions.hl7_version = '" + version + "'");
117         while (rs.next()) {
118             types.add(rs.getString(1));
119         }
120         
121         
122         //get CF, CK, CM, CN, CQ sub-types ... 
123  
124        rs = stmt.executeQuery("select data_structure from HL7DataStructures, HL7Versions where (" + 
125             "data_type_code  = 'CF' or " + 
126             "data_type_code  = 'CK' or " + 
127             "data_type_code  = 'CM' or " + 
128             "data_type_code  = 'CN' or " + 
129             "data_type_code  = 'CQ') and " +
130 	    "HL7Versions.version_id = HL7DataStructures.version_id and  HL7Versions.hl7_version = '" + version + "'");
131         while (rs.next()) {
132             String string = rs.getString(1);
133             if (string.equals("-")) {
134                 continue;
135             }
136             
137             types.add(string);
138         }
139         
140         stmt.close();
141         normativeDatabase.returnConnection(conn);
142         return types;
143     }
144     
145     /**
146      * Creates source code for a single data type in the HL7 normative
147      * database. 
148      * @param targetDirectory the directory into which the file will be written
149      * @param datatype the name (e.g. ST, ID, etc.) of the data type to be created
150      * @param version the HL7 version of the intended data type
151      * @param theFileExt 
152      */
153     public static void make(File targetDirectory, String dataType, String version, String basePackage, String theTemplatePackage, String theFileExt) throws Exception {
154         //make sure that targetDirectory is a directory ... 
155         if (!targetDirectory.isDirectory()) throw new IOException("Can't create file in " + 
156             targetDirectory.toString() + " - it is not a directory.");
157                 
158         //get any components for this data type
159         NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
160         Connection conn = normativeDatabase.getConnection();
161         Statement stmt = conn.createStatement();
162         StringBuffer sql = new StringBuffer();
163         //this query is adapted from the XML SIG informative document
164         sql.append("SELECT HL7DataStructures.data_structure, HL7DataStructureComponents.seq_no, HL7DataStructures.description, HL7DataStructureComponents.table_id,  ");
165         sql.append("HL7Components.description, HL7Components.table_id, HL7Components.data_type_code, HL7Components.data_structure ");
166         sql.append("FROM HL7Versions LEFT JOIN (HL7DataStructures LEFT JOIN (HL7DataStructureComponents LEFT JOIN HL7Components ");
167         sql.append("ON HL7DataStructureComponents.comp_no = HL7Components.comp_no AND ");
168         sql.append("HL7DataStructureComponents.version_id = HL7Components.version_id) ");
169         sql.append("ON HL7DataStructures.version_id = HL7DataStructureComponents.version_id ");
170         sql.append("AND HL7DataStructures.data_structure = HL7DataStructureComponents.data_structure) ");
171         sql.append("ON HL7DataStructures.version_id = HL7Versions.version_id ");
172         sql.append("WHERE HL7DataStructures.data_structure = '");
173         sql.append(dataType);
174         sql.append("' AND HL7Versions.hl7_version = '");
175         sql.append(version);
176         sql.append("' ORDER BY HL7DataStructureComponents.seq_no");
177         //System.out.println(sql.toString());  //for debugging
178         ResultSet rs = stmt.executeQuery(sql.toString());
179         
180         ArrayList<String> dataTypes = new ArrayList<String>(20);
181         ArrayList<String> descriptions = new ArrayList<String>(20);
182         ArrayList<Integer> tables = new ArrayList<Integer>(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(new Integer(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 = null;
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             DatatypeComponentDef[] componentDefs = new DatatypeComponentDef[numComponents];
225             for (int i = 0; i < numComponents; i++) {
226                 type[i] = (String)dataTypes.get(i);
227                 desc[i] = (String)descriptions.get(i);
228                 table[i] = ((Integer) tables.get(i)).intValue();
229                 
230                 String typeName = (String)dataTypes.get(i);
231                 typeName = SourceGenerator.getAlternateType(typeName, version);
232                 
233                 componentDefs[i] = new DatatypeComponentDef(dataType, i, typeName, (String)descriptions.get(i), ((Integer) tables.get(i)).intValue());
234             }
235             
236             source = makeComposite(dataType, description, componentDefs, type, desc, table, version, basePackage, theTemplatePackage);
237         } else {
238             if (dataType.equals("var")) {
239                 return; // Varies isn't actually a type
240             }
241             
242             //no components?  
243             throw new DataTypeException("The data type " + dataType + " could not be found");
244         }
245         
246         //System.out.println(source);
247         
248         //write to file ... 
249         if (source != null) {
250             String targetFile = targetDirectory.toString() + "/" + dataType + "." + theFileExt;
251             BufferedWriter writer = null;
252             try {
253 	            writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile, false), SourceGenerator.ENCODING));
254 	            writer.write(source);
255 	            writer.flush();
256             } finally {
257             	if (writer != null) writer.close();
258             }
259         }
260     }
261 
262     
263     /**
264      * Returns a String containing the complete source code for a Primitive HL7 data
265      * type.  Note: this method is no longer used, as all Primitives are now coded manually.  
266      */
267     private static String makePrimitive(DatatypeDef theDatatype, String version, String basePackage, String theTemplatePackage) throws Exception {
268 
269             StringWriter out = new StringWriter();
270 
271             theTemplatePackage = theTemplatePackage.replace(".", "/");
272             Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/datatype_primitive.vsm");
273             Context ctx = new VelocityContext();
274             ctx.put("datatype", theDatatype);
275             ctx.put("version", version);
276             ctx.put("basePackageName", basePackage);
277             ctx.put("normalBasePackageName", DefaultModelClassFactory.getVersionPackageName(version));
278             
279             template.merge(ctx, out);
280             return out.toString();
281             
282     }
283     
284     /**
285      * Returns a String containing source code for a Composite data type. The 
286      * dataTypes array contains the data type names (e.g. ST) of each component. 
287      * The descriptions array contains the corresponding descriptions (e.g. string).
288      */
289     private static String makeComposite(String dataType, String description, DatatypeComponentDef[] componentDefs, String[] dataTypes, 
290             String[] descriptions, int[] tables, String version, String basePackage, String theTemplatePackage) throws Exception {
291         
292             StringWriter out = new StringWriter();
293 
294             theTemplatePackage = theTemplatePackage.replace(".", "/");
295             Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/datatype_composite.vsm");
296             Context ctx = new VelocityContext();
297             ctx.put("datatypeName", dataType);
298             ctx.put("version", version);
299             ctx.put("basePackageName", basePackage);
300             ctx.put("components", Arrays.asList(componentDefs));
301             ctx.put("desc", description);
302             
303             template.merge(ctx, out);
304             return out.toString();
305             
306     }
307     
308     //test
309     public static void main(String args[]) {
310         //System.out.println(makePrimitive("ID", "identifier"));
311         try {
312             Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
313             //System.setProperty("ca.on.uhn.hl7.database.url", "jdbc:odbc:hl7v25");        
314             //make(new File("c:/testsourcegen"), args[0], args[1]);
315             //make(new File("c:/testsourcegen"), "CE_0048", "2.3");
316             makeAll("c:/testsourcegen", "2.5", "", "java");
317         } catch (Exception e) {
318             e.printStackTrace();
319         }
320            
321         //test directory maker
322         /*try {
323             makeDirectory(args[0]);
324         } catch (IOException ioe) {
325             ioe.printStackTrace();
326         }*/
327     }
328 
329     public static void writeDatatype(String theFileName, String theVersion, DatatypeDef theFieldDef, String theBasePackage, String theTemplatePackage) throws Exception {
330         
331         String text;
332         if (theFieldDef.getSubComponentDefs().isEmpty()) {
333             text = makePrimitive(theFieldDef, theVersion, theBasePackage, theTemplatePackage);
334         } else {
335             text = makeComposite(theFieldDef.getType(), theFieldDef.getName(), theFieldDef.getSubComponentDefs().toArray(new DatatypeComponentDef[0]), null, null, null, theVersion, theBasePackage, theTemplatePackage);
336         }
337 
338         BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(theFileName, false), SourceGenerator.ENCODING));
339         writer.write(text);
340         writer.flush();
341         writer.close();
342         
343     }
344     
345     /**
346      * Set to true if all data types should be made, including types which are normally
347      * not generated because they are special cases. Defaults to false.
348      */
349     public static void setMakeAll(boolean theMakeAll) {
350         ourMakeAll = theMakeAll;
351     }
352     
353 }