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 "XsdGroupGenerator.java".  Description:
10   ""
11  
12   The Initial Developer of the Original Code is University Health Network. Copyright (C)
13   2013.  All Rights Reserved.
14  
15   Contributor(s): ______________________________________.
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.*;
30  import java.net.URL;
31  import java.nio.charset.StandardCharsets;
32  import java.util.*;
33  
34  import ca.uhn.hl7v2.Version;
35  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
36  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
37  import com.sun.xml.xsom.*;
38  import com.sun.xml.xsom.parser.XSOMParser;
39  import org.apache.velocity.Template;
40  import org.apache.velocity.VelocityContext;
41  import org.apache.velocity.context.Context;
42  import org.slf4j.Logger;
43  import org.slf4j.LoggerFactory;
44  
45  /**
46   * Create HAPI message classes from XML Schema files. Download the files from
47   * http://www.hl7.org/documentcenter/private/standards/V26/HL7-xml_v2.6_annotated.zip and
48   * http://www.hl7.org/documentcenter/public/standards/V2/Sun_HL7v2xsd.zip
49   * and unpack into src/main/resources/hl7v2xsd.
50   * <p/>
51   * This is an attempt to remove the need to have the HL7 database around because the
52   * schema files can be downloaded by any who has access to the HL7 standards.
53   */
54  public class XsdMessageGenerator {
55  
56      private static final Logger LOG = LoggerFactory.getLogger(XsdMessageGenerator.class);
57      public static final String URN_HL7_ORG_V2XML = "urn:hl7-org:v2xml";
58  
59      private final String templatePackage;
60      private final String targetDirectory;
61      private Template messageTemplate;
62      private Template groupTemplate;
63  
64  
65      public XsdMessageGenerator(String dir, String templatePackage) throws IOException {
66          File f = new File(dir);
67          if (!f.isDirectory())
68              throw new IOException("Can't create file in " + dir + " - it is not a directory.");
69          this.targetDirectory = dir;
70          this.templatePackage = templatePackage.replace(".", "/");
71      }
72  
73      public void parse(Version version) throws Exception {
74          messageTemplate = VelocityFactory.getClasspathTemplateInstance(templatePackage + "/messages.vsm");
75          groupTemplate = VelocityFactory.getClasspathTemplateInstance(templatePackage + "/group.vsm");
76          XSOMParser parser = new XSOMParser();
77          String dir = String.format("/hl7v2xsd/%s/messages.xsd", version.getVersion());
78          URL url = getClass().getResource(dir);
79          parser.setErrorHandler(new SimpleErrorHandler());
80          parser.parse(url);
81          XSSchemaSet result = parser.getResult();
82          Iterator<XSSchema> iter = result.iterateSchema();
83          while (iter.hasNext()) {
84              XSSchema schema = iter.next();
85              if (URN_HL7_ORG_V2XML.equals(schema.getTargetNamespace())) {
86                  parseMessages(schema, version);
87              }
88          }
89      }
90  
91  
92      /**
93       * Message (Structures) and Groups are both contained within the message structure files included from
94       * messages.xsd, and although they are structured very similarly they e.g. inherit from different base
95       * classes.
96       *
97       * @param schema
98       * @param version
99       * @throws Exception
100      */
101     private void parseMessages(XSSchema schema, Version version) throws Exception {
102 
103         String basePackageName = DefaultModelClassFactory.getVersionPackageName(version.getVersion());
104 
105         XSModelGroupDecl allMessages = schema.getModelGroupDecl("ALLMESSAGES.CONTENT");
106         XSParticle[] messages = allMessages.getModelGroup().getChildren();
107         for (XSParticle message : messages) {
108             XSElementDecl messageElement = message.getTerm().asElementDecl();
109             GroupDef messageDef = parseGroupOrMessage(messageElement.getName(), message, true, version, basePackageName, new HashMap<>());
110             String messageClass = makeMessage(messageDef, basePackageName, version.getVersion(), null);
111             writeFile(messageClass, "message", messageElement.getName(), version);
112         }
113     }
114 
115     private GroupDef parseGroupOrMessage(String messageName, XSParticle groupParticle,
116                                              boolean isMessage, Version version,
117                                              String basePackageName,
118                                              Map<String, StructureDef> structures) throws Exception {
119         XSElementDecl groupDecl = groupParticle.getTerm().asElementDecl();
120         XSComplexType complexType = groupDecl.getType().asComplexType();
121         // Find the structures of the group
122         XSParticle[] children = complexType
123                 .getContentType()
124                 .asParticle()
125                 .getTerm()
126                 .asModelGroup()
127                 .getChildren();
128         LOG.debug("found {}, having {} children", groupDecl.getName(), children.length);
129 
130         GroupDefpDef.html#GroupDef">GroupDef def = new GroupDef(
131                 messageName,
132                 // chop off message name from beginning of group
133                 messageName.equals(groupDecl.getName()) ?
134                         messageName :
135                         groupDecl.getName().substring(messageName.length() + 1),
136                 groupParticle.getMinOccurs().intValue() > 0,
137                 groupParticle.isRepeated(),
138                 "a Group object");
139         structures.put(groupDecl.getName(), def);
140 
141         // Collect the substructures of the group
142         for (XSParticle child : children) {
143             XSElementDecl childDecl = child.getTerm().asElementDecl();
144 
145             // The type starts with the message structure name, if the structure is a group,
146             if (childDecl.getName().startsWith(messageName)) {
147 
148                 // Groups can be recursive, so watch out!
149                 if (structures.containsKey(childDecl.getName())) {
150                     LOG.debug("{} has already been seen here", childDecl.getName());
151                     def.addStructure(structures.get(childDecl.getName()));
152                 } else {
153                     def.addStructure(parseGroupOrMessage(messageName, child, false, version, basePackageName, structures));
154                 }
155             } else {
156                 // the structure is a segment
157                 def.addStructure(new SegmentDef(childDecl.getName(),
158                         null,
159                         child.getMinOccurs().intValue() > 0,
160                         child.isRepeated(),
161                         false,
162                         // need to add better description here, but nothing is in XSD!
163                         childDecl.getName()));
164             }
165         }
166 
167         if (!isMessage) {
168             String groupClass = makeGroup(def, basePackageName, version.getVersion());
169             writeFile(groupClass, "groups", def.getName(), version);
170         }
171         return def;
172     }
173 
174     private void writeFile(String source, String type, String name, Version version) throws IOException {
175         // TODO must be more robust
176         String dirName = String.format("%s/ca/uhn/hl7v2/model/%s/%s/", targetDirectory, version.getPackageVersion(), type);
177         File dir = new File(dirName);
178         if (!dir.exists()) {
179             dir.mkdirs();
180         }
181         String targetFile = String.format("%s/%s.java", dirName, name);
182 
183         try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile, false), StandardCharsets.UTF_8))) {
184             writer.write(source);
185             writer.flush();
186         }
187     }
188 
189     private String makeMessage(GroupDef def, String normalBasePackageName,
190                         String version, List<String> structureNameToChildNames) {
191         StringWriter out = new StringWriter();
192         Context ctx = new VelocityContext();
193         ctx.put("message", def.getRawGroupName());
194         ctx.put("specVersion", version);
195         ctx.put("chapter", ""); // missing in XSDs
196         ctx.put("haveGroups", hasGroups(def));
197         ctx.put("basePackageName", normalBasePackageName);
198         ctx.put("segments", Arrays.asList(def.getStructures()));
199         ctx.put("structureNameToChildNames", structureNameToChildNames);
200         ctx.put("HASH", "#");
201 
202         messageTemplate.merge(ctx, out);
203         return out.toString();
204     }
205 
206     private String makeGroup(GroupDef def, String normalBasePackageName, String version) {
207         StringWriter out = new StringWriter();
208         Context ctx = new VelocityContext();
209         ctx.put("groupName", def.getName());
210         ctx.put("specVersion", version);
211         ctx.put("typeDescription", "a Group object");
212         ctx.put("basePackageName", normalBasePackageName);
213         ctx.put("groups", Arrays.asList(def.getStructures()));
214         ctx.put("chapter", "");
215 
216         groupTemplate.merge(ctx, out);
217         return out.toString();
218     }
219 
220 
221     private static boolean hasGroups(GroupDef def) {
222         for (StructureDef structure : def.getStructures()) {
223             if (structure.isGroup()) return true;
224         }
225         return false;
226     }
227 
228     public static void main(String... args) {
229         try {
230             XsdMessageGeneratortor.html#XsdMessageGenerator">XsdMessageGenerator xdtg = new XsdMessageGenerator("C:/temp", "/ca.uhn.hl7v2.sourcegen.templates");
231             long start = System.currentTimeMillis();
232 //            for (Version version : Version.values()) {
233 //                System.out.println("Creating messages and groups for " + version);
234 //                xdtg.parse(version);
235 //            }
236             xdtg.parse(Version.V25);
237             System.out.println("Done in " + (System.currentTimeMillis() - start) + " ms.");
238         } catch (Exception e) {
239             e.printStackTrace();
240         }
241     }
242 
243 
244 }