View Javadoc
1   package ca.uhn.hl7v2.mvnplugin;
2   
3   import java.io.File;
4   import java.io.FileReader;
5   import java.io.FileWriter;
6   import java.io.StringWriter;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.HashMap;
10  import java.util.HashSet;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  import java.util.TreeMap;
15  
16  import org.apache.commons.lang.StringUtils;
17  import org.apache.maven.model.Resource;
18  import org.apache.maven.plugin.AbstractMojo;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.apache.maven.plugin.MojoFailureException;
21  import org.apache.maven.project.MavenProject;
22  import org.apache.velocity.Template;
23  import org.apache.velocity.VelocityContext;
24  import org.apache.velocity.tools.generic.EscapeTool;
25  import org.codehaus.plexus.util.IOUtil;
26  
27  import ca.uhn.hl7v2.VersionLogger;
28  import ca.uhn.hl7v2.conf.ProfileException;
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.sourcegen.util.VelocityFactory;
40  import ca.uhn.hl7v2.util.XMLUtils;
41  
42  /**
43   * The XsdConfGen tool takes an HL7 conformance profile and creates an XSD
44   * schema which matches the XML encoding for messages meeting that conformance
45   * profile. In addition, it is able to combine multiple profiles into a single
46   * schema.
47   * 
48   * See the <a href="./xsdconfgen-usage.html">usage page</a> for information on
49   * how to use this plugin.
50   * 
51   * This plugin was contributed as a part of the <a
52   * href="http://conftest.connectinggta.ca/">ConnectingGTA</a> project.
53   * 
54   * @author This plugin was contributed as a part of the <a
55   *         href="http://conftest.connectinggta.ca/">ConnectingGTA</a> project
56   * @goal xsdconfgen
57   * @phase generate-sources
58   * @requiresDependencyResolution runtime
59   * @requiresProject
60   * @inheritedByDefault false
61  	 * @since 2.1
62   */
63  public class XsdConfGenMojo extends AbstractMojo {
64  
65  	private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XsdConfGenMojo.class);
66  
67  	/**
68  	 * One or more XML conformance profiles. If more than one is used, the
69  	 * contents will be combined. Note that this has the effect of merging the
70  	 * profiles in a specific way: When an element is found in a profile for the
71  	 * first time (e.g. a definition for the PID segment), that element is
72  	 * generated into the XSD. If the same element is found in a subsequent
73  	 * profile, it is ignored and the definition from the first profile is used.
74  	 * <p>
75  	 * This is desirable if you want to process multiple message types in
76  	 * certain toolsets and reuse modules for processing some structures.
77  	 * </p><p>
78  	 * If completely separate definitions are desired, this can be accomplished
79  	 * by configuring the xsdconfgen plugin to have multiple executions and to
80  	 * use only a single conformance profile for each execution.
81  	 * </p>
82  	 * 
83  	 * @parameter
84  	 * @required
85  	 * @since 2.1
86  	 */
87  	private List<String> profiles;
88  
89  	/**
90  	 * The maven project.
91  	 * 
92  	 * @parameter expression="${project}"
93  	 * @required
94  	 * @since 2.1
95  	 * @readonly
96  	 */
97  	private MavenProject project;
98  
99  	/**
100 	 * The target directory for the generated source
101 	 * 
102 	 * @parameter
103 	 * @required
104 	 * @since 2.1
105 	 */
106 	private String targetDirectory;
107 
108 	/**
109 	 * The Message Workbench tool generally creates segment groups as two level
110 	 * structures, with an outer group which has only a single child which is
111 	 * the actual group. If this is set to true (which is the default), these
112 	 * "bogus" groups are filtered.
113 	 * 
114 	 * @parameter default="true"
115 	 * @since 2.1
116 	 */
117 	private final boolean filterBogusGroups = true;
118 
119 	/**
120 	 * The file name for the generated file (file will be placed in the
121 	 * targetDirectory)
122 	 * 
123 	 * @parameter
124 	 * @required
125 	 * @since 2.1
126 	 */
127 	private String targetFile;
128 
129 	/**
130 	 * If set to <code>true</code> (default is <code>false</code>), any elements
131 	 * which are defined to have a usage of "X" (not supported) will not be
132 	 * generated as elements in the schema.
133 	 * 
134 	 * @parameter default="false"
135 	 * @since 2.1
136 	 */
137 	private boolean constrain = false;
138 
139 	/**
140 	 * If provided, created additional message types in the schema and links
141 	 * them to a structure provided by any of the conformance profiles defined
142 	 * by the &lt;profiles&gt; configuration element. For example, if you have a
143 	 * conformance profile which defines a structure called ADT_A01, you may use
144 	 * this parameter to create an additional message of ADT_A02 which also uses
145 	 * the ADT_A01 structure. This is useful when you want to have a single
146 	 * structure to use for a number of message types.
147 	 * 
148 	 * @parameter
149 	 * @since 2.1
150 	 */
151 	private Map<String, String> linkTriggerToStructure;
152 
153 	private String templatePackage = "ca.uhn.hl7v2.sourcegen.templates.xsd";
154 
155 	private static final Set<String> ourConstrainedUsageTypes = new HashSet<>(new ArrayList<>(Arrays.asList("R", "RE", "O", "C")));
156 
157 	/**
158 	 * {@inheritDoc}
159 	 */
160 	public void execute() throws MojoExecutionException {
161 
162 		try {
163 
164 			if (!new File(targetDirectory).exists()) {
165 				ourLog.info("Creating directory: {}", targetDirectory);
166 				new File(targetDirectory).mkdirs();
167 			}
168 
169 			List<RuntimeProfile> parsedProfiles = new ArrayList<>();
170 			for (String nextProfile : profiles) {
171 
172 				ourLog.info("Reading profile: {}", nextProfile);
173 				FileReader reader = new FileReader(nextProfile);
174 				String profileString = IOUtil.toString(reader);
175 
176 				ProfileParser profileParser = new ProfileParser(false);
177 				RuntimeProfile runtimeProfile = profileParser.parse(profileString);
178 
179 				if (constrain) {
180 					for (int i = 0; i < runtimeProfile.getMessage().getChildrenAsList().size(); i++) {
181 						ProfileStructure next = runtimeProfile.getMessage().getChildrenAsList().get(i);
182 						if (!ourConstrainedUsageTypes.contains(next.getUsage())) {
183 							runtimeProfile.getMessage().getChildrenAsList().remove(i);
184 							i--;
185 						} else {
186 							if (next instanceof Seg) {
187 								constrainSeg((Seg) next);
188 							} else {
189 								constrainSegGroup((SegGroup) next);
190 							}
191 						}
192 					}
193 				}
194 
195 				if (filterBogusGroups) {
196 					StaticDef staticDef = runtimeProfile.getMessage();
197 					filterBogusGroups(staticDef);
198 				}
199 
200 				parsedProfiles.add(runtimeProfile);
201 			}
202 
203 			if (linkTriggerToStructure == null) {
204 				linkTriggerToStructure = new HashMap<>();
205 			}
206 
207 			linkTriggerToStructure = new TreeMap<>(linkTriggerToStructure);
208 
209 			VelocityContext ctx = new VelocityContext();
210 			ctx.put("runtimeProfiles", parsedProfiles);
211 			ctx.put("hapiVersion", StringUtils.defaultString(VersionLogger.getVersion(), "X.X"));
212 			ctx.put("segmentDefs", new HashSet<String>());
213 			ctx.put("fieldDefs", new HashSet<String>());
214 			ctx.put("esc", new EscapeTool());
215 			ctx.put("compositeFieldDefs", new HashSet<String>());
216 			ctx.put("triggerMappings", linkTriggerToStructure);
217 
218 			Template template = VelocityFactory.getClasspathTemplateInstance(templatePackage.replace('.', '/') + "/message_xml_its.vm");
219 
220 			File targetFileDef = new File(targetDirectory, targetFile);
221 			ourLog.info("Generating: {}", targetFileDef.getAbsolutePath());
222 
223 			StringWriter sw = new StringWriter();
224 			template.merge(ctx, sw);
225 			sw.close();
226 
227 			// FileWriter w3 = new FileWriter(targetFileDef, false);
228 			// w3.write(sw.toString());
229 			// w3.close();
230 
231 			if (ourLog.isDebugEnabled()) {
232 				ourLog.debug("DOcument: " + sw.toString());
233 			}
234 
235 			String xml = XMLUtils.serialize(XMLUtils.parse(sw.toString()), true);
236 
237 			FileWriter w = new FileWriter(targetFileDef, false);
238 			w.write(xml);
239 			w.close();
240 
241 		} catch (Exception e) {
242 			throw new MojoExecutionException(e.getMessage(), e);
243 		}
244 
245 		if (project != null) {
246 			Resource resource = new Resource();
247 			resource.setDirectory(targetDirectory);
248 			project.addResource(resource);
249 //			project.addCompileSourceRoot(targetDirectory);
250 		}
251 
252 	}
253 
254 	private void filterBogusGroups(AbstractSegmentContainer theParent) {
255 		List<ProfileStructure> children = theParent.getChildrenAsList();
256 
257 		for (int childIndex = 0; childIndex < children.size(); childIndex++) {
258 			ProfileStructure nextChild = children.get(childIndex);
259 			if (nextChild instanceof SegGroup) {
260 				SegGroup nextChildSg = (SegGroup) nextChild;
261 				if (nextChildSg.getChildren() == 1 && nextChildSg.getChild(1) instanceof SegGroup) {
262 					SegGroup newNextChildSg = (SegGroup) nextChildSg.getChild(1);
263 					ourLog.info("Replacing bogus group {} with {}", newNextChildSg.toString(), nextChildSg.toString());
264 					nextChildSg = newNextChildSg;
265 					children.set(childIndex, nextChildSg);
266 				}
267 
268 				filterBogusGroups(nextChildSg);
269 			}
270 		}
271 
272 	}
273 
274 	private void constrainSegGroup(SegGroup theNext) throws ProfileException {
275 		for (int i = 0; i < theNext.getChildrenAsList().size(); i++) {
276 			ProfileStructure next = theNext.getChildrenAsList().get(i);
277 			if (!ourConstrainedUsageTypes.contains(next.getUsage())) {
278 				theNext.getChildrenAsList().remove(i);
279 				i--;
280 			} else {
281 				if (next instanceof Seg) {
282 					constrainSeg((Seg) next);
283 				} else {
284 					constrainSegGroup((SegGroup) next);
285 				}
286 			}
287 		}
288 	}
289 
290 	private void constrainSeg(Seg theNext) throws ProfileException {
291 		for (int i = 0; i < theNext.getFieldsAsList().size(); i++) {
292 			Field next = theNext.getFieldsAsList().get(i);
293 			if (!ourConstrainedUsageTypes.contains(next.getUsage())) {
294 				Field unsupField = new Field();
295 				unsupField.setDatatype("ST");
296 				unsupField.setDescription("Unsupported");
297 				unsupField.setMax((short) 0);
298 				unsupField.setMin((short) 0);
299 				unsupField.setName("Unsupported");
300 				unsupField.setUsage("X");
301 				theNext.getFieldsAsList().set(i, unsupField);
302 			} else {
303 				for (int j = 0; j < next.getChildrenAsList().size(); j++) {
304 					Component nextComponent = next.getChildrenAsList().get(j);
305 					if (!ourConstrainedUsageTypes.contains(nextComponent.getUsage())) {
306 						nextComponent.setName("Unsupported");
307 						nextComponent.setDescription("Unsupported");
308 						for (SubComponent nextSub : nextComponent.getChildrenAsList()) {
309 							nextSub.setName("Unsupported");
310 							nextSub.setDescription("Unsupported");
311 						}
312 					} else {
313 						for (SubComponent nextSub : nextComponent.getChildrenAsList()) {
314 							if (!ourConstrainedUsageTypes.contains(nextSub.getUsage())) {
315 								nextSub.setName("Unsupported");
316 								nextSub.setDescription("Unsupported");
317 							}
318 						}
319 					}
320 				}
321 			}
322 		}
323 	}
324 
325 	public static void main(String[] args) throws MojoExecutionException, MojoFailureException {
326 
327 		XsdConfGenMojo tst;
328 		// tst = new XsdConfGenMojo();
329 		// tst.targetDirectory = "hapi-test/target/generated-sources/confgen";
330 		// tst.targetFile = "ADT_All_Other_Triggers.xsd";
331 		// tst.profile =
332 		// "hapi-test/src/test/resources/ca/uhn/hl7v2/conf/parser/cgta-adt_a01.xml";
333 		// tst.templatePackage = "ca.uhn.hl7v2.sourcegen.templates.xsd";
334 		// tst.execute();
335 		//
336 		// tst = new XsdConfGenMojo();
337 		// tst.targetDirectory = "hapi-test/target/generated-sources/confgen";
338 		// tst.targetFile = "ADT_A17_a37.xsd";
339 		// tst.profile =
340 		// "hapi-test/src/test/resources/ca/uhn/hl7v2/conf/parser/cgta-adt_a17_a37.xml";
341 		// tst.templatePackage = "ca.uhn.hl7v2.sourcegen.templates.xsd";
342 		// tst.execute();
343 
344 		tst = new XsdConfGenMojo();
345 		tst.targetDirectory = "hapi-test/target/generated-sources/confgen";
346 		tst.targetFile = "CGTA_INPUT_HL7V2.xsd";
347 		tst.profiles = new ArrayList<>();
348 		tst.linkTriggerToStructure = new HashMap<>();
349 
350 		tst.profiles.add("/eclipse/workspace/CGTA_Input_Tester_gc/ConverterLibrary/src/main/resources/ca/cgta/input/conf/cgta-adt_a01.xml");
351 		tst.linkTriggerToStructure.put("ADT_A02", "ADT_A01");
352 		tst.linkTriggerToStructure.put("ADT_A03", "ADT_A01");
353 		tst.linkTriggerToStructure.put("ADT_A04", "ADT_A01");
354 		tst.linkTriggerToStructure.put("ADT_A05", "ADT_A01");
355 		tst.linkTriggerToStructure.put("ADT_A06", "ADT_A01");
356 		tst.linkTriggerToStructure.put("ADT_A07", "ADT_A01");
357 		tst.linkTriggerToStructure.put("ADT_A08", "ADT_A01");
358 		tst.linkTriggerToStructure.put("ADT_A10", "ADT_A01");
359 		tst.linkTriggerToStructure.put("ADT_A11", "ADT_A01");
360 		tst.linkTriggerToStructure.put("ADT_A13", "ADT_A01");
361 		tst.linkTriggerToStructure.put("ADT_A28", "ADT_A01");
362 		tst.linkTriggerToStructure.put("ADT_A31", "ADT_A01");
363 		tst.linkTriggerToStructure.put("ADT_A37", "ADT_A01");
364 		tst.linkTriggerToStructure.put("ADT_A40", "ADT_A01");
365 		tst.linkTriggerToStructure.put("ADT_A42", "ADT_A01");
366 		tst.linkTriggerToStructure.put("ADT_A45", "ADT_A01");
367 		tst.linkTriggerToStructure.put("ADT_A60", "ADT_A01");
368 
369 		tst.profiles.add("/eclipse/workspace//CGTA_Input_Tester_gc/ConverterLibrary/src/main/resources/ca/cgta/input/conf/cgta-adt_a17_a37.xml");
370 		tst.linkTriggerToStructure.put("ADT_A37", "ADT_A17");
371 
372 		tst.profiles.add("/eclipse/workspace//CGTA_Input_Tester_gc/ConverterLibrary/src/main/resources/ca/cgta/input/conf/cgta-oru_r01.xml");
373 
374 		tst.profiles.add("/eclipse/workspace//CGTA_Input_Tester_gc/ConverterLibrary/src/main/resources/ca/cgta/input/conf/cgta-rde_o11.xml");
375 
376 		tst.profiles.add("/eclipse/workspace//CGTA_Input_Tester_gc/ConverterLibrary/src/main/resources/ca/cgta/input/conf/cgta-ras_o17.xml");
377 
378 		tst.templatePackage = "ca.uhn.hl7v2.sourcegen.templates.xsd";
379 
380 		tst.constrain = true;
381 		tst.execute();
382 
383 	}
384 
385 }