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 "DataTypeGenerator.java".  Description: 
10  "Generates skeletal source code for Datatype classes based on the 
11    HL7 database" 
12  
13  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
14  2001.  All Rights Reserved. 
15  
16  Contributor(s):  James Agnew 
17  
18  Alternatively, the contents of this file may be used under the terms of the 
19  GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
20  applicable instead of those above.  If you wish to allow use of your version of this 
21  file only under the terms of the GPL and not to allow others to use your version 
22  of this file under the MPL, indicate your decision by deleting  the provisions above 
23  and replace  them with the notice and other provisions required by the GPL License.  
24  If you do not delete the provisions above, a recipient may use your version of 
25  this file under either the MPL or the GPL. 
26  
27   */
28  
29  package ca.uhn.hl7v2.mvnplugin;
30  
31  import java.io.File;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.Collections;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  import java.util.TreeMap;
40  
41  import org.apache.maven.plugin.AbstractMojo;
42  import org.apache.maven.plugin.MojoExecutionException;
43  import org.apache.maven.plugin.MojoFailureException;
44  import org.apache.maven.plugins.annotations.Component;
45  import org.apache.maven.plugins.annotations.LifecyclePhase;
46  import org.apache.maven.plugins.annotations.Mojo;
47  import org.apache.maven.plugins.annotations.Parameter;
48  import org.apache.maven.plugins.annotations.ResolutionScope;
49  import org.apache.maven.project.MavenProject;
50  import org.springframework.beans.factory.config.BeanDefinition;
51  import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
52  import org.springframework.core.io.DefaultResourceLoader;
53  import org.springframework.core.type.filter.AssignableTypeFilter;
54  
55  import ca.uhn.hl7v2.HL7Exception;
56  import ca.uhn.hl7v2.Version;
57  import ca.uhn.hl7v2.model.GenericComposite;
58  import ca.uhn.hl7v2.model.Group;
59  import ca.uhn.hl7v2.model.Message;
60  import ca.uhn.hl7v2.parser.DefaultModelClassFactory;
61  import ca.uhn.hl7v2.sourcegen.GroupDef;
62  import ca.uhn.hl7v2.sourcegen.GroupGenerator;
63  import ca.uhn.hl7v2.sourcegen.MessageGenerator;
64  import ca.uhn.hl7v2.sourcegen.SegmentDef;
65  import ca.uhn.hl7v2.sourcegen.StructureDef;
66  import ca.uhn.hl7v2.util.ReflectionUtil;
67  
68  /**
69   * Maven Plugin Mojo for generating HAPI HL7 message/segment/etc source files
70   * 
71   * @author <a href="mailto:jamesagnew@sourceforge.net">James Agnew</a>
72   */
73  @Mojo(name = "superstructuregen",
74  		defaultPhase = LifecyclePhase.GENERATE_SOURCES,
75  requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME,
76  requiresProject = true,
77  inheritByDefault = false)
78  public class SuperStructureMojo extends AbstractMojo {
79  
80  	/**
81  	 * The maven project.
82  	 */
83  	@Component
84  	private MavenProject project;
85  
86  	/**
87  	 * Should build be skipped
88  	 */
89  	@Parameter(required = false)
90  	private boolean skip;
91  
92  	/**
93  	 * Structures to merge
94  	 */
95  	@Parameter(required = false)
96  	private List<String> structures;
97  
98  	/**
99  	 * The target directory for the generated source
100 
101 	 */
102 	@Parameter(required = false)
103 	private String targetDirectory;
104 
105 	/**
106 	 * The target structure name (e.g "ADT_AXX")
107 	 */
108 	@Parameter(required = false)
109 	private String targetStructureName;
110 
111 	private final String templatePackage = "ca.uhn.hl7v2.sourcegen.templates";
112 
113 	/**
114 	 * The version for the generated source
115 	 */
116 	@Parameter(required = false)
117 	private String version;
118 
119 	/**
120 	 * {@inheritDoc}
121 	 */
122 	@Override
123 	public void execute() throws MojoFailureException {
124 
125 		if (skip) {
126 			getLog().warn("Configured to skip");
127 		}
128 
129 		try {
130 			List<String> allStructures = new ArrayList<>();
131 			DefaultModelClassFactory mcf = new DefaultModelClassFactory();
132 
133 			// We want a sorted and unique list of all structures
134 			Version versionOf = Version.versionOf(version);
135 			if (versionOf == null) {
136 				throw new MojoExecutionException("Unknown version: " + version);
137 			}
138 
139 //			Map<String, String> eventMap = mcf.getEventMapForVersion(versionOf);
140 //			if (eventMap == null) {
141 //				throw new MojoExecutionException("Failed to load structures for version " + version + ". Do you have the right dependencies configured for this plugin?");
142 //			}
143 //
144 //			Set<String> allStructuresSet = new HashSet<String>();
145 //			allStructuresSet.addAll(eventMap.values());
146 //			allStructures.addAll(allStructuresSet);
147 //			Collections.sort(allStructures);
148 
149 			ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(true);
150 			DefaultResourceLoader resourceLoader = new DefaultResourceLoader(GenericComposite.class.getClassLoader());
151 			scanner.setResourceLoader(resourceLoader);
152 			scanner.addIncludeFilter(new AssignableTypeFilter(Message.class));
153 			Set<BeanDefinition> components = scanner.findCandidateComponents("ca/uhn/hl7v2/model/" + versionOf.getPackageVersion() + "/message");
154 			for (BeanDefinition beanDefinition : components) {
155 				String nextName = Class.forName(beanDefinition.getBeanClassName()).getSimpleName();
156 				if (nextName.equals(targetStructureName)) {
157 					continue;
158 				}
159 				allStructures.add(nextName);
160 			}
161 			
162 			getLog().info("Found " + allStructures.size() + " message classes for version: " + version);
163 			
164 			List<Message> messagesToMerge = new ArrayList<>();
165 
166 			Collections.sort(allStructures);
167 			for (String nextStructure : allStructures) {
168 				for (String nextStructureToMerge : structures) {
169 					if (nextStructure.matches(nextStructureToMerge)) {
170 						Class<? extends Message> clazz = mcf.getMessageClass(nextStructure, version, true);
171 						messagesToMerge.add(ReflectionUtil.instantiateMessage(clazz, mcf));
172 					}
173 				}
174 			}
175 
176 			if (messagesToMerge.isEmpty()) {
177 				throw new MojoFailureException("No messages match pattern(s): " + structures);
178 			}
179 			
180 			ListOfStructureDefsAndMapOfStructreNames mergedMessages = mergeGroups(messagesToMerge, messagesToMerge);
181 			List<StructureDef> structures = mergedMessages.myStructureDefs;
182 			if (structures.isEmpty()) {
183 				throw new MojoExecutionException("No structures found matching structures to merge");
184 			}
185 
186 			getLog().info("Creating directory: " + targetDirectory);
187 			new File(targetDirectory).mkdirs();
188 
189 			boolean haveGroups = false;
190 			for (StructureDef structureDef : structures) {
191 				if (structureDef.isGroup()) {
192 					haveGroups = true;
193 					writeGroup((GroupDef) structureDef);
194 				}
195 			}
196 
197 			String fileName = MessageGenerator.determineTargetDir(targetDirectory + "/", version) + "/" + targetStructureName + ".java";
198 			getLog().info("Filename will be: " + fileName);
199 
200 			StructureDef[] contents = structures.toArray(new StructureDef[0]);
201 			String basePackageName = DefaultModelClassFactory.getVersionPackageName(version);
202 			MessageGenerator.writeMessage(fileName, contents, targetStructureName, "", version, basePackageName, haveGroups, templatePackage, mergedMessages.myStructureNameToChildNames);
203 
204 		} catch (Exception e) {
205 			throw new MojoFailureException("Failed to generate structure", e);
206 		}
207 		getLog().info("Adding " + targetDirectory + " to compile source root");
208 		project.addCompileSourceRoot(targetDirectory);
209 
210 	}
211 
212 	private void writeGroup(GroupDef theStructureDef) throws Exception {
213 
214 		StructureDef[] structures = theStructureDef.getStructures();
215 		String groupName = theStructureDef.getUnqualifiedName();
216 		GroupGenerator.writeGroup(structures, groupName, targetDirectory, version, targetStructureName, templatePackage, "java");
217 
218 		for (StructureDef structureDef : structures) {
219 			if (structureDef instanceof GroupDef) {
220 				writeGroup((GroupDef) structureDef);
221 			}
222 		}
223 
224 	}
225 
226 	private static class ListOfStructureDefsAndMapOfStructreNames {
227 		private List<StructureDef> myStructureDefs;
228 		private Map<String, List<String>> myStructureNameToChildNames;
229 	}
230 
231 	private ListOfStructureDefsAndMapOfStructreNames mergeGroups(List<? extends Group> theGroupsToMerge, List<Message> theAssociatedStructures) throws HL7Exception, MojoFailureException {
232 		ArrayList<StructureDef> retValStructureDefs = new ArrayList<>();
233 
234 		List<List<String>> allNameLists = new ArrayList<>();
235 		for (Group nextGroup : theGroupsToMerge) {
236 			List<String> nextList = Arrays.asList(nextGroup.getNames());
237 			// for (int i = 0; i < nextList.size(); i++) {
238 			// if (nextGroup.isGroup(nextList.get(i)) == false) {
239 			// nextList.set(i, nextList.get(i).substring(0, 3));
240 			// }
241 			// }
242 			allNameLists.add(nextList);
243 		}
244 
245 		ArrayList<String> structureNames = mergeStringLists(allNameLists);
246 		int currentStructureIdx = 0;
247 		for (String nextStructureName : structureNames) {
248 
249 			/*
250 			 * Don't have the same name a second time. This mainly prevents a
251 			 * second OBX in ADT messages, so it should be ok since there is an
252 			 * appropriate OBX in a PROCEDURE group right before the second
253 			 * one..?
254 			 */
255 			if (structureNames.subList(0, currentStructureIdx++).contains(nextStructureName)) {
256 				continue;
257 			}
258 
259 			boolean required = true;
260 			boolean repeating = false;
261 			boolean group = false;
262 			boolean choice = false;
263 			List<Group> childGroups = new ArrayList<>();
264 			List<Message> associatedChildStructures = new ArrayList<>();
265 
266 			int idx = 0;
267 			for (Group nextGroup : theGroupsToMerge) {
268 				if (Arrays.asList(nextGroup.getNames()).contains(nextStructureName)) {
269 
270 					if (theAssociatedStructures != null) {
271 						associatedChildStructures.add(theAssociatedStructures.get(idx));
272 					}
273 
274 					repeating |= nextGroup.isRepeating(nextStructureName);
275 					choice |= nextGroup.isChoiceElement(nextStructureName);
276 					required &= nextGroup.isRequired(nextStructureName);
277 					if (nextGroup.isGroup(nextStructureName)) {
278 						group = true;
279 						childGroups.add((Group) nextGroup.get(nextStructureName));
280 					}
281 				} else {
282 					required = false;
283 				}
284 
285 				idx++;
286 			}
287 
288 			Version versionEnum = Version.versionOf(version);
289 			if (versionEnum == null) {
290 				throw new MojoFailureException("Invalid version: " + version);
291 			}
292 			Map<String, String> eventMapForVersion = new DefaultModelClassFactory().getEventMapForVersion(versionEnum);
293 			if (eventMapForVersion == null) {
294 				throw new MojoFailureException("No event map for version: " + version);
295 			}
296 			if (!group) {
297 				SegmentDef seg = new SegmentDef(nextStructureName.substring(0, 3), "", required, repeating, choice, "");
298 				retValStructureDefs.add(seg);
299 
300 				/*
301 				 * Use the event map to turn each asociated message (e.g.
302 				 * ADT_A01.class) into associated structure names (e.g.
303 				 * "ADT_A01", "ADT_A04")
304 				 */
305 				for (Message next : associatedChildStructures) {
306 					seg.addAssociatedStructure(next.getName());
307 					Map<String, String> evtMap = new TreeMap<>(eventMapForVersion);
308 					for (Map.Entry<String, String> nextEntry : evtMap.entrySet()) {
309 						String value = nextEntry.getValue();
310 						String name = next.getName();
311 						if (value.equals(name)) {
312 							seg.addAssociatedStructure(nextEntry.getValue());
313 							seg.addAssociatedStructure(nextEntry.getKey());
314 						}
315 					}
316 				}
317 
318 				seg.setIndexName(nextStructureName);
319 
320 				continue;
321 			}
322 
323 			GroupDef grp = new GroupDef(targetStructureName, nextStructureName, required, repeating, "");
324 			grp.setIndexName(nextStructureName);
325 			List<StructureDef> children = mergeGroups(childGroups, null).myStructureDefs;
326 
327 			/*
328 			 * Use the event map to turn each asociated message (e.g.
329 			 * ADT_A01.class) into associated structure names (e.g.
330 			 * "ADT_A01", "ADT_A04")
331 			 */
332 			for (Message next : associatedChildStructures) {
333 				Map<String, String> evtMap = eventMapForVersion;
334 				for (Map.Entry<String, String> nextEntry : evtMap.entrySet()) {
335 					if (nextEntry.getValue().equals(next.getName())) {
336 						grp.addAssociatedStructure(nextEntry.getKey());
337 					}
338 				}
339 			}
340 
341 			for (StructureDef structureDef : children) {
342 				grp.addStructure(structureDef);
343 			}
344 			retValStructureDefs.add(grp);
345 
346 		}
347 
348 		ListOfStructureDefsAndMapOfStructreNames retVal = new ListOfStructureDefsAndMapOfStructreNames();
349 		retVal.myStructureDefs = retValStructureDefs;
350 
351 		if (theAssociatedStructures != null) {
352 			HashMap<String, List<String>> retValMap = new HashMap<>();
353 			for (Message next : theAssociatedStructures) {
354 				retValMap.put(next.getName(), Arrays.asList(next.getNames()));
355 			}
356 			retVal.myStructureNameToChildNames = retValMap;
357 		}
358 
359 		return retVal;
360 	}
361 
362 	ArrayList<String> mergeStringLists(List<List<String>> allNameLists) {
363 		ArrayList<String> baseList = new ArrayList<>(allNameLists.remove(0));
364 		getLog().debug("Base list is: "+ baseList);
365 
366 		for (List<String> nextCompareList : allNameLists) {
367 
368 			getLog().debug("Next compare list: "+ nextCompareList);
369 
370 			int baseIndex = 0;
371 			int compareIndex = 0;
372 
373 			while (compareIndex < nextCompareList.size()) {
374 
375 				String currentBase = baseList.get(baseIndex);
376 				String currentCompare = nextCompareList.get(compareIndex);
377 				if (currentBase.equals(currentCompare)) {
378 					if (baseIndex + 1 < baseList.size()) {
379 						baseIndex++;
380 					}
381 					compareIndex++;
382 					continue;
383 				}
384 
385 				List<String> subList = baseList.subList(baseIndex, baseList.size());
386 
387 				// Find next match
388 				List<String> toAdd = null;
389 				for (int searchCompareIndex = compareIndex + 1; searchCompareIndex < nextCompareList.size(); searchCompareIndex++) {
390 					String find = nextCompareList.get(searchCompareIndex);
391 					int foundAt = subList.indexOf(find);
392 					if (foundAt != -1) {
393 						toAdd = nextCompareList.subList(compareIndex, searchCompareIndex);
394 						break;
395 					}
396 				}
397 
398 				int addAtIndex = baseIndex;
399 
400 				if (toAdd == null) {
401 
402 					toAdd = nextCompareList.subList(compareIndex, nextCompareList.size());
403 					addAtIndex = baseList.size();
404 
405 					int foundInBaseAtIndex = subList.indexOf(currentCompare);
406 					if (foundInBaseAtIndex != -1) {
407 						baseIndex += foundInBaseAtIndex;
408 						compareIndex++;
409 						continue;
410 					}
411 
412 				} else {
413 
414 					int foundInBaseAtIndex = subList.indexOf(currentCompare);
415 					if (foundInBaseAtIndex != -1) {
416 						baseIndex += foundInBaseAtIndex;
417 						// compareIndex++;
418 						continue;
419 					}
420 
421 				}
422 
423 				baseList.addAll(addAtIndex, toAdd);
424 				baseIndex += toAdd.size();
425 				compareIndex += toAdd.size();
426 
427 				// if (toAdd.size() == 0) {
428 				// break;
429 				// }
430 			}
431 			getLog().debug("Base list is now: "+ baseList);
432 
433 		}
434 
435 		getLog().debug("Merged name list: "+ baseList);
436 		return baseList;
437 	}
438 
439 	public static void main(String[] args) throws MojoExecutionException, MojoFailureException {
440 		
441 		SuperStructureMojo m = new SuperStructureMojo();
442 		m.structures = new ArrayList<>();
443 		m.structures.add("ADT_A[0-9]{2}");
444 
445 		m.targetDirectory = "target/merge";
446 		m.targetStructureName = "ADT_AXX";
447 		m.version = "2.1";
448 		m.execute();
449 	}
450 
451 }