001 /**
002 The contents of this file are subject to the Mozilla Public License Version 1.1
003 (the "License"); you may not use this file except in compliance with the License.
004 You may obtain a copy of the License at http://www.mozilla.org/MPL/
005 Software distributed under the License is distributed on an "AS IS" basis,
006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007 specific language governing rights and limitations under the License.
008
009 The Original Code is "DefaultValidator.java". Description:
010 "A default conformance validator."
011
012 The Initial Developer of the Original Code is University Health Network. Copyright (C)
013 2003. All Rights Reserved.
014
015 Contributor(s): ______________________________________.
016
017 Alternatively, the contents of this file may be used under the terms of the
018 GNU General Public License (the �GPL�), in which case the provisions of the GPL are
019 applicable instead of those above. If you wish to allow use of your version of this
020 file only under the terms of the GPL and not to allow others to use your version
021 of this file under the MPL, indicate your decision by deleting the provisions above
022 and replace them with the notice and other provisions required by the GPL License.
023 If you do not delete the provisions above, a recipient may use your version of
024 this file under either the MPL or the GPL.
025
026 */
027
028 package ca.uhn.hl7v2.conf.check;
029
030 import java.io.BufferedReader;
031 import java.io.File;
032 import java.io.FileReader;
033 import java.io.IOException;
034 import java.util.ArrayList;
035
036 import org.slf4j.Logger;
037 import org.slf4j.LoggerFactory;
038
039 import ca.uhn.hl7v2.HL7Exception;
040 import ca.uhn.hl7v2.conf.ProfileException;
041 import ca.uhn.hl7v2.conf.parser.ProfileParser;
042 import ca.uhn.hl7v2.conf.spec.RuntimeProfile;
043 import ca.uhn.hl7v2.conf.spec.message.AbstractComponent;
044 import ca.uhn.hl7v2.conf.spec.message.AbstractSegmentContainer;
045 import ca.uhn.hl7v2.conf.spec.message.Component;
046 import ca.uhn.hl7v2.conf.spec.message.Field;
047 import ca.uhn.hl7v2.conf.spec.message.ProfileStructure;
048 import ca.uhn.hl7v2.conf.spec.message.Seg;
049 import ca.uhn.hl7v2.conf.spec.message.SegGroup;
050 import ca.uhn.hl7v2.conf.spec.message.StaticDef;
051 import ca.uhn.hl7v2.conf.spec.message.SubComponent;
052 import ca.uhn.hl7v2.conf.store.CodeStore;
053 import ca.uhn.hl7v2.conf.store.ProfileStoreFactory;
054 import ca.uhn.hl7v2.model.Composite;
055 import ca.uhn.hl7v2.model.DataTypeException;
056 import ca.uhn.hl7v2.model.Group;
057 import ca.uhn.hl7v2.model.Message;
058 import ca.uhn.hl7v2.model.Primitive;
059 import ca.uhn.hl7v2.model.Segment;
060 import ca.uhn.hl7v2.model.Structure;
061 import ca.uhn.hl7v2.model.Type;
062 import ca.uhn.hl7v2.parser.EncodingCharacters;
063 import ca.uhn.hl7v2.parser.GenericParser;
064 import ca.uhn.hl7v2.parser.Parser;
065 import ca.uhn.hl7v2.parser.PipeParser;
066 import ca.uhn.hl7v2.util.Terser;
067
068 /**
069 * A default conformance validator.
070 * @author Bryan Tripp
071 */
072 public class DefaultValidator implements Validator {
073
074 private EncodingCharacters enc; //used to check for content in parts of a message
075 private static final Logger log = LoggerFactory.getLogger(DefaultValidator.class);
076 private boolean validateChildren = true;
077 private CodeStore codeStore;
078
079
080 /** Creates a new instance of DefaultValidator */
081 public DefaultValidator() {
082 enc = new EncodingCharacters('|', null); //the | is assumed later -- don't change
083 }
084
085 /**
086 * If set to false (default is true), each testXX and validateXX method will only
087 * test the direct object it is responsible for, not its children.
088 */
089 public void setValidateChildren(boolean validateChildren) {
090 this.validateChildren = validateChildren;
091 }
092
093 /**
094 * <p>
095 * Provides a code store to use to provide the code tables which will be
096 * used to validate coded value types. If a code store has not been set (which
097 * is the default), {@link ProfileStoreFactory} will be checked for an
098 * appropriate code store, and if none is found then coded values will not
099 * be validated.
100 * </p>
101 */
102 public void setCodeStore(CodeStore theCodeStore) {
103 codeStore = theCodeStore;
104 }
105
106 /**
107 * @see Validator#validate
108 */
109 public HL7Exception[] validate(Message message, StaticDef profile) throws ProfileException, HL7Exception {
110 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
111 Terser t = new Terser(message);
112
113 //check msg type, event type, msg struct ID
114 String msgType = t.get("/MSH-9-1");
115 if (!msgType.equals(profile.getMsgType())) {
116 HL7Exception e =
117 new ProfileNotFollowedException("Message type " + msgType + " doesn't match profile type of " + profile.getMsgType());
118 exList.add(e);
119 }
120
121 String evType = t.get("/MSH-9-2");
122 if (!evType.equals(profile.getEventType()) && !profile.getEventType().equalsIgnoreCase("ALL")) {
123 HL7Exception e =
124 new ProfileNotFollowedException("Event type " + evType + " doesn't match profile type of " + profile.getEventType());
125 exList.add(e);
126 }
127
128 String msgStruct = t.get("/MSH-9-3");
129 if (msgStruct == null || !msgStruct.equals(profile.getMsgStructID())) {
130 HL7Exception e =
131 new ProfileNotFollowedException("Message structure " + msgStruct + " doesn't match profile type of " + profile.getMsgStructID());
132 exList.add(e);
133 }
134
135 // if (validateChildren) {
136 HL7Exception[] childExceptions;
137 childExceptions = doTestGroup(message, profile, profile.getIdentifier(), validateChildren);
138 for (int i = 0; i < childExceptions.length; i++) {
139 exList.add(childExceptions[i]);
140 // }
141 }
142
143 return toArray(exList);
144 }
145
146 /**
147 * Tests a group against a group section of a profile.
148 */
149 public HL7Exception[] testGroup(Group group, AbstractSegmentContainer profile, String profileID) throws ProfileException {
150 return doTestGroup(group, profile, profileID, true);
151 }
152
153 private HL7Exception[] doTestGroup(Group group, AbstractSegmentContainer profile, String profileID, boolean theValidateChildren) throws ProfileException {
154 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
155 ArrayList<String> allowedStructures = new ArrayList<String>(20);
156
157 for (int i = 1; i <= profile.getChildren(); i++) {
158 ProfileStructure struct = profile.getChild(i);
159
160 //only test a structure in detail if it isn't X
161 if (!struct.getUsage().equalsIgnoreCase("X")) {
162 allowedStructures.add(struct.getName());
163
164 //see which instances have content
165 try {
166 Structure[] instances = group.getAll(struct.getName());
167 ArrayList<Structure> instancesWithContent = new ArrayList<Structure>(10);
168 for (int j = 0; j < instances.length; j++) {
169 if (hasContent(instances[j])) instancesWithContent.add(instances[j]);
170 }
171
172 HL7Exception ce = testCardinality(instancesWithContent.size(),
173 struct.getMin(), struct.getMax(), struct.getUsage(), struct.getName());
174 if (ce != null) exList.add(ce);
175
176 //test children on instances with content
177 if (theValidateChildren) {
178 for (int j = 0; j < instancesWithContent.size(); j++) {
179 Structure s = (Structure) instancesWithContent.get(j);
180 HL7Exception[] childExceptions = testStructure(s, struct, profileID);
181 addToList(childExceptions, exList);
182 }
183 }
184
185 } catch (HL7Exception he) {
186 exList.add(new ProfileNotHL7CompliantException(struct.getName() + " not found in message"));
187 }
188 }
189 }
190
191 //complain about X structures that have content
192 addToList(checkForExtraStructures(group, allowedStructures), exList);
193
194 return toArray(exList);
195 }
196
197 /**
198 * Checks a group's children against a list of allowed structures for the group
199 * (ie those mentioned in the profile with usage other than X). Returns
200 * a list of exceptions representing structures that appear in the message
201 * but are not supposed to.
202 */
203 private HL7Exception[] checkForExtraStructures(Group group, ArrayList<String> allowedStructures) throws ProfileException {
204 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
205 String[] childNames = group.getNames();
206 for (int i = 0; i < childNames.length; i++) {
207 if (!allowedStructures.contains(childNames[i])) {
208 try {
209 Structure[] reps = group.getAll(childNames[i]);
210 for (int j = 0; j < reps.length; j++) {
211 if (hasContent(reps[j])) {
212 HL7Exception e =
213 new XElementPresentException("The structure "
214 + childNames[i]
215 + " 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 return toArray(exList);
225 }
226
227 /**
228 * Checks cardinality and creates an appropriate exception if out
229 * of bounds. The usage code is needed because if min cardinality
230 * is > 0, the min # of reps is only required if the usage code
231 * is 'R' (see HL7 v2.5 section 2.12.6.4).
232 * @param reps the number of reps
233 * @param min the minimum number of reps
234 * @param max the maximum number of reps (-1 means *)
235 * @param usage the usage code
236 * @param name the name of the repeating structure (used in exception msg)
237 * @return null if cardinality OK, exception otherwise
238 */
239 protected HL7Exception testCardinality(int reps, int min, int max, String usage, String name) {
240 HL7Exception e = null;
241 if (reps < min && usage.equalsIgnoreCase("R")) {
242 e = new ProfileNotFollowedException(name + " must have at least " + min + " repetitions (has " + reps + ")");
243 } else if (max > 0 && reps > max) {
244 e = new ProfileNotFollowedException(name + " must have no more than " + max + " repetitions (has " + reps + ")");
245 }
246 return e;
247 }
248
249 /**
250 * Tests a structure (segment or group) against the corresponding part of a profile.
251 */
252 public HL7Exception[] testStructure(Structure s, ProfileStructure profile, String profileID) throws ProfileException {
253 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
254 if (profile instanceof Seg) {
255 if (Segment.class.isAssignableFrom(s.getClass())) {
256 addToList(doTestSegment((Segment) s, (Seg) profile, profileID, validateChildren), exList);
257 } else {
258 exList.add(new ProfileNotHL7CompliantException("Mismatch between a segment in the profile and the structure "
259 + s.getClass().getName() + " in the message"));
260 }
261 } else if (profile instanceof SegGroup) {
262 if (Group.class.isAssignableFrom(s.getClass())) {
263 addToList(testGroup((Group) s, (SegGroup) profile, profileID), exList);
264 } else {
265 exList.add(new ProfileNotHL7CompliantException("Mismatch between a group in the profile and the structure "
266 + s.getClass().getName() + " in the message"));
267 }
268 }
269 return toArray(exList);
270 }
271
272 /**
273 * Tests a segment against a segment section of a profile.
274 */
275 public HL7Exception[] testSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile, String profileID) throws ProfileException {
276 return doTestSegment(segment, profile, profileID, true);
277 }
278
279 private HL7Exception[] doTestSegment(ca.uhn.hl7v2.model.Segment segment, Seg profile, String profileID, boolean theValidateChildren) throws ProfileException {
280 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
281 ArrayList<Integer> allowedFields = new ArrayList<Integer>(20);
282
283 for (int i = 1; i <= profile.getFields(); i++) {
284 Field field = profile.getField(i);
285
286 //only test a field in detail if it isn't X
287 if (!field.getUsage().equalsIgnoreCase("X")) {
288 allowedFields.add(new Integer(i));
289
290 //see which instances have content
291 try {
292 Type[] instances = segment.getField(i);
293 ArrayList<Type> instancesWithContent = new ArrayList<Type>(10);
294 for (int j = 0; j < instances.length; j++) {
295 if (hasContent(instances[j])) instancesWithContent.add(instances[j]);
296 }
297
298 HL7Exception ce = testCardinality(instancesWithContent.size(),
299 field.getMin(), field.getMax(), field.getUsage(), field.getName());
300 if (ce != null) {
301 ce.setFieldPosition(i);
302 exList.add(ce);
303 }
304
305 //test field instances with content
306 if (theValidateChildren) {
307 for (int j = 0; j < instancesWithContent.size(); j++) {
308 Type s = (Type) instancesWithContent.get(j);
309
310 boolean escape = true; //escape field value when checking length
311 if (profile.getName().equalsIgnoreCase("MSH") && i < 3) {
312 escape = false;
313 }
314 HL7Exception[] childExceptions = doTestField(s, field, escape, profileID, validateChildren);
315 for (int k = 0; k < childExceptions.length; k++) {
316 childExceptions[k].setFieldPosition(i);
317 }
318 addToList(childExceptions, exList);
319 }
320 }
321
322 } catch (HL7Exception he) {
323 exList.add(new ProfileNotHL7CompliantException("Field " + i + " not found in message"));
324 }
325 }
326
327 }
328
329 //complain about X fields with content
330 this.addToList(checkForExtraFields(segment, allowedFields), exList);
331
332 HL7Exception[] ret = toArray(exList);
333 for (int i = 0; i < ret.length; i++) {
334 ret[i].setSegmentName(profile.getName());
335 }
336 return ret;
337 }
338
339 /**
340 * Checks a segment against a list of allowed fields
341 * (ie those mentioned in the profile with usage other than X). Returns
342 * a list of exceptions representing field that appear
343 * but are not supposed to.
344 * @param allowedFields an array of Integers containing field #s of allowed fields
345 */
346 private HL7Exception[] checkForExtraFields(Segment segment, ArrayList<Integer> allowedFields) throws ProfileException {
347 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
348 for (int i = 1; i <= segment.numFields(); i++) {
349 if (!allowedFields.contains(new Integer(i))) {
350 try {
351 Type[] reps = segment.getField(i);
352 for (int j = 0; j < reps.length; j++) {
353 if (hasContent(reps[j])) {
354 HL7Exception e = new XElementPresentException(
355 "Field " + i + " in " + segment.getName() + " appears in the message but not in the profile");
356 exList.add(e);
357 }
358 }
359 } catch (HL7Exception he) {
360 throw new ProfileException("Problem testing against profile", he);
361 }
362 }
363 }
364 return this.toArray(exList);
365 }
366
367 /**
368 * Tests a Type against the corresponding section of a profile.
369 * @param encoded optional encoded form of type (if you want to specify this -- if null,
370 * default pipe-encoded form is used to check length and constant val)
371 */
372 public HL7Exception[] testType(Type type, AbstractComponent profile, String encoded, String profileID) {
373 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
374 if (encoded == null) encoded = PipeParser.encode(type, this.enc);
375
376 HL7Exception ue = testUsage(encoded, profile.getUsage(), profile.getName());
377 if (ue != null) exList.add(ue);
378
379 if ( !profile.getUsage().equals("X") ) {
380 //check datatype
381 String typeName = type.getName();
382 if (!typeName.equals(profile.getDatatype())) {
383 exList.add(new ProfileNotHL7CompliantException("HL7 datatype " + typeName + " doesn't match profile datatype " + profile.getDatatype()));
384 }
385
386 //check length
387 if (encoded.length() > profile.getLength())
388 exList.add(new ProfileNotFollowedException("The type " + profile.getName() + " has length " + encoded.length() + " which exceeds max of " + profile.getLength()));
389
390 //check constant value
391 if (profile.getConstantValue() != null && profile.getConstantValue().length() > 0) {
392 if (!encoded.equals(profile.getConstantValue()))
393 exList.add(new ProfileNotFollowedException("'" + encoded + "' doesn't equal constant value of '" + profile.getConstantValue() + "'"));
394 }
395
396 HL7Exception[] te = testTypeAgainstTable(type, profile, profileID);
397 for (int i = 0; i < te.length; i++) {
398 exList.add(te[i]);
399 }
400 }
401
402 return this.toArray(exList);
403 }
404
405 /**
406 * Tests whether the given type falls within a maximum length.
407 * @return null of OK, an HL7Exception otherwise
408 */
409 public HL7Exception testLength(Type type, int maxLength) {
410 HL7Exception e = null;
411 String encoded = PipeParser.encode(type, this.enc);
412 if (encoded.length() > maxLength) {
413 e = new ProfileNotFollowedException("Length of " + encoded.length() + " exceeds maximum of " + maxLength);
414 }
415 return e;
416 }
417
418 /**
419 * Tests an element against the corresponding usage code. The element
420 * is required in its encoded form.
421 * @param encoded the pipe-encoded message element
422 * @param usage the usage code (e.g. "CE")
423 * @param name the name of the element (for use in exception messages)
424 * @returns null if there is no problem, an HL7Exception otherwise
425 */
426 private HL7Exception testUsage(String encoded, String usage, String name) {
427 HL7Exception e = null;
428 if (usage.equalsIgnoreCase("R")) {
429 if (encoded.length() == 0)
430 e = new ProfileNotFollowedException("Required element " + name + " is missing");
431 } else if (usage.equalsIgnoreCase("RE")) {
432 //can't test anything
433 } else if (usage.equalsIgnoreCase("O")) {
434 //can't test anything
435 } else if (usage.equalsIgnoreCase("C")) {
436 //can't test anything yet -- wait for condition syntax in v2.6
437 } else if (usage.equalsIgnoreCase("CE")) {
438 //can't test anything
439 } else if (usage.equalsIgnoreCase("X")) {
440 if (encoded.length() > 0)
441 e = new XElementPresentException("Element \"" + name + "\" is present but specified as not used (X)");
442 } else if (usage.equalsIgnoreCase("B")) {
443 //can't test anything
444 }
445 return e;
446 }
447
448 /**
449 * Tests table values for ID, IS, and CE types. An empty list is returned for
450 * all other types or if the table name or number is missing.
451 */
452 private HL7Exception[] testTypeAgainstTable(Type type, AbstractComponent profile, String profileID) {
453 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
454 if (profile.getTable() != null && (type.getName().equals("IS") || type.getName().equals("ID"))) {
455 String codeSystem = makeTableName(profile.getTable());
456 String value = ((Primitive) type).getValue();
457 addTableTestResult(exList, profileID, codeSystem, value);
458 } else if (type.getName().equals("CE")) {
459 String value = Terser.getPrimitive(type, 1, 1).getValue();
460 String codeSystem = Terser.getPrimitive(type, 3, 1).getValue();
461 addTableTestResult(exList, profileID, codeSystem, value);
462
463 value = Terser.getPrimitive(type, 4, 1).getValue();
464 codeSystem = Terser.getPrimitive(type, 6, 1).getValue();
465 addTableTestResult(exList, profileID, codeSystem, value);
466 }
467 return this.toArray(exList);
468 }
469
470 private void addTableTestResult(ArrayList<HL7Exception> exList, String profileID, String codeSystem, String value) {
471 if (codeSystem != null && value != null) {
472 HL7Exception e = testValueAgainstTable(profileID, codeSystem, value);
473 if (e != null) exList.add(e);
474 }
475 }
476
477 private HL7Exception testValueAgainstTable(String profileID, String codeSystem, String value) {
478 HL7Exception ret = null;
479 if (validateChildren == false) {
480 return ret;
481 }
482
483 CodeStore store = codeStore;
484 if (codeStore == null) {
485 store = ProfileStoreFactory.getCodeStore(profileID, codeSystem);
486 }
487
488 if (store == null) {
489 log.warn("Not checking value {}: no code store was found for profile {} code system {}"
490 , new Object[] {value, profileID, codeSystem});
491 } else {
492 if (!store.knowsCodes(codeSystem)) {
493 log.warn("Not checking value {}: Don't have a table for code system {}", value, codeSystem);
494 } else if (!store.isValidCode(codeSystem, value)) {
495 ret = new ProfileNotFollowedException("Code '" + value + "' not found in table " + codeSystem + ", profile " + profileID);
496 }
497 }
498 return ret;
499 }
500
501 private String makeTableName(String hl7Table) {
502 StringBuffer buf = new StringBuffer("HL7");
503 if (hl7Table.length() < 4) buf.append("0");
504 if (hl7Table.length() < 3) buf.append("0");
505 if (hl7Table.length() < 2) buf.append("0");
506 buf.append(hl7Table);
507 return buf.toString();
508 }
509
510 public HL7Exception[] testField(Type type, Field profile, boolean escape, String profileID) throws ProfileException {
511 return doTestField(type, profile, escape, profileID, true);
512 }
513
514 private HL7Exception[] doTestField(Type type, Field profile, boolean escape, String profileID, boolean theValidateChildren) throws ProfileException {
515 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
516
517 //account for MSH 1 & 2 which aren't escaped
518 String encoded = null;
519 if (!escape && Primitive.class.isAssignableFrom(type.getClass())) encoded = ((Primitive) type).getValue();
520
521 addToList(testType(type, profile, encoded, profileID), exList);
522
523 //test children
524 if (theValidateChildren) {
525 if (profile.getComponents() > 0 && !profile.getUsage().equals("X")) {
526 if (Composite.class.isAssignableFrom(type.getClass())) {
527 Composite comp = (Composite) type;
528 for (int i = 1; i <= profile.getComponents(); i++) {
529 Component childProfile = profile.getComponent(i);
530 try {
531 Type child = comp.getComponent(i-1);
532 addToList(doTestComponent(child, childProfile, profileID, validateChildren), exList);
533 } catch (DataTypeException de) {
534 exList.add(new ProfileNotHL7CompliantException("More components in profile than allowed in message: " + de.getMessage()));
535 }
536 }
537 addToList(checkExtraComponents(comp, profile.getComponents()), exList);
538 } else {
539 exList.add(new ProfileNotHL7CompliantException(
540 "A field has type primitive " + type.getClass().getName() + " but the profile defines components"));
541 }
542 }
543 }
544
545 return toArray(exList);
546 }
547
548 public HL7Exception[] testComponent(Type type, Component profile, String profileID) throws ProfileException {
549 return doTestComponent(type, profile, profileID, true);
550 }
551
552 private HL7Exception[] doTestComponent(Type type, Component profile, String profileID, boolean theValidateChildren) throws ProfileException {
553 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
554
555 addToList(testType(type, profile, null, profileID), exList);
556
557 //test children
558 if (profile.getSubComponents() > 0 && !profile.getUsage().equals("X") && hasContent(type)) {
559 if (Composite.class.isAssignableFrom(type.getClass())) {
560 Composite comp = (Composite) type;
561
562 if (theValidateChildren) {
563 for (int i = 1; i <= profile.getSubComponents(); i++) {
564 SubComponent childProfile = profile.getSubComponent(i);
565 try {
566 Type child = comp.getComponent(i-1);
567 addToList(testType(child, childProfile, null, profileID), exList);
568 } catch (DataTypeException de) {
569 exList.add(new ProfileNotHL7CompliantException("More subcomponents in profile than allowed in message: " + de.getMessage()));
570 }
571 }
572 }
573
574 addToList(checkExtraComponents(comp, profile.getSubComponents()), exList);
575 } else {
576 exList.add(new ProfileNotFollowedException(
577 "A component has primitive type " + type.getClass().getName() + " but the profile defines subcomponents"));
578 }
579 }
580
581 return toArray(exList);
582 }
583
584 /** Tests for extra components (ie any not defined in the profile) */
585 private HL7Exception[] checkExtraComponents(Composite comp, int numInProfile) throws ProfileException {
586 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>(20);
587
588 StringBuffer extra = new StringBuffer();
589 for (int i = numInProfile; i < comp.getComponents().length; i++) {
590 try {
591 String s = PipeParser.encode(comp.getComponent(i), enc);
592 if (s.length() > 0) {
593 extra.append(s);
594 extra.append(enc.getComponentSeparator());
595 }
596 } catch (DataTypeException de) {
597 throw new ProfileException("Problem testing against profile", de);
598 }
599 }
600
601 if (extra.toString().length() > 0) {
602 exList.add(new XElementPresentException("The following components are not defined in the profile: " + extra.toString()));
603 }
604
605 return toArray(exList);
606 }
607
608 /**
609 * Tests a composite against the corresponding section of a profile.
610 */
611 /*public HL7Exception[] testComposite(Composite comp, AbstractComposite profile) {
612 }*/
613
614 /**
615 * Tests a primitive datatype against a profile. Tests include
616 * length, datatype, whether the profile defines any children
617 * (this would indicate an error), constant value if defined.
618 * Table values are not verified.
619 */
620 /*public Hl7Exception[] testPrimitive(Primitive, AbstractComponent profile) {
621
622 }*/
623
624 /** Returns true is there is content in the given structure */
625 private boolean hasContent(Structure struct) throws HL7Exception {
626 if (Group.class.isAssignableFrom(struct.getClass())) {
627 return hasContent( (Group) struct );
628 } else if (Segment.class.isAssignableFrom(struct.getClass())) {
629 return hasContent( (Segment) struct );
630 } else {
631 throw new HL7Exception("Structure " + struct.getClass().getName() + " not recognized", HL7Exception.APPLICATION_INTERNAL_ERROR);
632 }
633 }
634
635 /** Returns true is there is content in the given group */
636 private boolean hasContent(Group group) throws HL7Exception {
637 boolean has = false;
638 String encoded = PipeParser.encode(group, enc);
639 if (encoded.indexOf('|') >= 0) has = true;
640 return has;
641 }
642
643 /** Returns true is there is content in the given segment */
644 private boolean hasContent(Segment segment) {
645 boolean has = false;
646 String encoded = PipeParser.encode(segment, enc);
647 if (encoded != null && encoded.length() > 3) has = true;
648 return has;
649 }
650
651 /** Returns true is there is content in the given type */
652 private boolean hasContent(Type type) {
653 boolean has = false;
654 String encoded = PipeParser.encode(type, enc);
655 if (encoded != null && encoded.length() > 0) has = true;
656 return has;
657 }
658
659 /** Appends an array of HL7 exceptions to a list */
660 private void addToList(HL7Exception[] exceptions, ArrayList<HL7Exception> list) {
661 for (int i = 0; i < exceptions.length; i++) {
662 list.add(exceptions[i]);
663 }
664 }
665
666 /** Returns the HL7 exceptions in the given ArrayList<HL7Exception> in an array */
667 private HL7Exception[] toArray(ArrayList<HL7Exception> list) {
668 return (HL7Exception[]) list.toArray(new HL7Exception[0]);
669 }
670
671 public static void main(String args[]) {
672
673 if (args.length != 2) {
674 System.out.println("Usage: DefaultValidator message_file profile_file");
675 System.exit(1);
676 }
677
678 DefaultValidator val = new DefaultValidator();
679 try {
680 String msgString = loadFile(args[0]);
681 Parser parser = new GenericParser();
682 Message message = parser.parse(msgString);
683
684 String profileString = loadFile(args[1]);
685 ProfileParser profParser = new ProfileParser(true);
686 RuntimeProfile profile = profParser.parse(profileString);
687
688 HL7Exception[] exceptions = val.validate(message, profile.getMessage());
689
690 System.out.println("Exceptions: ");
691 for (int i = 0; i < exceptions.length; i++) {
692 System.out.println((i + 1) + ". " + exceptions[i].getMessage());
693 }
694 } catch (Exception e) {
695 e.printStackTrace();
696 }
697 }
698
699 /** loads file at the given path */
700 private static String loadFile(String path) throws IOException {
701 File file = new File(path);
702 //char[] cbuf = new char[(int) file.length()];
703 BufferedReader in = new BufferedReader(new FileReader(file));
704 StringBuffer buf = new StringBuffer(5000);
705 int c = -1;
706 while ( (c = in.read()) != -1) {
707 buf.append( (char) c );
708 }
709 //in.read(cbuf, 0, (int) file.length());
710 in.close();
711 //return String.valueOf(cbuf);
712 return buf.toString();
713 }
714
715 }