/** * Tests the group (or message) against a list of profile descriptions that are either a {@link * org.openehealth.ipf.gazelle.validation.core.stub.SegmentType} or a {@link * org.openehealth.ipf.gazelle.validation.core.stub.HL7V2XStaticDef.SegGroup}. * * @param group current message/group element * @param profile available profile objcts to test the element against * @return a list with identified violations against the profile(s) */ protected List<ValidationException> testGroup(Group group, List<Object> profile) { List<ValidationException> exList = new ArrayList<>(); List<String> allowedStructures = new ArrayList<>(); for (Object struct : profile) { UsageInfo usage = new UsageInfo(struct); if (!usage.disallowed()) { allowedStructures.add(usage.name); try { List<Structure> nonEmptyStructures = nonEmptyStructure(group.getAll(usage.name)); exList.addAll(testCardinality(nonEmptyStructures.size(), usage)); // test children on instances with content if (validateChildren) { for (Structure structure : nonEmptyStructures) { exList.addAll(testStructure(structure, struct)); } } } catch (HL7Exception he) { profileNotHL7Compliant(exList, PROFILE_STRUCTURE_NOT_EXIST_IN_JAVA_CLASS, usage.name); } } } // complain about X structures that have content exList.addAll(checkForExtraStructures(group, allowedStructures)); return exList; }
protected List<ValidationException> testField( Type type, SegmentType.Field profile, boolean escape) { List<ValidationException> exList = new ArrayList<>(); UsageInfo usage = new UsageInfo(profile); // account for MSH 1 & 2 which aren't escaped String encoded = null; if (!escape && Primitive.class.isAssignableFrom(type.getClass())) encoded = ((Primitive) type).getValue(); exList.addAll(testType(type, profile.getDatatype(), usage, encoded, false)); // test children if (validateChildren) { if (profile.getComponents().size() > 0 && !usage.disallowed()) { if (Composite.class.isAssignableFrom(type.getClass())) { Composite comp = (Composite) type; int i = 1; boolean nullContext = false; for (SegmentType.Field.Component component : profile.getComponents()) { try { SegmentType.Field.Component component2; if (nullContext) { component2 = new SegmentType.Field.Component(); try { BeanUtils.copyProperties(component2, component); } catch (InvocationTargetException | IllegalAccessException e) { // nop } component2.setUsage("NULL"); } else { component2 = component; if ((i == 1) && profile.isNullable() && PipeParser.encode(comp.getComponent(0), this.enc).equals("\"\"")) { nullContext = true; } } exList.addAll(testComponent(comp.getComponent(i - 1), component2)); } catch (DataTypeException de) { profileNotHL7Compliant(exList, COMPONENT_TYPE_MISMATCH, type.getName(), i); } ++i; } exList.addAll(checkUndefinedComponents(comp, profile.getComponents().size())); } else { profileNotHL7Compliant(exList, WRONG_FIELD_TYPE, type.getClass().getName()); } } } return exList; }
protected List<ValidationException> testSegment(Segment segment, SegmentType profile) { List<ValidationException> exList = new ArrayList<>(); List<Integer> allowedFields = new ArrayList<>(); int i = 1; for (SegmentType.Field field : profile.getFields()) { UsageInfo usage = new UsageInfo(field); // only test a field in detail if it isn't X if (!usage.disallowed()) { allowedFields.add(i); // see which instances have content try { Collection<Type> nonEmptyFields = nonEmptyField(segment.getField(i)); exList.addAll(testCardinality(nonEmptyFields.size(), usage)); // test field instances with content if (validateChildren) { for (Type type : nonEmptyFields) { boolean escape = true; // escape field value when checking length if (profile.getName().equalsIgnoreCase("MSH") && i < 3) { escape = false; } List<ValidationException> childExceptions = testField(type, field, escape); for (ValidationException ex : childExceptions) { ex.setFieldPosition(i); } exList.addAll(childExceptions); } } } catch (HL7Exception he) { profileNotHL7Compliant(exList, FIELD_NOT_FOUND, i); } } ++i; } // complain about X fields with content exList.addAll(checkForExtraFields(segment, allowedFields)); for (ValidationException ex : exList) { ex.setSegmentName(profile.getName()); } return exList; }
/** * Checks cardinality and creates an appropriate exception if out of bounds. The usage code is * needed because if min cardinality is > 0, the min # of reps is only required if the usage code * is 'R' (see HL7 v2.5 section 2.12.6.4). * * @param reps the number of reps * @param usage usage info * @return exceptions */ protected List<ValidationException> testCardinality(int reps, UsageInfo usage) { List<ValidationException> violations = new ArrayList<>(); profileViolatedWhen( reps < usage.min && usage.required(), violations, LESS_THAN_MINIMUM_CARDINALITY, usage.name, usage.min, reps); profileViolatedWhen( usage.max > 0 && reps > usage.max, violations, MORE_THAN_MAXIMUM_CARDINALITY, usage.name, usage.max, reps); profileViolatedWhen( reps > 0 && usage.disallowed(), violations, NOT_SUPPORTED_ELEMENT_PRESENT, usage.name); return violations; }
/** * Tests an element against the corresponding usage code. The element is required in its encoded * form. * * @param encoded the pipe-encoded message element * @param usage the usage code (e.g. "CE") */ protected void testUsage(List<ValidationException> exList, String encoded, UsageInfo usage) { if (usage.required()) { profileViolatedWhen(encoded.isEmpty(), exList, REQUIRED_ELEMENT_MISSING, usage.name); } else if (usage.disallowed()) { profileViolatedWhen(!encoded.isEmpty(), exList, NOT_SUPPORTED_ELEMENT_PRESENT, usage.name); } else if (usage.nullContext()) { profileViolatedWhen(!encoded.isEmpty(), exList, NO_ELEMENTS_AFTER_NULL, usage.name); } /* else if (usage.equalsIgnoreCase("RE")) { // can't test anything } else if (usage.equalsIgnoreCase("O")) { // can't test anything } else if (usage.equalsIgnoreCase("C")) { // can't test anything yet -- wait for condition syntax in v2.6 } else if (usage.equalsIgnoreCase("CE")) { // can't test anything } else if (usage.equalsIgnoreCase("B")) { // can't test anything } */ }
protected List<ValidationException> testComponent( Type type, SegmentType.Field.Component profile) { List<ValidationException> exList = new ArrayList<>(); UsageInfo usage = new UsageInfo(profile); exList.addAll(testType(type, profile.getDatatype(), usage, null)); // test children try { if (profile.getSubComponents().size() > 0 && !usage.disallowed() && !isEmpty(type)) { if (Composite.class.isAssignableFrom(type.getClass())) { Composite comp = (Composite) type; if (validateChildren) { int i = 1; for (SegmentType.Field.Component.SubComponent subComponent : profile.getSubComponents()) { UsageInfo scUsage = new UsageInfo(subComponent); try { Type sub = comp.getComponent(i - 1); exList.addAll(testType(sub, subComponent.getDatatype(), scUsage, null)); } catch (DataTypeException de) { profileNotHL7Compliant(exList, SUBCOMPONENT_TYPE_MISMATCH, type.getName(), i); } ++i; } } exList.addAll(checkUndefinedComponents(comp, profile.getSubComponents().size())); } else { profileViolatedWhen(true, exList, WRONG_COMPONENT_TYPE, type.getClass().getName()); } } } catch (HL7Exception e) { exList.add(new ValidationException(e)); } return exList; }
/** * Tests a Type against the corresponding section of a profile. * * @param encoded optional encoded form of type (if you want to specify this -- if null, default * pipe-encoded form is used to check length and constant val) */ protected List<ValidationException> testType( Type type, String dataType, UsageInfo usage, String encoded, boolean testUsage) { ArrayList<ValidationException> exList = new ArrayList<>(); if (encoded == null) encoded = PipeParser.encode(type, this.enc); if (testUsage) { testUsage(exList, encoded, usage); } if (!usage.disallowed() && !encoded.isEmpty()) { // check datatype if ((type instanceof ca.uhn.hl7v2.model.v231.datatype.TSComponentOne || type instanceof ca.uhn.hl7v2.model.v24.datatype.TSComponentOne) && !dataType.equals("ST")) { profileNotHL7Compliant(exList, HL7_DATATYPE_MISMATCH, type.getName(), dataType); } else if (!(type instanceof TSComponentOne) && !type.getName().contains(dataType)) { profileViolatedWhen( !(type.getClass().getSimpleName().equals("Varies") || type.getClass().getSimpleName().equals("QIP")), exList, HL7_DATATYPE_MISMATCH, type.getName(), dataType); } // check length profileViolatedWhen( encoded.length() > usage.length, exList, LENGTH_EXCEEDED, usage.name, encoded.length(), usage.length); // check constant value if (usage.constantValue != null && usage.constantValue.length() > 0) { profileViolatedWhen( !encoded.equals(usage.constantValue), exList, WRONG_CONSTANT_VALUE, encoded, usage.constantValue); } // TODO : check against table, or do we need this check? // Gazelle checks code system and issues a WARNING if a check fails } return exList; }