Coverage Report - ca.uhn.hl7v2.conf.check.DefaultValidator
 
Classes in this File Line Coverage Branch Coverage Complexity
DefaultValidator
73%
196/268
73%
130/176
4.704
 
 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  1
         private static final Logger log = LoggerFactory.getLogger(DefaultValidator.class);
 83  12
         private boolean validateChildren = true;
 84  
         private CodeStore codeStore;
 85  
 
 86  
         /** Creates a new instance of DefaultValidator */
 87  
         public DefaultValidator() {
 88  6
             this(new DefaultHapiContext());
 89  6
         }
 90  
         
 91  
     public DefaultValidator(HapiContext context) {
 92  12
         super(context);
 93  12
         enc = new EncodingCharacters('|', null); // the | is assumed later -- don't change
 94  12
     }        
 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  0
                 this.validateChildren = validateChildren;
 102  0
         }
 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  0
                 codeStore = theCodeStore;
 114  0
         }
 115  
 
 116  
         /**
 117  
          * @see Validator#validate
 118  
          */
 119  
         public HL7Exception[] validate(Message message, StaticDef profile) throws ProfileException,
 120  
                         HL7Exception {
 121  21
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 122  21
                 Terser t = new Terser(message);
 123  
 
 124  
                 // check msg type, event type, msg struct ID
 125  21
                 String msgType = t.get("/MSH-9-1");
 126  21
                 if (!msgType.equals(profile.getMsgType())) {
 127  0
                         HL7Exception e = new ProfileNotFollowedException("Message type " + msgType
 128  
                                         + " doesn't match profile type of " + profile.getMsgType());
 129  0
                         exList.add(e);
 130  
                 }
 131  
 
 132  21
                 String evType = t.get("/MSH-9-2");
 133  21
                 if (!evType.equals(profile.getEventType())
 134  
                                 && !profile.getEventType().equalsIgnoreCase("ALL")) {
 135  0
                         HL7Exception e = new ProfileNotFollowedException("Event type " + evType
 136  
                                         + " doesn't match profile type of " + profile.getEventType());
 137  0
                         exList.add(e);
 138  
                 }
 139  
 
 140  21
                 String msgStruct = t.get("/MSH-9-3");
 141  21
                 if (msgStruct == null || !msgStruct.equals(profile.getMsgStructID())) {
 142  0
                         HL7Exception e = new ProfileNotFollowedException("Message structure " + msgStruct
 143  
                                         + " doesn't match profile type of " + profile.getMsgStructID());
 144  0
                         exList.add(e);
 145  
                 }
 146  
 
 147  21
                 exList.addAll(doTestGroup(message, profile, profile.getIdentifier(),
 148  
                                 validateChildren));
 149  21
                 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  0
                 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  21
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 163  21
                 List<String> allowedStructures = new ArrayList<String>();
 164  
 
 165  21
                 for (ProfileStructure struct : profile) {
 166  
 
 167  
                         // only test a structure in detail if it isn't X
 168  68
                         if (!struct.getUsage().equalsIgnoreCase("X")) {
 169  66
                                 allowedStructures.add(struct.getName());
 170  
 
 171  
                                 // see which instances have content
 172  
                                 try {
 173  66
                                         List<Structure> instancesWithContent = new ArrayList<Structure>();
 174  102
                                         for (Structure instance : group.getAll(struct.getName())) {
 175  36
                                                 if (!instance.isEmpty())
 176  36
                                                         instancesWithContent.add(instance);
 177  
                                         }
 178  
 
 179  64
                                         HL7Exception ce = testCardinality(instancesWithContent.size(), struct.getMin(),
 180  
                                                         struct.getMax(), struct.getUsage(), struct.getName());
 181  64
                                         if (ce != null)
 182  4
                                                 exList.add(ce);
 183  
 
 184  
                                         // test children on instances with content
 185  64
                                         if (theValidateChildren) {
 186  64
                                                 for (Structure s : instancesWithContent) {
 187  36
                                                         List<HL7Exception> childExceptions = testStructure(s, struct, profileID);
 188  36
                             exList.addAll(childExceptions);
 189  36
                                                 }
 190  
                                         }
 191  
 
 192  2
                                 } catch (HL7Exception he) {
 193  2
                                         exList.add(new ProfileNotHL7CompliantException(struct.getName()
 194  
                                                         + " not found in message"));
 195  64
                                 }
 196  
                         }
 197  68
                 }
 198  
 
 199  
                 // complain about X structures that have content
 200  21
         exList.addAll(checkForExtraStructures(group, allowedStructures));
 201  
 
 202  21
                 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  21
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 213  369
                 for (String childName : group.getNames()) {
 214  348
                         if (!allowedStructures.contains(childName)) {
 215  
                                 try {
 216  286
                                         for (Structure rep : group.getAll(childName)) {
 217  1
                                                 if (!rep.isEmpty()) {
 218  1
                                                         HL7Exception e = new XElementPresentException("The structure "
 219  
                                                                         + childName + " appears in the message but not in the profile");
 220  1
                                                         exList.add(e);
 221  
                                                 }
 222  
                                         }
 223  0
                                 } catch (HL7Exception he) {
 224  0
                                         throw new ProfileException("Problem checking profile", he);
 225  285
                                 }
 226  
                         }
 227  
                 }
 228  21
                 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  593
                 HL7Exception e = null;
 245  593
                 if (reps < min && usage.equalsIgnoreCase("R")) {
 246  10
                         e = new ProfileNotFollowedException(name + " must have at least " + min
 247  
                                         + " repetitions (has " + reps + ")");
 248  583
                 } else if (max > 0 && reps > max) {
 249  0
                         e = new ProfileNotFollowedException(name + " must have no more than " + max
 250  
                                         + " repetitions (has " + reps + ")");
 251  
                 }
 252  593
                 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  36
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 261  36
                 if (profile instanceof Seg) {
 262  36
                         if (Segment.class.isAssignableFrom(s.getClass())) {
 263  36
                                 exList.addAll(doTestSegment((Segment) s, (Seg) profile, profileID, validateChildren));
 264  
                         } else {
 265  0
                                 exList.add(new ProfileNotHL7CompliantException(
 266  
                                                 "Mismatch between a segment in the profile and the structure "
 267  
                                                                 + s.getClass().getName() + " in the message"));
 268  
                         }
 269  0
                 } else if (profile instanceof SegGroup) {
 270  0
                         if (Group.class.isAssignableFrom(s.getClass())) {
 271  0
                 exList.addAll(testGroup((Group) s, (SegGroup) profile, profileID));
 272  
                         } else {
 273  0
                                 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  36
                 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  0
                 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  36
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 292  36
                 List<Integer> allowedFields = new ArrayList<Integer>();
 293  
 
 294  567
                 for (int i = 1; i <= profile.getFields(); i++) {
 295  531
                         Field field = profile.getField(i);
 296  
 
 297  
                         // only test a field in detail if it isn't X
 298  531
                         if (!field.getUsage().equalsIgnoreCase("X")) {
 299  529
                                 allowedFields.add(i);
 300  
 
 301  
                                 // see which instances have content
 302  
                                 try {
 303  529
                                         Type[] instances = segment.getField(i);
 304  529
                                         List<Type> instancesWithContent = new ArrayList<Type>();
 305  740
                                         for (Type instance : instances) {
 306  211
                                                 if (!instance.isEmpty())
 307  211
                                                         instancesWithContent.add(instance);
 308  
                                         }
 309  
 
 310  529
                                         HL7Exception ce = testCardinality(instancesWithContent.size(), field.getMin(),
 311  
                                                         field.getMax(), field.getUsage(), field.getName());
 312  529
                                         if (ce != null) {
 313  6
                                                 ce.setFieldPosition(i);
 314  6
                                                 exList.add(ce);
 315  
                                         }
 316  
 
 317  
                                         // test field instances with content
 318  529
                                         if (theValidateChildren) {
 319  529
                                                 for (Type s : instancesWithContent) {
 320  211
                                                         boolean escape = true; // escape field value when checking length
 321  211
                                                         if (profile.getName().equalsIgnoreCase("MSH") && i < 3) {
 322  42
                                                                 escape = false;
 323  
                                                         }
 324  211
                             List<HL7Exception> childExceptions = doTestField(s, field, escape,
 325  
                                                                         profileID, validateChildren);
 326  211
                                                         for (HL7Exception ex : childExceptions) {
 327  43
                                 ex.setFieldPosition(i);
 328  43
                                                         }
 329  211
                             exList.addAll(childExceptions);
 330  211
                                                 }
 331  
                                         }
 332  
 
 333  0
                                 } catch (HL7Exception he) {
 334  0
                                         exList.add(new ProfileNotHL7CompliantException("Field " + i
 335  
                                                         + " not found in message"));
 336  529
                                 }
 337  
                         }
 338  
 
 339  
                 }
 340  
 
 341  
                 // complain about X fields with content
 342  36
                 exList.addAll(checkForExtraFields(segment, allowedFields));
 343  
 
 344  36
                 for (HL7Exception ex : exList) {
 345  50
             ex.setSegmentName(profile.getName());
 346  50
                 }
 347  36
                 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  36
                 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
 360  567
                 for (int i = 1; i <= segment.numFields(); i++) {
 361  531
                         if (!allowedFields.contains(new Integer(i))) {
 362  
                                 try {
 363  2
                                         Type[] reps = segment.getField(i);
 364  3
                                         for (Type rep : reps) {
 365  1
                                                 if (!rep.isEmpty()) {
 366  1
                                                         HL7Exception e = new XElementPresentException("Field " + i + " in "
 367  
                                                                         + segment.getName()
 368  
                                                                         + " appears in the message but not in the profile");
 369  1
                                                         exList.add(e);
 370  
                                                 }
 371  
                                         }
 372  0
                                 } catch (HL7Exception he) {
 373  0
                                         throw new ProfileException("Problem testing against profile", he);
 374  2
                                 }
 375  
                         }
 376  
                 }
 377  36
                 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  919
                 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
 389  919
                 if (encoded == null)
 390  875
                         encoded = PipeParser.encode(type, this.enc);
 391  
 
 392  919
                 HL7Exception ue = testUsage(encoded, profile.getUsage(), profile.getName());
 393  919
                 if (ue != null)
 394  14
                         exList.add(ue);
 395  
 
 396  919
                 if (!profile.getUsage().equals("X")) {
 397  
                         // check datatype
 398  897
                         String typeName = type.getName();
 399  897
                         if (!typeName.equals(profile.getDatatype())) {
 400  9
                                 exList.add(new ProfileNotHL7CompliantException("HL7 datatype " + typeName
 401  
                                                 + " doesn't match profile datatype " + profile.getDatatype()));
 402  
                         }
 403  
 
 404  
                         // check length
 405  897
                         if (encoded.length() > profile.getLength())
 406  20
                                 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  897
                         if (profile.getConstantValue() != null && profile.getConstantValue().length() > 0) {
 412  0
                                 if (!encoded.equals(profile.getConstantValue()))
 413  0
                                         exList.add(new ProfileNotFollowedException("'" + encoded
 414  
                                                         + "' doesn't equal constant value of '" + profile.getConstantValue()
 415  
                                                         + "'"));
 416  
                         }
 417  
 
 418  897
             exList.addAll(testTypeAgainstTable(type, profile, profileID));
 419  
                 }
 420  
 
 421  919
                 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  0
                 HL7Exception e = null;
 431  0
                 String encoded = PipeParser.encode(type, this.enc);
 432  0
                 if (encoded.length() > maxLength) {
 433  0
                         e = new ProfileNotFollowedException("Length of " + encoded.length()
 434  
                                         + " exceeds maximum of " + maxLength);
 435  
                 }
 436  0
                 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  919
                 HL7Exception e = null;
 450  919
                 if (usage.equalsIgnoreCase("R")) {
 451  227
                         if (encoded.length() == 0)
 452  12
                                 e = new ProfileNotFollowedException("Required element " + name + " is missing");
 453  692
                 } else if (usage.equalsIgnoreCase("RE")) {
 454  
                         // can't test anything
 455  692
                 } else if (usage.equalsIgnoreCase("O")) {
 456  
                         // can't test anything
 457  93
                 } else if (usage.equalsIgnoreCase("C")) {
 458  
                         // can't test anything yet -- wait for condition syntax in v2.6
 459  31
                 } else if (usage.equalsIgnoreCase("CE")) {
 460  
                         // can't test anything
 461  31
                 } else if (usage.equalsIgnoreCase("X")) {
 462  22
                         if (encoded.length() > 0)
 463  2
                                 e = new XElementPresentException("Element \"" + name
 464  
                                                 + "\" is present but specified as not used (X)");
 465  9
                 } else if (usage.equalsIgnoreCase("B")) {
 466  
                         // can't test anything
 467  
                 }
 468  919
                 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  897
                 ArrayList<HL7Exception> exList = new ArrayList<HL7Exception>();
 478  897
                 if (profile.getTable() != null
 479  
                                 && (type.getName().equals("IS") || type.getName().equals("ID"))) {
 480  366
                         String codeSystem = makeTableName(profile.getTable());
 481  366
                         String value = ((Primitive) type).getValue();
 482  366
                         addTableTestResult(exList, profileID, codeSystem, value);
 483  366
                 } else if (type.getName().equals("CE")) {
 484  43
                         String value = Terser.getPrimitive(type, 1, 1).getValue();
 485  43
                         String codeSystem = Terser.getPrimitive(type, 3, 1).getValue();
 486  43
                         addTableTestResult(exList, profileID, codeSystem, value);
 487  
 
 488  43
                         value = Terser.getPrimitive(type, 4, 1).getValue();
 489  43
                         codeSystem = Terser.getPrimitive(type, 6, 1).getValue();
 490  43
                         addTableTestResult(exList, profileID, codeSystem, value);
 491  
                 }
 492  897
                 return exList;
 493  
         }
 494  
 
 495  
         private void addTableTestResult(List<HL7Exception> exList, String profileID,
 496  
                         String codeSystem, String value) {
 497  452
                 if (codeSystem != null && value != null) {
 498  147
                         HL7Exception e = testValueAgainstTable(profileID, codeSystem, value);
 499  147
                         if (e != null)
 500  1
                                 exList.add(e);
 501  
                 }
 502  452
         }
 503  
 
 504  
         private HL7Exception testValueAgainstTable(String profileID, String codeSystem, String value) {
 505  147
                 HL7Exception ret = null;
 506  147
                 if (!validateChildren) {
 507  0
                         return ret;
 508  
                 }
 509  
 
 510  147
                 CodeStore store = codeStore;
 511  147
                 if (codeStore == null) {
 512  147
                         store = getHapiContext().getCodeStoreRegistry().getCodeStore(profileID, codeSystem);
 513  
                 }
 514  
 
 515  147
                 if (store == null) {
 516  142
                         log.warn(
 517  
                                         "Not checking value {}: no code store was found for profile {} code system {}",
 518  
                                         new Object[] { value, profileID, codeSystem });
 519  
                 } else {
 520  5
                         if (!store.knowsCodes(codeSystem)) {
 521  0
                                 log.warn("Not checking value {}: Don't have a table for code system {}", value,
 522  
                                                 codeSystem);
 523  5
                         } else if (!store.isValidCode(codeSystem, value)) {
 524  1
                                 ret = new ProfileNotFollowedException("Code '" + value + "' not found in table "
 525  
                                                 + codeSystem + ", profile " + profileID);
 526  
                         }
 527  
                 }
 528  147
                 return ret;
 529  
         }
 530  
 
 531  
         private String makeTableName(String hl7Table) {
 532  366
         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  3
                 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  214
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 543  
 
 544  
                 // account for MSH 1 & 2 which aren't escaped
 545  214
                 String encoded = null;
 546  214
                 if (!escape && Primitive.class.isAssignableFrom(type.getClass()))
 547  44
                         encoded = ((Primitive) type).getValue();
 548  
 
 549  214
                 exList.addAll(testType(type, profile, encoded, profileID));
 550  
 
 551  
                 // test children
 552  214
                 if (theValidateChildren) {
 553  214
                         if (profile.getComponents() > 0 && !profile.getUsage().equals("X")) {
 554  124
                                 if (Composite.class.isAssignableFrom(type.getClass())) {
 555  124
                                         Composite comp = (Composite) type;
 556  523
                                         for (int i = 1; i <= profile.getComponents(); i++) {
 557  399
                                                 Component childProfile = profile.getComponent(i);
 558  
                                                 try {
 559  399
                                                         Type child = comp.getComponent(i - 1);
 560  399
                                                         exList.addAll(doTestComponent(child, childProfile, profileID, validateChildren));
 561  0
                                                 } catch (DataTypeException de) {
 562  0
                                                         exList.add(new ProfileNotHL7CompliantException(
 563  
                                                                         "More components in profile than allowed in message: "
 564  
                                                                                         + de.getMessage()));
 565  399
                                                 }
 566  
                                         }
 567  124
                                         exList.addAll(checkExtraComponents(comp, profile.getComponents()));
 568  124
                                 } else {
 569  0
                                         exList.add(new ProfileNotHL7CompliantException("A field has type primitive "
 570  
                                                         + type.getClass().getName() + " but the profile defines components"));
 571  
                                 }
 572  
                         }
 573  
                 }
 574  
 
 575  214
                 return exList;
 576  
         }
 577  
 
 578  
         public List<HL7Exception> testComponent(Type type, Component profile, String profileID)
 579  
                         throws ProfileException {
 580  0
                 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  399
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 586  399
                 exList.addAll(testType(type, profile, null, profileID));
 587  
 
 588  
                 // test children
 589  399
                 if (profile.getSubComponents() > 0 && !profile.getUsage().equals("X") && (!type.isEmpty())) {
 590  60
                         if (Composite.class.isAssignableFrom(type.getClass())) {
 591  60
                                 Composite comp = (Composite) type;
 592  
 
 593  60
                                 if (theValidateChildren) {
 594  366
                                         for (int i = 1; i <= profile.getSubComponents(); i++) {
 595  306
                                                 SubComponent childProfile = profile.getSubComponent(i);
 596  
                                                 try {
 597  306
                                                         Type child = comp.getComponent(i - 1);
 598  306
                                                         exList.addAll(testType(child, childProfile, null, profileID));
 599  0
                                                 } catch (DataTypeException de) {
 600  0
                                                         exList.add(new ProfileNotHL7CompliantException(
 601  
                                                                         "More subcomponents in profile than allowed in message: "
 602  
                                                                                         + de.getMessage()));
 603  306
                                                 }
 604  
                                         }
 605  
                                 }
 606  
 
 607  60
                 exList.addAll(checkExtraComponents(comp, profile.getSubComponents()));
 608  60
                         } else {
 609  0
                                 exList.add(new ProfileNotFollowedException("A component has primitive type "
 610  
                                                 + type.getClass().getName() + " but the profile defines subcomponents"));
 611  
                         }
 612  
                 }
 613  
 
 614  399
                 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  184
                 List<HL7Exception> exList = new ArrayList<HL7Exception>();
 621  
 
 622  184
                 StringBuffer extra = new StringBuffer();
 623  184
                 for (int i = numInProfile; i < comp.getComponents().length; i++) {
 624  
                         try {
 625  0
                                 String s = PipeParser.encode(comp.getComponent(i), enc);
 626  0
                                 if (s.length() > 0) {
 627  0
                                         extra.append(s).append(enc.getComponentSeparator());
 628  
                                 }
 629  0
                         } catch (DataTypeException de) {
 630  0
                                 throw new ProfileException("Problem testing against profile", de);
 631  0
                         }
 632  
                 }
 633  
 
 634  184
                 if (extra.toString().length() > 0) {
 635  0
                         exList.add(new XElementPresentException(
 636  
                                         "The following components are not defined in the profile: " + extra.toString()));
 637  
                 }
 638  
 
 639  184
                 return exList;
 640  
         }
 641  
 
 642  
         public static void main(String args[]) {
 643  
 
 644  0
                 if (args.length != 2) {
 645  0
                         System.out.println("Usage: DefaultValidator message_file profile_file");
 646  0
                         System.exit(1);
 647  
                 }
 648  
 
 649  0
                 DefaultValidator val = new DefaultValidator();
 650  
                 try {
 651  0
                         String msgString = loadFile(args[0]);
 652  0
                         Parser parser = new GenericParser();
 653  0
                         Message message = parser.parse(msgString);
 654  
 
 655  0
                         String profileString = loadFile(args[1]);
 656  0
                         ProfileParser profParser = new ProfileParser(true);
 657  0
                         RuntimeProfile profile = profParser.parse(profileString);
 658  
 
 659  0
                         HL7Exception[] exceptions = val.validate(message, profile.getMessage());
 660  
 
 661  0
                         System.out.println("Exceptions: ");
 662  0
                         for (int i = 0; i < exceptions.length; i++) {
 663  0
                                 System.out.println((i + 1) + ". " + exceptions[i].getMessage());
 664  
                         }
 665  0
                 } catch (Exception e) {
 666  0
                         e.printStackTrace();
 667  0
                 }
 668  0
         }
 669  
 
 670  
         /** loads file at the given path */
 671  
         private static String loadFile(String path) throws IOException {
 672  0
                 File file = new File(path);
 673  
                 // char[] cbuf = new char[(int) file.length()];
 674  0
                 BufferedReader in = new BufferedReader(new FileReader(file));
 675  0
                 StringBuffer buf = new StringBuffer(5000);
 676  
                 int c;
 677  0
                 while ((c = in.read()) != -1) {
 678  0
                         buf.append((char) c);
 679  
                 }
 680  
                 // in.read(cbuf, 0, (int) file.length());
 681  0
                 in.close();
 682  
                 // return String.valueOf(cbuf);
 683  0
                 return buf.toString();
 684  
         }
 685  
 
 686  
 }