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