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