View Javadoc
1   /*
2    * To change this template, choose Tools | Templates
3    * and open the template in the editor.
4    */
5   
6   package ca.uhn.hl7v2.sourcegen.conf;
7   
8   import java.io.BufferedWriter;
9   import java.io.File;
10  import java.io.FileOutputStream;
11  import java.io.OutputStreamWriter;
12  import java.util.ArrayList;
13  import java.util.Collections;
14  import java.util.HashMap;
15  import java.util.HashSet;
16  import java.util.Map;
17  import java.util.Set;
18  import java.util.regex.Matcher;
19  import java.util.regex.Pattern;
20  
21  import org.apache.commons.io.FileUtils;
22  import org.apache.commons.lang.StringUtils;
23  import org.apache.velocity.Template;
24  import org.apache.velocity.VelocityContext;
25  import org.apache.velocity.context.Context;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  import ca.uhn.hl7v2.conf.parser.ProfileParser;
30  import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
31  import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
32  import ca.uhn.hl7v2.conf.spec.message.Component;
33  import ca.uhn.hl7v2.conf.spec.message.Field;
34  import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
35  import ca.uhn.hl7v2.conf.spec.message.Seg;
36  import ca.uhn.hl7v2.conf.spec.message.SegGroup;
37  import ca.uhn.hl7v2.conf.spec.message.StaticDef;
38  import ca.uhn.hl7v2.conf.spec.message.SubComponent;
39  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
40  import ca.uhn.hl7v2.sourcegen.DataTypeGenerator;
41  import ca.uhn.hl7v2.sourcegen.DatatypeComponentDef;
42  import ca.uhn.hl7v2.sourcegen.DatatypeDef;
43  import ca.uhn.hl7v2.sourcegen.GroupDef;
44  import ca.uhn.hl7v2.sourcegen.GroupGenerator;
45  import ca.uhn.hl7v2.sourcegen.MessageGenerator;
46  import ca.uhn.hl7v2.sourcegen.SegmentDef;
47  import ca.uhn.hl7v2.sourcegen.SegmentElement;
48  import ca.uhn.hl7v2.sourcegen.SegmentGenerator;
49  import ca.uhn.hl7v2.sourcegen.SourceGenerator;
50  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
51  
52  /**
53   * Generates HAPI custom model classes using HL7 conformance profiles
54   */
55  public class ProfileSourceGenerator {
56  
57      private static final Logger ourLog = LoggerFactory.getLogger(ProfileSourceGenerator.class);
58  
59      private final RuntimeProfile myProfile;
60      private String myTargetDirectory;
61      private String myBasePackage;
62      private String myMessageName;
63      private final Set<String> mySegmentDefNames;
64      private final Set<String> myGroupDefNames;
65      private final ArrayList<SegmentDef> mySegmentDefs;
66      private final Map<String, ArrayList<SegmentElement>> mySegmentNameToSegmentElements;
67      private final ArrayList<GroupDef> myGroupDefs;
68      private final GenerateDataTypesEnum myGenerateDataTypes;
69      private final String myTemplatePackage;
70      private final String myFileExt;
71  
72      public ProfileSourceGenerator(RuntimeProfile theProfile, String theTargetDirectory, String theBasePackage, GenerateDataTypesEnum theGenDt, String theTemplatePackage,
73              String theFileExt) {
74          myProfile = theProfile;
75          myGenerateDataTypes = theGenDt;
76          myTemplatePackage = theTemplatePackage;
77          myFileExt = theFileExt;
78  
79          myTargetDirectory = theTargetDirectory;
80          if (!myTargetDirectory.endsWith("/")) {
81              myTargetDirectory += "/";
82          }
83  
84          myBasePackage = theBasePackage;
85          if (!myBasePackage.endsWith(".")) {
86              myBasePackage += ".";
87          }
88  
89          myTargetDirectory += myBasePackage.replaceAll("\\.", "/");
90  
91          mySegmentDefs = new ArrayList<>();
92          myGroupDefs = new ArrayList<>();
93          mySegmentNameToSegmentElements = new HashMap<>();
94          mySegmentDefNames = new HashSet<>();
95          myGroupDefNames = new HashSet<>();
96      }
97  
98      public void generate() throws Exception {
99          StaticDef staticDef = myProfile.getMessage();
100         myMessageName = staticDef.getMsgStructID();
101 
102         SourceGenerator.makeDirectory(myTargetDirectory + "/message");
103         SourceGenerator.makeDirectory(myTargetDirectory + "/segment");
104         SourceGenerator.makeDirectory(myTargetDirectory + "/group");
105 
106         String chapter = "";
107         String version = myProfile.getHL7Version();
108         GroupDef group = convertToGroupDef(staticDef, version);
109 
110         String basePackageName = DefaultModelClassFactory.getVersionPackageName(version);
111         String[] datatypePackages;
112 
113         switch (myGenerateDataTypes) {
114         default:
115         case NONE:
116             datatypePackages = new String[] { basePackageName + "datatype" };
117             break;
118         case SINGLE:
119             datatypePackages = new String[] { myBasePackage + "datatype" };
120             SourceGenerator.makeDirectory(myTargetDirectory + "/datatype");
121         }
122 
123         boolean haveGroups = myGroupDefs.size() > 0;
124 
125         // Write Message
126         {
127             String parent = myTargetDirectory + "message/";
128             FileUtils.forceMkdir(new File(parent));
129 			String fileName = parent + staticDef.getMsgStructID() + "." + myFileExt;
130             ourLog.debug("Writing Message file: " + fileName);
131             MessageGenerator.writeMessage(fileName, group.getStructures(), myMessageName, chapter, version, myBasePackage, haveGroups, myTemplatePackage, null);
132         }
133 
134         for (GroupDef next : myGroupDefs) {
135             String parent = myTargetDirectory + "group/";
136             FileUtils.forceMkdir(new File(parent));
137             String fileName = parent + next.getName() + "." + myFileExt;
138             ourLog.debug("Writing Group file: " + fileName);
139             GroupGenerator.writeGroup(next.getName(), fileName, next, version, myBasePackage, myTemplatePackage, next.getDescription());
140         }
141 
142         // Write Segments
143         Set<String> alreadyWrittenDatatypes = new HashSet<>();
144         Set<String> alreadyWrittenSegments = new HashSet<>();
145         for (SegmentDef next : mySegmentDefs) {
146             alreadyWrittenSegments.add(next.getName());
147 
148             String parent = myTargetDirectory + "segment/";
149             FileUtils.forceMkdir(new File(parent));
150             String fileName = parent + next.getName() + "." + myFileExt;
151             ourLog.debug("Writing Segment file: " + fileName);
152             String segmentName = next.getName();
153             String description = next.getDescription();
154             ArrayList<SegmentElement> elements = mySegmentNameToSegmentElements.get(segmentName);
155 
156             for (SegmentElement nextElement : elements) {
157                 if ("*".equals(nextElement.type) || "VARIES".equals(nextElement.type)) {
158                     nextElement.type = "Varies";
159                 }
160             }
161 
162             SegmentGenerator.writeSegment(fileName, version, segmentName, elements, description, myBasePackage, datatypePackages, myTemplatePackage);
163 
164             if (myGenerateDataTypes == GenerateDataTypesEnum.SINGLE) {
165                 for (DatatypeDef nextFieldDef : next.getFieldDefs()) {
166                     writeDatatype(nextFieldDef, alreadyWrittenDatatypes, version);
167                 }
168             }
169 
170         }
171 
172         if ("json".equals(myFileExt)) {
173             String fileName = myTargetDirectory + "/structures." + myFileExt;
174             ourLog.debug("Writing Structures file: " + fileName);
175             BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fileName, false), SourceGenerator.ENCODING));
176             String templatePackage = myTemplatePackage.replace(".", "/");
177             Template template = VelocityFactory.getClasspathTemplateInstance(templatePackage + "/available_structures.vsm");
178             Context ctx = new VelocityContext();
179             ctx.put("messages", Collections.singletonList(myMessageName));
180             ctx.put("segments", alreadyWrittenSegments);
181             ctx.put("datatypes", alreadyWrittenDatatypes);
182 
183             template.merge(ctx, out);
184 
185             out.flush();
186             out.close();
187         }
188 
189     }
190 
191     private void writeDatatype(DatatypeDef theFieldDef, Set<String> theAlreadyWrittenDatatypes, String version) throws Exception {
192         if (theAlreadyWrittenDatatypes != null) {
193             if (theAlreadyWrittenDatatypes.contains(theFieldDef.getType())) {
194                 return;
195             } else {
196                 theAlreadyWrittenDatatypes.add(theFieldDef.getType());
197             }
198         }
199 
200         String parent = myTargetDirectory + "datatype/";
201         FileUtils.forceMkdir(new File(parent));
202         String fileName = parent + theFieldDef.getType() + "." + myFileExt;
203         DataTypeGenerator.writeDatatype(fileName, version, theFieldDef, myBasePackage, myTemplatePackage);
204 
205         for (DatatypeDef next : theFieldDef.getSubComponentDefs()) {
206             writeDatatype(next, theAlreadyWrittenDatatypes, version);
207         }
208     }
209 
210     private GroupDef convertToGroupDef(StaticDef staticDef, String theVersion) {
211 
212         boolean required = true;
213         boolean repeating = true;
214         String description = staticDef.getDescription();
215         GroupDefpDef.html#GroupDef">GroupDef retVal = new GroupDef(myMessageName, null, required, repeating, description);
216 
217         populateChildren(retVal, staticDef, theVersion);
218 
219         return retVal;
220     }
221 
222     private GroupDef./../ca/uhn/hl7v2/sourcegen/GroupDef.html#GroupDef">GroupDef convertToGroupDef(GroupDef theParent, SegGroup segGroup, String theVersion) {
223 
224         /*
225          * MWB exports optional repeating groups as an optional group with a
226          * single child which is a required repeating group If we encounter
227          * this, we flatten this to be an optional repeating group
228          */
229         boolean required = true;
230         if (segGroup.getChildren() == 1 && segGroup.getChild(1) instanceof SegGroup) {
231             required = segGroup.getMin() > 0;
232             segGroup = (SegGroup) segGroup.getChild(1);
233         }
234 
235         String name = segGroup.getName();
236         required = required && (segGroup.getMin() > 0);
237         boolean repeating = segGroup.getMax() != 1;
238         String description = segGroup.getLongName();
239 
240         GroupDefpDef.html#GroupDef">GroupDef retVal = new GroupDef(myMessageName, name, required, repeating, description);
241 
242         populateChildren(retVal, segGroup, theVersion);
243 
244         if (!myGroupDefNames.contains(name)) {
245             myGroupDefNames.add(name);
246             myGroupDefs.add(retVal);
247         }
248 
249         return retVal;
250     }
251 
252     private void populateChildren(GroupDef retVal, AbstractSegmentContainer segGroup, String theVersion) {
253 
254         for (int i = 0; i < segGroup.getChildren(); i++) {
255             ProfileStructure nextProfileStructure = segGroup.getChild(i + 1);
256 
257             if (nextProfileStructure instanceof SegGroup) {
258 
259                 SegGroup/../../ca/uhn/hl7v2/conf/spec/message/SegGroup.html#SegGroup">SegGroup nextSegGroup = (SegGroup) nextProfileStructure;
260                 GroupDef nextGroupDef = convertToGroupDef(retVal, nextSegGroup, theVersion);
261                 retVal.addStructure(nextGroupDef);
262 
263             } else if (nextProfileStructure instanceof Seg) {
264 
265                 Seg="../../../../../ca/uhn/hl7v2/conf/spec/message/Seg.html#Seg">Seg nextSeg = (Seg) nextProfileStructure;
266                 SegmentDef nextSegmentDef = convertToSegmentDef(retVal, nextSeg, theVersion);
267                 retVal.addStructure(nextSegmentDef);
268 
269             } else {
270 
271                 throw new IllegalStateException("Unknown profile structure: " + nextProfileStructure);
272 
273             } // if-else
274 
275         } // for
276 
277     }
278 
279     private SegmentDef convertToSegmentDef(GroupDef theParent, Seg nextSeg, String theVersion) {
280         String name = nextSeg.getName();
281         String groupName = null;
282         boolean required = nextSeg.getMin() > 0;
283         boolean repeating = nextSeg.getMax() > 1 || nextSeg.getMax() == -1;
284         String description = nextSeg.getLongName();
285 
286         SegmentDeftDef.html#SegmentDef">SegmentDef retVal = new SegmentDef(name, groupName, required, repeating, false, description);
287         ArrayList<SegmentElement> segmentElements = new ArrayList<>();
288 
289         // Extract fields from the segment definition
290         for (int i = 0; i < nextSeg.getFields(); i++) {
291             Field nextField = nextSeg.getField(i + 1);
292 
293             DatatypeDef nextFieldDef = convertToDatatypeDef(nextField);
294             retVal.addFieldDef(nextFieldDef);
295 
296             SegmentElementgmentElement">SegmentElement nextSegmentElement = new SegmentElement(name, theVersion, i);
297 
298             nextSegmentElement.desc = nextField.getName();
299             nextSegmentElement.field = i + 1;
300             nextSegmentElement.length = (int) nextField.getLength();
301             nextSegmentElement.opt = nextField.getUsage();
302             nextSegmentElement.rep = (nextField.getMax() != 1) ? "Y" : "N";
303             nextSegmentElement.repetitions = nextField.getMax();
304             nextSegmentElement.type = nextField.getDatatype();
305 
306             if (nextSegmentElement.type.startsWith("CM_")) {
307                 nextSegmentElement.type = nextSegmentElement.type.substring(3);
308             }
309 
310             String table = nextField.getTable();
311             if (table != null && table.length() > 0) {
312                 extractTableInfo(nextSegmentElement, table);
313             }
314 
315             segmentElements.add(nextSegmentElement);
316         }
317 
318         if (!mySegmentDefNames.contains(name)) {
319             mySegmentDefNames.add(name);
320             mySegmentDefs.add(retVal);
321             mySegmentNameToSegmentElements.put(name, segmentElements);
322         }
323 
324         return retVal;
325     }
326 
327 	static void extractTableInfo(SegmentElement nextSegmentElement, String table) {
328 		Pattern p = Pattern.compile("^([a-zA-Z]+)([0-9]+)$");
329 		Matcher m = p.matcher(table);
330 		if (m.find()) {
331             nextSegmentElement.tableNamespace = m.group(1);
332 			
333 			String tableNum = m.group(2);
334 			nextSegmentElement.table = Integer.parseInt(tableNum);
335 			
336 			String alternateType = nextSegmentElement.getAlternateType();
337 			if ("ID".equals(alternateType)) {
338 				nextSegmentElement.setAlternateType("ca.uhn.hl7v2.model.primitive.IDWithNamespace");
339 			} else if ("IS".equals(alternateType)) {
340 				nextSegmentElement.setAlternateType("ca.uhn.hl7v2.model.primitive.IDWithNamespace");
341 			}
342 			
343 		} else {
344 			try {
345 		    	nextSegmentElement.table = Integer.parseInt(table);
346 		    } catch (NumberFormatException e) {
347 		    	ourLog.warn("Unable to parse number out of table name: \"" + table +"\" for field \"" + nextSegmentElement.desc + "\". Ignoring this value and setting table number to 0.");
348 		    }
349 		}
350 	}
351 
352     private DatatypeDef convertToDatatypeDef(Field theField) {
353         String type = theField.getDatatype();
354         String name = theField.getName();
355 
356         DatatypeDefeDef.html#DatatypeDef">DatatypeDef retVal = new DatatypeDef(type, name);
357 
358         for (int i = 0; i < theField.getComponents(); i++) {
359             Component nextComponent = theField.getComponent(i + 1);
360             DatatypeComponentDef nextDef = convertToDatatypeComponentDef(nextComponent, type, i);
361             retVal.addSubcomponentDef(nextDef);
362         }
363 
364         return retVal;
365     }
366 
367     private DatatypeComponentDef convertToDatatypeComponentDef(Component theComponent, String parentType, int indexWithinParent) {
368 
369         String type = theComponent.getDatatype();
370         String name = theComponent.getName();
371 
372         int table = 0;
373         if (StringUtils.isNotBlank(theComponent.getTable())) {
374             try {
375                 table = Integer.parseInt(theComponent.getTable());
376             } catch (NumberFormatException e) {
377                 // TODO: handle this somehow?
378             }
379         }
380 
381         DatatypeComponentDeftDef.html#DatatypeComponentDef">DatatypeComponentDef retVal = new DatatypeComponentDef(parentType, indexWithinParent, type, name, table);
382 
383         for (int i = 0; i < theComponent.getSubComponents(); i++) {
384             SubComponent next = theComponent.getSubComponent(i + 1);
385             DatatypeComponentDef def = convertToDatatypeComponentDef(next, type, i);
386             retVal.addSubcomponentDef(def);
387         }
388 
389         return retVal;
390     }
391 
392     private DatatypeComponentDef convertToDatatypeComponentDef(SubComponent theComponent, String parentType, int indexWithinParent) {
393 
394         String type = theComponent.getDatatype();
395         String desc = theComponent.getName();
396 
397         int table = 0;
398         if (StringUtils.isNotBlank(theComponent.getTable())) {
399             try {
400                 table = Integer.parseInt(theComponent.getTable());
401             } catch (NumberFormatException e) {
402                 // TODO: handle this somehow?
403             }
404         }
405 
406         return new DatatypeComponentDef(parentType, indexWithinParent, type, desc, table);
407     }
408 
409     public static void main(String[] args) throws Exception {
410         RuntimeProfile rp = new ProfileParser(false).parseClasspath("ca/uhn/hl7v2/conf/parser/ADT_A01.xml");
411         new ProfileSourceGenerator(rp, "hapi-test/target/generated-sources/confgen", "hapi.on.olis", GenerateDataTypesEnum.SINGLE, "ca.uhn.hl7v2.sourcegen.templates.json", "json")
412                 .generate();
413     }
414 }