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 "DefaultValidator.java".  Description: 
10  "A default conformance validator." 
11  
12  The Initial Developer of the Original Code is University Health Network. Copyright (C) 
13  2003.  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  
28  package ca.uhn.hl7v2.conf.check;
29  
30  import java.io.BufferedReader;
31  import java.io.File;
32  import java.io.FileReader;
33  import java.io.IOException;
34  import java.util.ArrayList;
35  import java.util.List;
36  
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  import ca.uhn.hl7v2.DefaultHapiContext;
41  import ca.uhn.hl7v2.HL7Exception;
42  import ca.uhn.hl7v2.HapiContext;
43  import ca.uhn.hl7v2.HapiContextSupport;
44  import ca.uhn.hl7v2.conf.ProfileException;
45  import ca.uhn.hl7v2.conf.parser.ProfileParser;
46  import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
47  import ca.uhn.hl7v2.conf.spec.message.AbstractComponent;
48  import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
49  import ca.uhn.hl7v2.conf.spec.message.Component;
50  import ca.uhn.hl7v2.conf.spec.message.Field;
51  import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
52  import ca.uhn.hl7v2.conf.spec.message.Seg;
53  import ca.uhn.hl7v2.conf.spec.message.SegGroup;
54  import ca.uhn.hl7v2.conf.spec.message.StaticDef;
55  import ca.uhn.hl7v2.conf.spec.message.SubComponent;
56  import ca.uhn.hl7v2.conf.store.CodeStore;
57  import ca.uhn.hl7v2.conf.store.ProfileStoreFactory;
58  import ca.uhn.hl7v2.model.Composite;
59  import ca.uhn.hl7v2.model.DataTypeException;
60  import ca.uhn.hl7v2.model.Group;
61  import ca.uhn.hl7v2.model.Message;
62  import ca.uhn.hl7v2.model.Primitive;
63  import ca.uhn.hl7v2.model.Segment;
64  import ca.uhn.hl7v2.model.Structure;
65  import ca.uhn.hl7v2.model.Type;
66  import ca.uhn.hl7v2.parser.EncodingCharacters;
67  import ca.uhn.hl7v2.parser.GenericParser;
68  import ca.uhn.hl7v2.parser.Parser;
69  import ca.uhn.hl7v2.parser.PipeParser;
70  import ca.uhn.hl7v2.util.Terser;
71  
72  /**
73   * A default conformance profile validator.
74   * 
75   * Note: this class is currently NOT thread-safe!
76   * 
77   * @author Bryan Tripp
78   */
79  public class DefaultValidator extends HapiContextSupport implements Validator {
80  
81  	private EncodingCharacters enc; // used to check for content in parts of a message
82  	private static final Logger log = LoggerFactory.getLogger(DefaultValidator.class);
83  	private boolean validateChildren = true;
84  	private CodeStore codeStore;
85  
86  	/** Creates a new instance of DefaultValidator */
87  	public DefaultValidator() {
88  	    this(new DefaultHapiContext());
89  	}
90  	
91      public DefaultValidator(HapiContext context) {
92          super(context);
93          enc = new EncodingCharacters('|', null); // the | is assumed later -- don't change
94      }	
95  
96  	/**
97  	 * If set to false (default is true), each testXX and validateXX method will only test the
98  	 * direct object it is responsible for, not its children.
99  	 */
100 	public void setValidateChildren(boolean validateChildren) {
101 		this.validateChildren = validateChildren;
102 	}
103 
104 	/**
105 	 * <p>
106 	 * Provides a code store to use to provide the code tables which will be used to validate coded
107 	 * value types. If a code store has not been set (which is the default),
108 	 * {@link ProfileStoreFactory} will be checked for an appropriate code store, and if none is
109 	 * found then coded values will not be validated.
110 	 * </p>
111 	 */
112 	public void setCodeStore(CodeStore theCodeStore) {
113 		codeStore = theCodeStore;
114 	}
115 
116 	/**
117 	 * @see Validator#validate
118 	 */
119 	public HL7Exception[] validate(Message message, StaticDef profile) throws ProfileException,
120 			HL7Exception {
121 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
122 		Terser t = new Terser(message);
123 
124 		// check msg type, event type, msg struct ID
125 		String msgType = t.get("/MSH-9-1");
126 		if (!msgType.equals(profile.getMsgType())) {
127 			HL7Exception e = new ProfileNotFollowedException("Message type " + msgType
128 					+ " doesn't match profile type of " + profile.getMsgType());
129 			exList.add(e);
130 		}
131 
132 		String evType = t.get("/MSH-9-2");
133 		if (!evType.equals(profile.getEventType())
134 				&& !profile.getEventType().equalsIgnoreCase("ALL")) {
135 			HL7Exception e = new ProfileNotFollowedException("Event type " + evType
136 					+ " doesn't match profile type of " + profile.getEventType());
137 			exList.add(e);
138 		}
139 
140 		String msgStruct = t.get("/MSH-9-3");
141 		if (msgStruct == null || !msgStruct.equals(profile.getMsgStructID())) {
142 			HL7Exception e = new ProfileNotFollowedException("Message structure " + msgStruct
143 					+ " doesn't match profile type of " + profile.getMsgStructID());
144 			exList.add(e);
145 		}
146 
147 		exList.addAll(doTestGroup(message, profile, profile.getIdentifier(),
148 				validateChildren));
149 		return exList.toArray(new HL7Exception[exList.size()]);
150 	}
151 
152 	/**
153 	 * Tests a group against a group section of a profile.
154 	 */
155 	public List<HL7Exception> testGroup(Group group, SegGroup profile, String profileID)
156 			throws ProfileException {
157 		return doTestGroup(group, profile, profileID, true);
158 	}
159 
160 	private List<HL7Exception> doTestGroup(Group group, AbstractSegmentContainer profile,
161 			String profileID, boolean theValidateChildren) throws ProfileException {
162 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
163 		List<String> allowedStructures = new ArrayList<String>();
164 
165 		for (ProfileStructure struct : profile) {
166 
167 			// only test a structure in detail if it isn't X
168 			if (!struct.getUsage().equalsIgnoreCase("X")) {
169 				allowedStructures.add(struct.getName());
170 
171 				// see which instances have content
172 				try {
173 					List<Structure> instancesWithContent = new ArrayList<Structure>();
174 					for (Structure instance : group.getAll(struct.getName())) {
175 						if (!instance.isEmpty())
176 							instancesWithContent.add(instance);
177 					}
178 
179 					HL7Exception ce = testCardinality(instancesWithContent.size(), struct.getMin(),
180 							struct.getMax(), struct.getUsage(), struct.getName());
181 					if (ce != null)
182 						exList.add(ce);
183 
184 					// test children on instances with content
185 					if (theValidateChildren) {
186 						for (Structure s : instancesWithContent) {
187 							List<HL7Exception> childExceptions = testStructure(s, struct, profileID);
188                             exList.addAll(childExceptions);
189 						}
190 					}
191 
192 				} catch (HL7Exception he) {
193 					exList.add(new ProfileNotHL7CompliantException(struct.getName()
194 							+ " not found in message"));
195 				}
196 			}
197 		}
198 
199 		// complain about X structures that have content
200         exList.addAll(checkForExtraStructures(group, allowedStructures));
201 
202 		return exList;
203 	}
204 
205 	/**
206 	 * Checks a group's children against a list of allowed structures for the group (ie those
207 	 * mentioned in the profile with usage other than X). Returns a list of exceptions representing
208 	 * structures that appear in the message but are not supposed to.
209 	 */
210 	private List<HL7Exception> checkForExtraStructures(Group group, List<String> allowedStructures)
211 			throws ProfileException {
212 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
213 		for (String childName : group.getNames()) {
214 			if (!allowedStructures.contains(childName)) {
215 				try {
216 					for (Structure rep : group.getAll(childName)) {
217 						if (!rep.isEmpty()) {
218 							HL7Exception e = new XElementPresentException("The structure "
219 									+ childName + " appears in the message but not in the profile");
220 							exList.add(e);
221 						}
222 					}
223 				} catch (HL7Exception he) {
224 					throw new ProfileException("Problem checking profile", he);
225 				}
226 			}
227 		}
228 		return exList;
229 	}
230 
231 	/**
232 	 * Checks cardinality and creates an appropriate exception if out of bounds. The usage code is
233 	 * needed because if min cardinality is > 0, the min # of reps is only required if the usage
234 	 * code is 'R' (see HL7 v2.5 section 2.12.6.4).
235 	 * 
236 	 * @param reps the number of reps
237 	 * @param min the minimum number of reps
238 	 * @param max the maximum number of reps (-1 means *)
239 	 * @param usage the usage code
240 	 * @param name the name of the repeating structure (used in exception msg)
241 	 * @return null if cardinality OK, exception otherwise
242 	 */
243 	protected HL7Exception testCardinality(int reps, int min, int max, String usage, String name) {
244 		HL7Exception e = null;
245 		if (reps < min && usage.equalsIgnoreCase("R")) {
246 			e = new ProfileNotFollowedException(name + " must have at least " + min
247 					+ " repetitions (has " + reps + ")");
248 		} else if (max > 0 && reps > max) {
249 			e = new ProfileNotFollowedException(name + " must have no more than " + max
250 					+ " repetitions (has " + reps + ")");
251 		}
252 		return e;
253 	}
254 
255 	/**
256 	 * Tests a structure (segment or group) against the corresponding part of a profile.
257 	 */
258 	public List<HL7Exception> testStructure(Structure s, ProfileStructure profile, String profileID)
259 			throws ProfileException {
260 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
261 		if (profile instanceof Seg) {
262 			if (Segment.class.isAssignableFrom(s.getClass())) {
263 				exList.addAll(doTestSegment((Segment) s, (Seg) profile, profileID, validateChildren));
264 			} else {
265 				exList.add(new ProfileNotHL7CompliantException(
266 						"Mismatch between a segment in the profile and the structure "
267 								+ s.getClass().getName() + " in the message"));
268 			}
269 		} else if (profile instanceof SegGroup) {
270 			if (Group.class.isAssignableFrom(s.getClass())) {
271                 exList.addAll(testGroup((Group) s, (SegGroup) profile, profileID));
272 			} else {
273 				exList.add(new ProfileNotHL7CompliantException(
274 						"Mismatch between a group in the profile and the structure "
275 								+ s.getClass().getName() + " in the message"));
276 			}
277 		}
278 		return exList;
279 	}
280 
281 	/**
282 	 * Tests a segment against a segment section of a profile.
283 	 */
284 	public List<HL7Exception> testSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile,
285 			String profileID) throws ProfileException {
286 		return doTestSegment(segment, profile, profileID, true);
287 	}
288 
289 	private List<HL7Exception> doTestSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile,
290 			String profileID, boolean theValidateChildren) throws ProfileException {
291 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
292 		List<Integer> allowedFields = new ArrayList<Integer>();
293 
294 		for (int i = 1; i <= profile.getFields(); i++) {
295 			Field field = profile.getField(i);
296 
297 			// only test a field in detail if it isn't X
298 			if (!field.getUsage().equalsIgnoreCase("X")) {
299 				allowedFields.add(i);
300 
301 				// see which instances have content
302 				try {
303 					Type[] instances = segment.getField(i);
304 					List<Type> instancesWithContent = new ArrayList<Type>();
305 					for (Type instance : instances) {
306 						if (!instance.isEmpty())
307 							instancesWithContent.add(instance);
308 					}
309 
310 					HL7Exception ce = testCardinality(instancesWithContent.size(), field.getMin(),
311 							field.getMax(), field.getUsage(), field.getName());
312 					if (ce != null) {
313 						ce.setFieldPosition(i);
314 						exList.add(ce);
315 					}
316 
317 					// test field instances with content
318 					if (theValidateChildren) {
319 						for (Type s : instancesWithContent) {
320 							boolean escape = true; // escape field value when checking length
321 							if (profile.getName().equalsIgnoreCase("MSH") && i < 3) {
322 								escape = false;
323 							}
324                             List<HL7Exception> childExceptions = doTestField(s, field, escape,
325 									profileID, validateChildren);
326 							for (HL7Exception ex : childExceptions) {
327                                 ex.setFieldPosition(i);
328 							}
329                             exList.addAll(childExceptions);
330 						}
331 					}
332 
333 				} catch (HL7Exception he) {
334 					exList.add(new ProfileNotHL7CompliantException("Field " + i
335 							+ " not found in message"));
336 				}
337 			}
338 
339 		}
340 
341 		// complain about X fields with content
342 		exList.addAll(checkForExtraFields(segment, allowedFields));
343 
344 		for (HL7Exception ex : exList) {
345             ex.setSegmentName(profile.getName());
346 		}
347 		return exList;
348 	}
349 
350 	/**
351 	 * Checks a segment against a list of allowed fields (ie those mentioned in the profile with
352 	 * usage other than X). Returns a list of exceptions representing field that appear but are not
353 	 * supposed to.
354 	 * 
355 	 * @param allowedFields an array of Integers containing field #s of allowed fields
356 	 */
357 	private List<HL7Exception> checkForExtraFields(Segment segment, List<Integer> allowedFields)
358 			throws ProfileException {
359 		ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
360 		for (int i = 1; i <= segment.numFields(); i++) {
361 			if (!allowedFields.contains(new Integer(i))) {
362 				try {
363 					Type[] reps = segment.getField(i);
364 					for (Type rep : reps) {
365 						if (!rep.isEmpty()) {
366 							HL7Exception e = new XElementPresentException("Field " + i + " in "
367 									+ segment.getName()
368 									+ " appears in the message but not in the profile");
369 							exList.add(e);
370 						}
371 					}
372 				} catch (HL7Exception he) {
373 					throw new ProfileException("Problem testing against profile", he);
374 				}
375 			}
376 		}
377 		return exList;
378 	}
379 
380 	/**
381 	 * Tests a Type against the corresponding section of a profile.
382 	 * 
383 	 * @param encoded optional encoded form of type (if you want to specify this -- if null, default
384 	 *            pipe-encoded form is used to check length and constant val)
385 	 */
386 	public List<HL7Exception> testType(Type type, AbstractComponent<?> profile, String encoded,
387 			String profileID) {
388 		ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
389 		if (encoded == null)
390 			encoded = PipeParser.encode(type, this.enc);
391 
392 		HL7Exception ue = testUsage(encoded, profile.getUsage(), profile.getName());
393 		if (ue != null)
394 			exList.add(ue);
395 
396 		if (!profile.getUsage().equals("X")) {
397 			// check datatype
398 			String typeName = type.getName();
399 			if (!typeName.equals(profile.getDatatype())) {
400 				exList.add(new ProfileNotHL7CompliantException("HL7 datatype " + typeName
401 						+ " doesn't match profile datatype " + profile.getDatatype()));
402 			}
403 
404 			// check length
405 			if (encoded.length() > profile.getLength())
406 				exList.add(new ProfileNotFollowedException("The type " + profile.getName()
407 						+ " has length " + encoded.length() + " which exceeds max of "
408 						+ profile.getLength()));
409 
410 			// check constant value
411 			if (profile.getConstantValue() != null && profile.getConstantValue().length() > 0) {
412 				if (!encoded.equals(profile.getConstantValue()))
413 					exList.add(new ProfileNotFollowedException("'" + encoded
414 							+ "' doesn't equal constant value of '" + profile.getConstantValue()
415 							+ "'"));
416 			}
417 
418             exList.addAll(testTypeAgainstTable(type, profile, profileID));
419 		}
420 
421 		return exList;
422 	}
423 
424 	/**
425 	 * Tests whether the given type falls within a maximum length.
426 	 * 
427 	 * @return null of OK, an HL7Exception otherwise
428 	 */
429 	public HL7Exception testLength(Type type, int maxLength) {
430 		HL7Exception e = null;
431 		String encoded = PipeParser.encode(type, this.enc);
432 		if (encoded.length() > maxLength) {
433 			e = new ProfileNotFollowedException("Length of " + encoded.length()
434 					+ " exceeds maximum of " + maxLength);
435 		}
436 		return e;
437 	}
438 
439 	/**
440 	 * Tests an element against the corresponding usage code. The element is required in its encoded
441 	 * form.
442 	 * 
443 	 * @param encoded the pipe-encoded message element
444 	 * @param usage the usage code (e.g. "CE")
445 	 * @param name the name of the element (for use in exception messages)
446 	 * @return null if there is no problem, an HL7Exception otherwise
447 	 */
448 	private HL7Exception testUsage(String encoded, String usage, String name) {
449 		HL7Exception e = null;
450 		if (usage.equalsIgnoreCase("R")) {
451 			if (encoded.length() == 0)
452 				e = new ProfileNotFollowedException("Required element " + name + " is missing");
453 		} else if (usage.equalsIgnoreCase("RE")) {
454 			// can't test anything
455 		} else if (usage.equalsIgnoreCase("O")) {
456 			// can't test anything
457 		} else if (usage.equalsIgnoreCase("C")) {
458 			// can't test anything yet -- wait for condition syntax in v2.6
459 		} else if (usage.equalsIgnoreCase("CE")) {
460 			// can't test anything
461 		} else if (usage.equalsIgnoreCase("X")) {
462 			if (encoded.length() > 0)
463 				e = new XElementPresentException("Element \"" + name
464 						+ "\" is present but specified as not used (X)");
465 		} else if (usage.equalsIgnoreCase("B")) {
466 			// can't test anything
467 		}
468 		return e;
469 	}
470 
471 	/**
472 	 * Tests table values for ID, IS, and CE types. An empty list is returned for all other types or
473 	 * if the table name or number is missing.
474 	 */
475 	private List<HL7Exception> testTypeAgainstTable(Type type, AbstractComponent<?> profile,
476 			String profileID) {
477 		ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
478 		if (profile.getTable() != null
479 				&& (type.getName().equals("IS") || type.getName().equals("ID"))) {
480 			String codeSystem = makeTableName(profile.getTable());
481 			String value = ((Primitive) type).getValue();
482 			addTableTestResult(exList, profileID, codeSystem, value);
483 		} else if (type.getName().equals("CE")) {
484 			String value = Terser.getPrimitive(type, 1, 1).getValue();
485 			String codeSystem = Terser.getPrimitive(type, 3, 1).getValue();
486 			addTableTestResult(exList, profileID, codeSystem, value);
487 
488 			value = Terser.getPrimitive(type, 4, 1).getValue();
489 			codeSystem = Terser.getPrimitive(type, 6, 1).getValue();
490 			addTableTestResult(exList, profileID, codeSystem, value);
491 		}
492 		return exList;
493 	}
494 
495 	private void addTableTestResult(List<HL7Exception> exList, String profileID,
496 			String codeSystem, String value) {
497 		if (codeSystem != null && value != null) {
498 			HL7Exception e = testValueAgainstTable(profileID, codeSystem, value);
499 			if (e != null)
500 				exList.add(e);
501 		}
502 	}
503 
504 	private HL7Exception testValueAgainstTable(String profileID, String codeSystem, String value) {
505 		HL7Exception ret = null;
506 		if (!validateChildren) {
507 			return ret;
508 		}
509 
510 		CodeStore store = codeStore;
511 		if (codeStore == null) {
512 			store = getHapiContext().getCodeStoreRegistry().getCodeStore(profileID, codeSystem);
513 		}
514 
515 		if (store == null) {
516 			log.warn(
517 					"Not checking value {}: no code store was found for profile {} code system {}",
518 					new Object[] { value, profileID, codeSystem });
519 		} else {
520 			if (!store.knowsCodes(codeSystem)) {
521 				log.warn("Not checking value {}: Don't have a table for code system {}", value,
522 						codeSystem);
523 			} else if (!store.isValidCode(codeSystem, value)) {
524 				ret = new ProfileNotFollowedException("Code '" + value + "' not found in table "
525 						+ codeSystem + ", profile " + profileID);
526 			}
527 		}
528 		return ret;
529 	}
530 
531 	private String makeTableName(String hl7Table) {
532         return String.format("HL7%1$4s", hl7Table).replace(" ", "0");
533 	}
534 
535 	public List<HL7Exception> testField(Type type, Field profile, boolean escape, String profileID)
536 			throws ProfileException {
537 		return doTestField(type, profile, escape, profileID, true);
538 	}
539 
540 	private List<HL7Exception> doTestField(Type type, Field profile, boolean escape, String profileID,
541 			boolean theValidateChildren) throws ProfileException {
542 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
543 
544 		// account for MSH 1 & 2 which aren't escaped
545 		String encoded = null;
546 		if (!escape && Primitive.class.isAssignableFrom(type.getClass()))
547 			encoded = ((Primitive) type).getValue();
548 
549 		exList.addAll(testType(type, profile, encoded, profileID));
550 
551 		// test children
552 		if (theValidateChildren) {
553 			if (profile.getComponents() > 0 && !profile.getUsage().equals("X")) {
554 				if (Composite.class.isAssignableFrom(type.getClass())) {
555 					Composite comp = (Composite) type;
556 					for (int i = 1; i <= profile.getComponents(); i++) {
557 						Component childProfile = profile.getComponent(i);
558 						try {
559 							Type child = comp.getComponent(i - 1);
560 							exList.addAll(doTestComponent(child, childProfile, profileID, validateChildren));
561 						} catch (DataTypeException de) {
562 							exList.add(new ProfileNotHL7CompliantException(
563 									"More components in profile than allowed in message: "
564 											+ de.getMessage()));
565 						}
566 					}
567 					exList.addAll(checkExtraComponents(comp, profile.getComponents()));
568 				} else {
569 					exList.add(new ProfileNotHL7CompliantException("A field has type primitive "
570 							+ type.getClass().getName() + " but the profile defines components"));
571 				}
572 			}
573 		}
574 
575 		return exList;
576 	}
577 
578 	public List<HL7Exception> testComponent(Type type, Component profile, String profileID)
579 			throws ProfileException {
580 		return doTestComponent(type, profile, profileID, true);
581 	}
582 
583 	private List<HL7Exception> doTestComponent(Type type, Component profile, String profileID,
584 			boolean theValidateChildren) throws ProfileException {
585 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
586 		exList.addAll(testType(type, profile, null, profileID));
587 
588 		// test children
589 		if (profile.getSubComponents() > 0 && !profile.getUsage().equals("X") && (!type.isEmpty())) {
590 			if (Composite.class.isAssignableFrom(type.getClass())) {
591 				Composite comp = (Composite) type;
592 
593 				if (theValidateChildren) {
594 					for (int i = 1; i <= profile.getSubComponents(); i++) {
595 						SubComponent childProfile = profile.getSubComponent(i);
596 						try {
597 							Type child = comp.getComponent(i - 1);
598 							exList.addAll(testType(child, childProfile, null, profileID));
599 						} catch (DataTypeException de) {
600 							exList.add(new ProfileNotHL7CompliantException(
601 									"More subcomponents in profile than allowed in message: "
602 											+ de.getMessage()));
603 						}
604 					}
605 				}
606 
607                 exList.addAll(checkExtraComponents(comp, profile.getSubComponents()));
608 			} else {
609 				exList.add(new ProfileNotFollowedException("A component has primitive type "
610 						+ type.getClass().getName() + " but the profile defines subcomponents"));
611 			}
612 		}
613 
614 		return exList;
615 	}
616 
617 	/** Tests for extra components (ie any not defined in the profile) */
618 	private List<HL7Exception> checkExtraComponents(Composite comp, int numInProfile)
619 			throws ProfileException {
620 		List<HL7Exception> exList = new ArrayList<HL7Exception>();
621 
622 		StringBuffer extra = new StringBuffer();
623 		for (int i = numInProfile; i < comp.getComponents().length; i++) {
624 			try {
625 				String s = PipeParser.encode(comp.getComponent(i), enc);
626 				if (s.length() > 0) {
627 					extra.append(s).append(enc.getComponentSeparator());
628 				}
629 			} catch (DataTypeException de) {
630 				throw new ProfileException("Problem testing against profile", de);
631 			}
632 		}
633 
634 		if (extra.toString().length() > 0) {
635 			exList.add(new XElementPresentException(
636 					"The following components are not defined in the profile: " + extra.toString()));
637 		}
638 
639 		return exList;
640 	}
641 
642 	public static void main(String args[]) {
643 
644 		if (args.length != 2) {
645 			System.out.println("Usage: DefaultValidator message_file profile_file");
646 			System.exit(1);
647 		}
648 
649 		DefaultValidator val = new DefaultValidator();
650 		try {
651 			String msgString = loadFile(args[0]);
652 			Parser parser = new GenericParser();
653 			Message message = parser.parse(msgString);
654 
655 			String profileString = loadFile(args[1]);
656 			ProfileParser profParser = new ProfileParser(true);
657 			RuntimeProfile profile = profParser.parse(profileString);
658 
659 			HL7Exception[] exceptions = val.validate(message, profile.getMessage());
660 
661 			System.out.println("Exceptions: ");
662 			for (int i = 0; i < exceptions.length; i++) {
663 				System.out.println((i + 1) + ". " + exceptions[i].getMessage());
664 			}
665 		} catch (Exception e) {
666 			e.printStackTrace();
667 		}
668 	}
669 
670 	/** loads file at the given path */
671 	private static String loadFile(String path) throws IOException {
672 		File file = new File(path);
673 		// char[] cbuf = new char[(int) file.length()];
674 		BufferedReader in = new BufferedReader(new FileReader(file));
675 		StringBuffer buf = new StringBuffer(5000);
676 		int c;
677 		while ((c = in.read()) != -1) {
678 			buf.append((char) c);
679 		}
680 		// in.read(cbuf, 0, (int) file.length());
681 		in.close();
682 		// return String.valueOf(cbuf);
683 		return buf.toString();
684 	}
685 
686 }