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