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 "SegmentGenerator.java".  Description:
10   * "This class is responsible for generating source code for HL7 segment objects"
11   *
12   * The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   * 2001.  All Rights Reserved.
14   *
15   * Contributor(s):  Eric Poiseau. 
16   *
17   * Alternatively, the contents of this file may be used under the terms of the
18   * GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
19   * applicable instead of those above.  If you wish to allow use of your version of this
20   * file only under the terms of the GPL and not to allow others to use your version
21   * of this file under the MPL, indicate your decision by deleting  the provisions above
22   * and replace  them with the notice and other provisions required by the GPL License.
23   * If you do not delete the provisions above, a recipient may use your version of
24   * this file under either the MPL or the GPL.
25   *
26   */
27  package ca.uhn.hl7v2.sourcegen;
28  
29  import java.io.BufferedWriter;
30  import java.io.File;
31  import java.io.FileOutputStream;
32  import java.io.IOException;
33  import java.io.OutputStreamWriter;
34  import java.sql.Connection;
35  import java.sql.ResultSet;
36  import java.sql.SQLException;
37  import java.sql.Statement;
38  import java.util.ArrayList;
39  import java.util.Collections;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Set;
43  
44  import org.apache.maven.plugin.MojoExecutionException;
45  import org.apache.maven.plugin.MojoFailureException;
46  import org.apache.velocity.Template;
47  import org.apache.velocity.VelocityContext;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  import ca.uhn.hl7v2.HL7Exception;
52  import ca.uhn.hl7v2.database.NormativeDatabase;
53  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
54  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
55  
56  /**
57   * This class is responsible for generating source code for HL7 segment objects.
58   * Each automatically generated segment inherits from AbstractSegment.
59   * 
60   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
61   * @author Eric Poiseau
62   */
63  public class SegmentGenerator {
64  
65  	private static final Logger log = LoggerFactory.getLogger(SegmentGenerator.class);
66  
67  	/**
68  	 * <p>Creates skeletal source code (without correct data structure but no business
69  	 * logic) for all segments found in the normative database.  </p>
70  	 */
71  	public static void makeAll(String baseDirectory, String version, String theTemplatePackage, String theFileExt) throws IOException, SQLException, HL7Exception, MojoExecutionException {
72  		//make base directory
73  		if (!(baseDirectory.endsWith("\\") || baseDirectory.endsWith("/"))) {
74  			baseDirectory = baseDirectory + "/";
75  		}
76  		File targetDir = SourceGenerator.makeDirectory(baseDirectory + DefaultModelClassFactory.getVersionPackagePath(version) + "segment");
77  
78  		ArrayList<String> segments = getSegmentNames(version);
79  
80  		if (segments.size() == 0) {
81  			log.warn("No version {} segments found in database {}", 
82  					version, System.getProperty("ca.on.uhn.hl7.database.url"));
83  		}
84  
85  		for (String segment : segments) {
86  			try {
87  
88  				String hapiTestGenSegment = System.getProperty("hapi.test.gensegment");
89  				if (hapiTestGenSegment != null && !hapiTestGenSegment.contains(segment)) {
90  					continue;
91  				}
92  
93  				makeSegment(segment, version, theTemplatePackage, targetDir, theFileExt);
94  			} catch (Exception e) {
95  //				System.err.println("Error creating source code for all segments: " + e.getMessage());
96  //				e.printStackTrace();
97  				throw new MojoExecutionException("Failure generating segments", e);
98  			}
99  		}
100 	}
101 
102     public static ArrayList<String> getSegmentNames(String version) throws SQLException {
103         //get list of segments
104 		NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
105 		Connection conn = normativeDatabase.getConnection();
106 		Statement stmt = conn.createStatement();
107 		String sql = "SELECT seg_code, section from HL7Segments, HL7Versions where HL7Segments.version_id = HL7Versions.version_id AND hl7_version = '" + version + "'";
108 		//System.out.println(sql);
109 		ResultSet rs = stmt.executeQuery(sql);
110 
111 		ArrayList<String> segments = new ArrayList<>();
112 		while (rs.next()) {
113 			String segName = rs.getString(1);
114 
115             // The DB has an invalid segment with this name
116             if ("ED".equals(segName)) {
117                 continue;
118             }
119 
120 			if (Character.isLetter(segName.charAt(0))) {
121 				segments.add(altSegName(segName));
122 			}
123 		}
124 		stmt.close();
125 		normativeDatabase.returnConnection(conn);
126         return segments;
127     }
128 
129 	/**
130 	 * <p>Returns an alternate segment name to replace the given segment name.  Substitutions
131 	 * made include:  </p>
132 	 * <ul><li>Replacing Z.. with Z</li>
133 	 *<li>Replacing ??? with ???</li></ul>
134 	 */
135 	public static String altSegName(String segmentName) {
136 		String ret = segmentName;
137 		if (ret.equals("Z..")) {
138 			ret = "Z";
139 		}
140 		if (ret.equals("CON")) {
141 			ret = "CON_";
142 		}
143 		return ret;
144 	}
145 
146 	/**
147 	 * Returns the Java source code for a class that represents the specified segment.
148 	 */
149 	public static void makeSegment(String name, String version, String theTemplatePackage, File theTargetDir, String theFileExt) throws Exception {
150 
151 		ArrayList<SegmentElement> elements = new ArrayList<>();
152 		String segDesc = null;
153 		SegmentElement se;
154 
155         NormativeDatabase normativeDatabase = NormativeDatabase.getInstance();
156 		try {
157 			Connection conn = normativeDatabase.getConnection();
158 
159 //			sql.append("SELECT ");
160 //			sql.append("HL7SegmentDataElements.seg_code, HL7SegmentDataElements.seq_no, ");
161 //			sql.append("HL7SegmentDataElements.repetitional, HL7SegmentDataElements.repetitions, ");
162 //			sql.append("HL7DataElements.description, HL7DataElements.length, HL7DataElements.table_id, ");
163 //			sql.append("HL7SegmentDataElements.req_opt, HL7Segments.description, HL7DataElements.data_structure ");
164 //			sql.append("FROM HL7Versions RIGHT JOIN (HL7Segments INNER JOIN (HL7DataElements INNER JOIN HL7SegmentDataElements ");
165 //			sql.append("ON (HL7DataElements.version_id = HL7SegmentDataElements.version_id) ");
166 //			sql.append("AND (HL7DataElements.data_item = HL7SegmentDataElements.data_item)) ");
167 //			sql.append("ON (HL7Segments.version_id = HL7SegmentDataElements.version_id) ");
168 //			sql.append("AND (HL7Segments.seg_code = HL7SegmentDataElements.seg_code)) ");
169 //			sql.append("ON (HL7Versions.version_id = HL7Segments.version_id) ");
170 //			sql.append("WHERE HL7SegmentDataElements.seg_code = '");
171 //			sql.append(name);
172 //			sql.append("' and HL7Versions.hl7_version = '");
173 //			sql.append(version);
174 //			sql.append("' ORDER BY HL7SegmentDataElements.seg_code, HL7SegmentDataElements.seq_no;");
175 
176 //			if (false) {
177 				listTables(conn, "HL7SegmentDataElements");
178 				listTables(conn, "HL7DataElements");
179 				listTables(conn, "HL7Segments");
180 //			}
181 			
182 			String lengthColName;
183 			if (version.startsWith("2.7") || version.startsWith("2.8")) {
184 				lengthColName = "HL7DataElements.max_length";
185 			} else {
186 				lengthColName = "HL7DataElements.length";
187 			}
188 			
189 			StringBuilder sql = new StringBuilder();
190 			sql.append("SELECT ");
191 //			sql.append("HL7SegmentDataElements.*, ");
192 //			sql.append("HL7DataElements.*, ");
193 //			sql.append("HL7Segments.* ");
194 			sql.append("HL7SegmentDataElements.seg_code, HL7SegmentDataElements.seq_no, ");
195 			sql.append("HL7SegmentDataElements.repetitional, HL7SegmentDataElements.repetitions, ");
196 			sql.append("HL7DataElements.description, ").append(lengthColName).append(", HL7DataElements.table_id, ");
197 			sql.append("HL7SegmentDataElements.req_opt, HL7Segments.description, HL7DataElements.data_structure ");
198 			sql.append("FROM HL7Versions RIGHT JOIN (HL7Segments INNER JOIN (HL7DataElements INNER JOIN HL7SegmentDataElements ");
199 			sql.append("ON (HL7DataElements.version_id = HL7SegmentDataElements.version_id) ");
200 			sql.append("AND (HL7DataElements.data_item = HL7SegmentDataElements.data_item)) ");
201 			sql.append("ON (HL7Segments.version_id = HL7SegmentDataElements.version_id) ");
202 			sql.append("AND (HL7Segments.seg_code = HL7SegmentDataElements.seg_code)) ");
203 			sql.append("ON (HL7Versions.version_id = HL7Segments.version_id) ");
204 			sql.append("WHERE ");
205 			sql.append("HL7SegmentDataElements.seg_code = '");
206 			sql.append(name);
207 			sql.append("' and HL7Versions.hl7_version = '");
208 			sql.append(version);
209 			sql.append("' ");
210 			sql.append("ORDER BY HL7SegmentDataElements.seg_code, HL7SegmentDataElements.seq_no;");
211 			//System.out.println(sql.toString());  //for debugging
212 			
213 			Statement stmt;
214 			ResultSet rs;
215 			try {
216 				stmt = conn.createStatement();
217 				rs = stmt.executeQuery(sql.toString());
218 			} catch (Exception e) {
219 				throw new MojoFailureException("Failed to execute the following SQL: " + sql.toString(), e);
220 			}
221 			List<String> usedFieldDescs = new ArrayList<>();
222 			int index = 0;
223 			while (rs.next()) {
224 				if (segDesc == null) {
225 					segDesc = rs.getString(9);
226 				}
227 				se = new SegmentElement(name, version, index++);
228 				se.field = rs.getInt(2);
229 				se.rep = rs.getString(3);
230 				se.repetitions = rs.getInt(4);
231 				if (se.repetitions == 0) {
232 					if (se.rep == null || !se.rep.equalsIgnoreCase("Y")) {
233 						se.repetitions = 1;
234 					}
235 				}
236 				se.desc = rs.getString(5);
237 
238 				// If two fields have the same name, add "Rep 1" or "Rep 2" etc to the name
239 				String originalSeDesc = se.desc;
240 				if (usedFieldDescs.contains(se.desc)) {
241 					se.desc = se.desc + " Number " + (Collections.frequency(usedFieldDescs, originalSeDesc) + 1);
242 				}
243 				usedFieldDescs.add(originalSeDesc);
244 
245 				se.length = rs.getInt(6);
246 				se.table = rs.getInt(7);
247 				se.opt = rs.getString(8);
248 				se.type = rs.getString(10);
249 				//shorten CE_x to CE
250 				if (se.type.startsWith("CE")) {
251 					se.type = "CE";
252 				}
253 
254 				// Fix problems
255 				if (se.type.equals("-") || se.type.equals("NUL")) {
256 					se.type = "NULLDT";
257 				}
258 
259 				/*
260 				 * ***
261 				 * index is 1-indexed here!!
262 				 * ***
263 				 */
264 				
265 				// 3454369
266 				if (version.equals("2.3") && name.equals("MRG") && index == 7) {
267 					se.type = "XPN";
268 				}
269 
270 				// https://sourceforge.net/p/hl7api/bugs/95/
271 				if (version.equals("2.3") && name.equals("ORC") && index == 14) {
272 					se.type = "XTN";
273 				}
274 
275 				// 2864817
276 				if (version.equals("2.3") && name.equals("PID") && index == 5) {
277 					se.rep = "Y";
278 					se.repetitions = -1;
279 				}
280 
281 				elements.add(se);
282 				/*System.out.println("Segment: " + name + " Field: " + se.field + " Rep: " + se.rep +
283 				" Repetitions: " + se.repetitions + " Desc: " + se.desc + " Length: " + se.length +
284 				" Table: " + se.table + " Segment Desc: " + segDesc);*/
285 			}
286 			stmt.close();
287 			normativeDatabase.returnConnection(conn);
288 		} catch (SQLException sqle) {
289 //			sqle.printStackTrace();
290 //			return;
291 			throw new MojoFailureException("Failed to generate segment", sqle);
292 		}
293 		
294 		String fileName = theTargetDir.toString() + "/" + name + "." + theFileExt;
295 		
296 		String basePackageName = DefaultModelClassFactory.getVersionPackageName(version);
297 		String[] datatypePackages = { basePackageName + "datatype" };
298         writeSegment(fileName, version, name, elements, segDesc, basePackageName, datatypePackages, theTemplatePackage);
299 
300 	}
301 
302 	private static void listTables(Connection conn, String tableName) throws MojoFailureException, SQLException {
303 		{
304 			StringBuilder sql = new StringBuilder();
305 			sql.append("SELECT ");
306 			sql.append(tableName).append(".* ");
307 			sql.append("FROM ").append(tableName).append(" ");
308 			//System.out.println(sql.toString());  //for debugging
309 			
310 			Statement stmt;
311 			ResultSet rs;
312 			try {
313 				stmt = conn.createStatement();
314 				rs = stmt.executeQuery(sql.toString());
315 			} catch (Exception e) {
316 				throw new MojoFailureException("Failed to execute the following SQL: " + sql.toString(), e);
317 			}
318 
319 			rs.next();
320 			Set<String> cols = new HashSet<>();
321 			for (int i = 0; i < rs.getMetaData().getColumnCount(); i++) {
322 				cols.add(rs.getMetaData().getColumnName(i+1));
323 			}
324 //			System.out.println();
325 //			System.out.println(tableName + "Cols: " + cols);
326 //			System.out.println();
327 			
328 			rs.close();
329 			stmt.close();
330 		}
331 		
332 	
333 	}
334 
335 
336 
337 	public static void writeSegment(String fileName, String version, String segmentName, ArrayList<SegmentElement> elements, String description, String basePackage, String[] datatypePackages, String theTemplatePackage) throws Exception {
338 		log.debug("Writing segment: {}", fileName);
339 		
340 		BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, false), SourceGenerator.ENCODING));
341 		
342         theTemplatePackage = theTemplatePackage.replace(".", "/");
343         Template template = VelocityFactory.getClasspathTemplateInstance(theTemplatePackage + "/segment.vsm");
344         VelocityContext ctx = new VelocityContext();
345         ctx.put("segmentName", segmentName);
346         ctx.put("typeDescription", description);
347         ctx.put("basePackageName", basePackage);
348         ctx.put("elements", elements);
349         ctx.put("datatypePackages", datatypePackages);
350         ctx.put("hl7VersionInQuotes", '"' + version + '"');
351         
352         template.merge(ctx, out);
353 		
354 //      String string = createSegmentString(version, segmentName, elements, description, basePackage, datatypePackageString);
355 //      out.write(string);
356 		
357 		out.flush();
358 		out.close();
359 	}
360 	
361 	
362 }