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 "XsdDataTypeGenerator.java".  Description:
10   "Create HAPI datatype model classes from XSD"
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.ArrayList;
33  import java.util.Arrays;
34  import java.util.Iterator;
35  import java.util.List;
36  
37  import ca.uhn.hl7v2.Version;
38  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
39  import ca.uhn.hl7v2.sourcegen.util.VelocityFactory;
40  import com.sun.xml.xsom.*;
41  import com.sun.xml.xsom.parser.XSOMParser;
42  import org.apache.velocity.Template;
43  import org.apache.velocity.VelocityContext;
44  import org.apache.velocity.context.Context;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * Create HAPI segment model classes from XML Schema files. Download the files from
50   * http://www.hl7.org/documentcenter/private/standards/V26/HL7-xml_v2.6_annotated.zip and
51   * http://www.hl7.org/documentcenter/public/standards/V2/Sun_HL7v2xsd.zip
52   * and unpack into src/main/resources/hl7v2xsd.
53   * <p/>
54   * This is an attempt to remove the need to have the HL7 database around because the
55   * schema files can be downloaded by any who has access to the HL7 standards.
56   */
57  public class XsdSegmentGenerator {
58  
59      private static final Logger LOG = LoggerFactory.getLogger(XsdSegmentGenerator.class);
60      private static final String[] EXCLUDE_SEGMENTS = {"anyHL7Segment", "anyZSegment"};
61  
62      public static final String URN_HL7_ORG_V2XML = "urn:hl7-org:v2xml";
63  
64      private final String templatePackage;
65      private final String targetDirectory;
66  
67      public XsdSegmentGenerator(String dir, String templatePackage) throws IOException {
68          File f = new File(dir);
69          if (!f.isDirectory())
70              throw new IOException("Can't create file in " + dir + " - it is not a directory.");
71          this.targetDirectory = dir;
72          this.templatePackage = templatePackage.replace(".", "/");
73      }
74  
75      public void parse(Version version) throws Exception {
76          XSOMParser parser = new XSOMParser();
77          String dir = String.format("/hl7v2xsd/%s/segments.xsd", version.getVersion());
78          URL url = getClass().getResource(dir);
79          parser.parse(url);
80          XSSchemaSet result = parser.getResult();
81          Iterator<XSSchema> iter = result.iterateSchema();
82          while (iter.hasNext()) {
83              XSSchema schema = iter.next();
84              if (URN_HL7_ORG_V2XML.equals(schema.getTargetNamespace())) {
85                  parseSegments(schema, version);
86              }
87          }
88      }
89  
90      //<xsd:complexType name="PD1.CONTENT">
91      //<xsd:sequence>
92      //<xsd:any namespace="##other" processContents="lax" minOccurs="0"/>
93      //</xsd:sequence>
94      //</xsd:complexType>
95      //<xsd:element name="PD1" type="PD1.CONTENT"/>
96  
97      private void parseSegments(XSSchema schema, Version version) throws Exception {
98  
99          Template template = VelocityFactory.getClasspathTemplateInstance(templatePackage + "/segment.vsm");
100         String basePackageName = DefaultModelClassFactory.getVersionPackageName(version.getVersion());
101         String[] datatypePackages = { basePackageName + "datatype" };
102 
103         Iterator<XSElementDecl> segmentDecls = schema.iterateElementDecls();
104         while (segmentDecls.hasNext()) {
105             XSElementDecl segmentDecl = segmentDecls.next();
106             String segmentName = segmentDecl.getName();
107             if (isRealSegment(segmentName)) {
108 
109                 List<SegmentElement> segmentsElements = new ArrayList<>();
110                 XSComplexType complexType = segmentDecl.getType().asComplexType();
111                 // Find and iterate over the fields of the segment
112                 XSParticle[] children = complexType
113                         .getContentType()
114                         .asParticle()
115                         .getTerm()
116                         .asModelGroup()
117                         .getChildren();
118                 LOG.debug("Creating segment {}, having {} children", segmentName, children.length);
119                 for (int i = 0; i < children.length; i++) {
120                     // Navigate to the attributes
121                     if (!children[i].getTerm().isWildcard()) {
122                         XSAttGroupDecl attrGroup = children[i]
123                                 .getTerm()
124                                 .asElementDecl()
125                                 .getType()
126                                 .asComplexType()
127                                 .getAttGroups().iterator().next();
128                         LOG.debug("Field {}", attrGroup.getName());
129                         String fieldType = attrGroup.getAttributeUse("", "Type").getFixedValue().toString();
130                         String fieldDescription = attrGroup.getAttributeUse("", "LongName").getFixedValue().toString();
131                         XSAttributeUse fieldLength = attrGroup.getAttributeUse("", "maxLength");
132                         int maxLength = 0;
133                         if (fieldLength != null)
134                             maxLength = Integer.parseInt(fieldLength.getFixedValue().toString());
135                         XSAttributeUse fieldTable = attrGroup.getAttributeUse("", "Table");
136                         int table = 0;
137                         if (fieldTable != null)
138                             table = Integer.parseInt(fieldTable.getFixedValue().toString().substring(3));
139                         SegmentElementement.html#SegmentElement">SegmentElement se = new SegmentElement(segmentName, version.getVersion(), i);
140                         se.field = i;
141                         if (children[i].getMaxOccurs().intValue() == XSParticle.UNBOUNDED) {
142                             se.rep = "Y";
143                             se.repetitions = 0;
144                         } else {
145                             se.rep = "N";
146                             se.repetitions = 1;
147                         }
148                         se.opt = children[i].getMinOccurs().intValue() > 0 ? "R" : "O";
149                         se.desc = fieldDescription;
150                         se.table = table;
151                         se.type = fieldType;
152                         fixType(i + 1, version, se);
153                         se.length = maxLength;
154                         segmentsElements.add(se);
155                     }
156                 }
157                 // need to add better segement description here, but nothing in XSD!
158                 writeSegment(template, basePackageName, datatypePackages,
159                         segmentName, segmentName, version, segmentsElements);
160             }
161 
162         }
163 
164     }
165 
166     private void writeSegment(Template template, String basePackageName, String[] datatypePackages,
167                               String segmentName, String segmentDescription, Version version,
168                               List<SegmentElement> segmentsElements) throws Exception {
169         String source = make(template, basePackageName, datatypePackages,
170                 segmentName, segmentDescription, version.getVersion(), segmentsElements);
171         if (source != null) {
172             writeFile(source, segmentName, version);
173         }
174     }
175 
176     /**
177      * This method can be used to circumvent errors in the XSDs without the need to
178      * modify the XDS.
179      *
180      * @param index
181      * @param version
182      * @param se
183      */
184     private static void fixType(int index, Version version, SegmentElement se) {
185         // Null/withdrawn
186         if (se.type.equals("NUL") || se.type.equals("-")) {
187             se.type = "NULLDT";
188 
189         // 3454369
190         } else if (version == Version.V23 && se.type.equals("MRG") && index == 7) {
191             se.type = "XPN";
192 
193         // https://sourceforge.net/p/hl7api/bugs/95/
194         } else if (version == Version.V23 && se.type.equals("ORC") && index == 14) {
195             se.type = "XTN";
196 
197         // 2864817
198         } else if (version == Version.V23 && se.type.equals("PID") && index == 5) {
199             se.rep = "Y";
200             se.repetitions = -1;
201         }
202     }
203 
204     private boolean isRealSegment(String dataTypeName) {
205         return Arrays.binarySearch(EXCLUDE_SEGMENTS, dataTypeName) < 0 &&
206                 dataTypeName.indexOf('.') < 0;
207     }
208 
209     private void writeFile(String source, String segmentName, Version version) throws IOException {
210         // TODO must be more robust
211         String dirName = String.format("%s/ca/uhn/hl7v2/model/%s/segment/", targetDirectory, version.getPackageVersion());
212         File dir = new File(dirName);
213         if (!dir.exists()) {
214             dir.mkdirs();
215         }
216         String targetFile = String.format("%s/%s.java", dirName, segmentName);
217 
218         try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(targetFile, false), StandardCharsets.UTF_8))) {
219             writer.write(source);
220             writer.flush();
221         }
222     }
223 
224     private String make(Template template, String normalBasePackageName, String[] datatypePackages,
225                         String segmentName, String description, String version, List<SegmentElement> elements) {
226         StringWriter out = new StringWriter();
227         Context ctx = new VelocityContext();
228         ctx.put("segmentName", segmentName);
229         ctx.put("typeDescription", description);
230         ctx.put("basePackageName", normalBasePackageName);
231         ctx.put("elements", elements);
232         ctx.put("datatypePackages", datatypePackages);
233         ctx.put("hl7VersionInQuotes", '"' + version + '"');
234 
235         template.merge(ctx, out);
236         return out.toString();
237     }
238 
239 
240     public static void main(String... args) {
241         try {
242             XsdSegmentGeneratortor.html#XsdSegmentGenerator">XsdSegmentGenerator xdtg = new XsdSegmentGenerator("C:/temp", "/ca.uhn.hl7v2.sourcegen.templates");
243             long start = System.currentTimeMillis();
244 //            for (Version version : Version.values()) {
245 //                System.out.println("Creating segments for " + version);
246 //                xdtg.parse(version);
247 //            }
248             xdtg.parse(Version.V25);
249             System.out.println("Done in " + (System.currentTimeMillis() - start) + " ms.");
250         } catch (Exception e) {
251             e.printStackTrace();
252         }
253     }
254 
255 }