
001package org.hl7.fhir.r5.conformance.profile; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.Set; 006 007import org.hl7.fhir.exceptions.FHIRException; 008import org.hl7.fhir.r5.context.IWorkerContext; 009import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 010import org.hl7.fhir.r5.extensions.ExtensionUtilities; 011import org.hl7.fhir.r5.model.BooleanType; 012import org.hl7.fhir.r5.model.CanonicalType; 013import org.hl7.fhir.r5.model.DataType; 014import org.hl7.fhir.r5.model.DateTimeType; 015import org.hl7.fhir.r5.model.DecimalType; 016import org.hl7.fhir.r5.model.ElementDefinition; 017import org.hl7.fhir.r5.model.ElementDefinition.ConstraintSeverity; 018import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent; 019import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent; 020import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; 021import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 022import org.hl7.fhir.r5.model.Enumerations.BindingStrength; 023import org.hl7.fhir.r5.model.Extension; 024import org.hl7.fhir.r5.model.Property; 025import org.hl7.fhir.r5.model.Quantity; 026import org.hl7.fhir.r5.model.StringType; 027import org.hl7.fhir.r5.model.StructureDefinition; 028import org.hl7.fhir.r5.model.ValueSet; 029import org.hl7.fhir.r5.terminologies.ValueSetUtilities; 030import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome; 031import org.hl7.fhir.r5.utils.DefinitionNavigator; 032import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 033import org.hl7.fhir.utilities.UUIDUtilities; 034import org.hl7.fhir.utilities.Utilities; 035import org.hl7.fhir.utilities.i18n.I18nConstants; 036import org.hl7.fhir.utilities.validation.ValidationMessage; 037import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 038import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 039import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 040 041public class CompliesWithChecker { 042 043 private IWorkerContext context; 044 private String id; 045 046 public CompliesWithChecker(IWorkerContext context) { 047 super(); 048 this.context = context; 049 this.id = UUIDUtilities.makeUuidLC(); 050 } 051 052 public List<ValidationMessage> checkCompliesWith(StructureDefinition claimee, StructureDefinition authority) { 053 List<ValidationMessage> messages = new ArrayList<ValidationMessage>(); 054 055 DefinitionNavigator cn = new DefinitionNavigator(context, claimee, false, true); 056 DefinitionNavigator an = new DefinitionNavigator(context, authority, false, true); 057 058 String path = claimee.getType(); 059 if (!path.equals(authority.getType())) { 060 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_WRONG_TYPE, path, authority.getType()), IssueSeverity.ERROR)); 061 } else { 062 checkCompliesWith(messages, path, cn, an, false); 063 } 064 return messages; 065 } 066 067 private void checkCompliesWith(List<ValidationMessage> messages, String path, DefinitionNavigator claimee, DefinitionNavigator authority, boolean isSlice) { 068 ElementDefinition c = claimee.current(); 069 ElementDefinition a = authority.current(); 070 if (checkElementComplies(messages, path, c, a, isSlice)) { 071 072 // if the type and children are the same on both sides, we can stop checking 073 if (!typesIdentical(c, a) || claimee.hasInlineChildren() || authority.hasInlineChildren()) { 074 // in principle, there is always children, but if the profile 075 // doesn't walk into them, and there's more than one type, the 076 // 077 for (int i = 0; i < authority.children().size(); i++) { 078 DefinitionNavigator anChild = authority.children().get(i); 079 checkCompilesWith(messages, path, claimee, anChild); 080 } 081 } 082 } 083 } 084 085 private void checkCompilesWith(List<ValidationMessage> messages, String path, DefinitionNavigator claimee, DefinitionNavigator anChild) { 086 DefinitionNavigator cnChild = claimee.childByName(anChild.current().getName()); 087 String cpath = path+"."+anChild.current().getName(); 088 if (cnChild == null) { 089 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_MISSING, anChild.globalPath()), IssueSeverity.ERROR)); 090 } else if (anChild.sliced() || cnChild.sliced()) { 091 if (!cnChild.hasSlices()) { 092 if (anChild.hasSlices()) { 093 // do we care? if the authority slicing is closed, or any are mandatory 094 boolean wecare = anChild.current().getSlicing().getRules() == SlicingRules.CLOSED; 095 for (DefinitionNavigator anSlice : anChild.slices()) { 096 wecare = wecare || anSlice.current().getMin() > 0; 097 } 098 if (wecare) { 099 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 100 context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_UNSLICED, cpath), IssueSeverity.ERROR)); 101 } 102 } 103 checkCompliesWith(messages, cpath, cnChild, anChild, false); 104 } else if (!anChild.hasSlices()) { 105 for (DefinitionNavigator cc : cnChild.slices()) { 106 checkCompliesWith(messages, cpath+":"+cnChild.current().getSliceName(), cc, anChild, true); 107 } 108 } else { 109 checkByDiscriminator(messages, anChild, cpath, cnChild); 110 } 111 } else { 112 checkCompliesWith(messages, cpath, cnChild, anChild, false); 113 } 114 } 115 116 private void checkByDiscriminator(List<ValidationMessage> messages, DefinitionNavigator anChild, String cpath, DefinitionNavigator cnChild) { 117 List<ElementDefinitionSlicingDiscriminatorComponent> discriminators = new ArrayList<>(); 118 if (slicingCompliesWith(messages, cpath, anChild.current(), cnChild.current(), discriminators)) { 119 List<DefinitionNavigator> processed = new ArrayList<DefinitionNavigator>(); 120 for (DefinitionNavigator anSlice : anChild.slices()) { 121 String spath = cpath +":"+anSlice.current().getSliceName(); 122 List<DataType> discriminatorValues = new ArrayList<>(); 123 List<DefinitionNavigator> cnSlices = findMatchingSlices(cnChild.slices(), discriminators, anSlice, discriminatorValues); 124 if (cnSlices.isEmpty() && anSlice.current().getSlicing().getRules() != SlicingRules.CLOSED) { 125 // if it's closed, then we just don't have any. But if it's not closed, we need the slice 126 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 127 context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_NO_SLICE, spath, discriminatorsToString(discriminators), 128 valuesToString(discriminatorValues)), IssueSeverity.ERROR)); 129 } 130 for (DefinitionNavigator cnSlice : cnSlices) { 131 spath = cpath +":"+cnSlice.current().getSliceName(); 132 if (!processed.contains(cnSlice)) { 133 // it's weird if it does - is that a problem? 134 processed.add(cnSlice); 135 } 136 checkCompliesWith(messages, spath, cnSlice, anSlice, false); 137 } 138 } 139 for (DefinitionNavigator cnSlice : cnChild.slices()) { 140 if (!processed.contains(cnSlice)) { 141 if (anChild.current().getSlicing().getRules() != SlicingRules.OPEN) { 142 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, cpath, 143 context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_EXTRA_SLICE, cpath, cnSlice.current().getSliceName()), IssueSeverity.ERROR)); 144 } 145 String spath = cpath +":"+cnSlice.current().getSliceName(); 146 checkCompliesWith(messages, spath, cnSlice, anChild, true); 147 } 148 } 149 } 150 } 151 152 private Object discriminatorsToString(List<ElementDefinitionSlicingDiscriminatorComponent> discriminators) { 153 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 154 for (ElementDefinitionSlicingDiscriminatorComponent dt : discriminators) { 155 b.append(dt.getType().toCode()+":"+dt.getPath()); 156 } 157 return b.toString(); 158 } 159 160 private String valuesToString(List<DataType> diffValues) { 161 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("|"); 162 for (DataType dt : diffValues) { 163 if (dt != null) { 164 b.append(dt.toString()); 165 } 166 } 167 return b.toString(); 168 } 169 170 private List<DefinitionNavigator> findMatchingSlices(List<DefinitionNavigator> slices, List<ElementDefinitionSlicingDiscriminatorComponent> discriminators, DefinitionNavigator anSlice, List<DataType> discriminatorValues) { 171 List<DefinitionNavigator> list = new ArrayList<DefinitionNavigator>(); 172 for (ElementDefinitionSlicingDiscriminatorComponent ad : discriminators) { 173 // first, determine the values for each of the discriminators in the authority slice 174 discriminatorValues.add(getDisciminatorValue(anSlice, ad)); 175 } 176 for (DefinitionNavigator slice : slices) { 177 List<DataType> values = new ArrayList<>(); 178 for (ElementDefinitionSlicingDiscriminatorComponent ad : discriminators) { 179 // first, determine the values for each of the discriminators in the authority slice 180 values.add(getDisciminatorValue(slice, ad)); 181 } 182 boolean ok = true; 183 for (int i = 0; i < discriminators.size(); i++) { 184 if (!isMatch(discriminators.get(i), discriminatorValues.get(i), values.get(i))) { 185 ok = false; 186 break; 187 } 188 } 189 if (ok) { 190 list.add(slice); 191 } 192 } 193 return list; 194 } 195 196 private boolean isMatch(ElementDefinitionSlicingDiscriminatorComponent discriminator, DataType dt1, DataType dt2) { 197 if (dt1 == null) { 198 return dt2 == null; 199 } else if (dt2 == null) { 200 return false; 201 } else { 202 switch (discriminator.getType()) { 203 case EXISTS: return false; 204 case NULL: return false; 205 case PATTERN: return dt1.equalsDeep(dt2); // todo 206 case POSITION: return false; 207 case PROFILE: 208 StructureDefinition sd1 = context.fetchResource(StructureDefinition.class, dt1.primitiveValue()); 209 StructureDefinition sd2 = context.fetchResource(StructureDefinition.class, dt2.primitiveValue()); 210 if (sd1 == null || sd2 == null) { 211 return false; 212 } 213 for (Extension ex : sd2.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_COMPLIES_WITH_PROFILE)) { 214 String url = ex.getValue().primitiveValue(); 215 if (url != null) { 216 StructureDefinition sde = sd1; 217 while (sde != null) { 218 if (url.equals(sde.getUrl()) || url.equals(sde.getVersionedUrl())) { 219 return true; 220 } 221 sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition()); 222 } 223 } 224 } 225 for (Extension ex : sd2.getExtensionsByUrl(ExtensionDefinitions.EXT_SD_IMPOSE_PROFILE)) { 226 String url = ex.getValue().primitiveValue(); 227 if (url != null) { 228 StructureDefinition sde = sd1; 229 while (sde != null) { 230 if (url.equals(sde.getUrl()) || url.equals(sde.getVersionedUrl())) { 231 return true; 232 } 233 sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition()); 234 } 235 } 236 } 237 StructureDefinition sde = sd1; 238 while (sde != null) { 239 if (sd2.getVersionedUrl().equals(sde.getVersionedUrl())) { 240 return true; 241 } 242 sde = context.fetchResource(StructureDefinition.class, sde.getBaseDefinition()); 243 } 244 return false; 245 case TYPE: return dt1.primitiveValue().equals(dt2.primitiveValue()); 246 case VALUE: return dt1.equalsDeep(dt2); 247 default: 248 return false; 249 250 } 251 } 252 } 253 254 private DataType getDisciminatorValue(DefinitionNavigator anSlice, ElementDefinitionSlicingDiscriminatorComponent ad) { 255 switch (ad.getType()) { 256 case EXISTS: return getExistsDiscriminatorValue(anSlice, ad.getPath()); 257 case NULL:throw new FHIRException("Discriminator type 'Null' Not supported yet"); 258 case POSITION:throw new FHIRException("Discriminator type 'Position' Not supported yet"); 259 case PROFILE: return getProfileDiscriminatorValue(anSlice, ad.getPath()); 260 case TYPE: return getTypeDiscriminatorValue(anSlice, ad.getPath()); 261 case PATTERN: 262 case VALUE: return getValueDiscriminatorValue(anSlice, ad.getPath()); 263 default: 264 throw new FHIRException("Not supported yet"); 265 } 266 } 267 268 private DataType getProfileDiscriminatorValue(DefinitionNavigator anSlice, String path) { 269 DefinitionNavigator pathDN = getByPath(anSlice, path); 270 if (pathDN == null) { 271 return null; 272 } 273 ElementDefinition ed = pathDN.current(); 274 if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile()) { 275 return null; 276 } else { 277 return new CanonicalType(ed.getTypeFirstRep().getProfile().get(0).asStringValue()); 278 } 279 } 280 281 private DataType getExistsDiscriminatorValue(DefinitionNavigator anSlice, String path) { 282 DefinitionNavigator pathDN = getByPath(anSlice, path); 283 if (pathDN == null) { 284 return null; 285 } 286 ElementDefinition ed = pathDN.current(); 287 DataType dt = new BooleanType("1".equals(ed.getMax())); 288 return dt; 289 } 290 291 private DataType getTypeDiscriminatorValue(DefinitionNavigator anSlice, String path) { 292 DefinitionNavigator pathDN = getByPath(anSlice, path); 293 if (pathDN == null) { 294 return null; 295 } 296 ElementDefinition ed = pathDN.current(); 297 DataType dt = new StringType(ed.typeSummary()); 298 return dt; 299 } 300 301 private DataType getValueDiscriminatorValue(DefinitionNavigator anSlice, String path) { 302 DefinitionNavigator pathDN = getByPath(anSlice, path); 303 if (pathDN == null) { 304 return null; 305 } 306 ElementDefinition ed = pathDN.current(); 307 DataType dt = ed.hasFixed() ? ed.getFixed() : ed.getPattern(); 308 return dt; 309 } 310 311 private DefinitionNavigator getByPath(DefinitionNavigator focus, String path) { 312 String segment = path.contains(".") ? path.substring(0, path.indexOf(".")) : path; 313 if ("$this".equals(segment)) { 314 return focus; 315 } 316 DefinitionNavigator p = focus.childByName(segment); 317 if (p != null && path.contains(".")) { 318 return getByPath(p, path.substring(path.indexOf(".")+1)); 319 } else { 320 // we might need to look at the profile pointed to from the type 321 322 return p; 323 } 324 } 325 326 private boolean slicingCompliesWith(List<ValidationMessage> messages, String path, ElementDefinition a, ElementDefinition c, List<ElementDefinitionSlicingDiscriminatorComponent> discriminators) { 327 // the child must be sliced the same as the authority 328 if (!(a.getSlicing().getRules() == SlicingRules.OPEN || c.getSlicing().getRules() == a.getSlicing().getRules())) { 329 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_RULES, path, a.getSlicing().getRules().toCode(), c.getSlicing().getRules().toCode()), IssueSeverity.ERROR)); 330 return false; 331 } else if (a.getSlicing().getOrdered() && !c.getSlicing().getOrdered()) { 332 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_ORDER, path), IssueSeverity.ERROR)); 333 return false; 334 } else { 335 // every discriminator that the authority has, the child has to have. The order of discriminators doesn't matter 336 for (ElementDefinitionSlicingDiscriminatorComponent ad : a.getSlicing().getDiscriminator()) { 337 discriminators.add(ad); 338 ElementDefinitionSlicingDiscriminatorComponent cd = null; 339 for (ElementDefinitionSlicingDiscriminatorComponent t : c.getSlicing().getDiscriminator()) { 340 if (t.getType() == ad.getType() && t.getPath().equals(ad.getPath())) { 341 cd = t; 342 } 343 } 344 if (cd == null) { 345 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_SLICING_DISCRIMINATOR, path, ad.getType(), ad.getPath()), IssueSeverity.ERROR)); 346 return false; 347 } 348 } 349 return true; 350 } 351 } 352 353 private boolean checkElementComplies(List<ValidationMessage> messages, String path, ElementDefinition c, ElementDefinition a, boolean inSlice) { 354 boolean doInner = true; 355 if (!inSlice) { 356 if (a.getMin() > c.getMin()) { 357 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "min", a.getMin(), c.getMin(), c.getId()), IssueSeverity.ERROR)); 358 } 359 } 360 if (a.getMaxAsInt() < c.getMaxAsInt()) { 361 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "max", a.getMax(), c.getMax(), c.getId()), IssueSeverity.ERROR)); 362 } 363 if (a.hasFixed()) { 364 if (!c.hasFixed()) { 365 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "fixed", a.getFixed(), null, c.getId()), IssueSeverity.ERROR)); 366 } else if (!compliesWith(a.getFixed(), c.getFixed())) { 367 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "fixed", a.getFixed(), c.getFixed(), c.getId()), IssueSeverity.ERROR)); 368 } 369 } else if (a.hasPattern()) { 370 if (!c.hasFixed() && !c.hasPattern()) { 371 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), null, c.getId()), IssueSeverity.ERROR)); 372 } else if (c.hasFixed()) { 373 if (!compliesWith(a.getFixed(), c.getFixed())) { 374 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), c.getFixed(), c.getId()), IssueSeverity.ERROR)); 375 } 376 } else { // if (c.hasPattern()) 377 if (!compliesWith(a.getPattern(), c.getPattern())) { 378 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "pattern", a.getFixed(), c.getPattern(), c.getId()), IssueSeverity.ERROR)); 379 } 380 } 381 } 382 if (!"Resource.id".equals(c.getBase().getPath())) { // tricky... there's definitional problems with Resource.id for legacy reasons, but whatever issues there are aren't due to anything the profile did 383 for (TypeRefComponent tr : c.getType()) { 384 if (!hasType(tr, a.getType())) { 385 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_BAD_TYPE, tr.getWorkingCode()), IssueSeverity.ERROR)); 386 } 387 doInner = false; 388 } 389 } 390 if (a.hasMinValue()) { 391 if (notGreaterThan(a.getMinValue(), c.getMinValue())) { 392 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "minValue", a.getMinValue(), c.getMinValue(), c.getId()), IssueSeverity.ERROR)); 393 } 394 } 395 if (a.hasMaxValue()) { 396 if (notLessThan(a.getMaxValue(), c.getMaxValue())) { 397 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "maxValue", a.getMaxValue(), c.getMaxValue(), c.getId()), IssueSeverity.ERROR)); 398 } 399 } 400 if (a.hasMaxLength()) { 401 if (a.getMaxLength() < c.getMaxLength()) { 402 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "maxLength", a.getMaxValue(), c.getMaxValue(), c.getId()), IssueSeverity.ERROR)); 403 } 404 } 405 if (a.hasMustHaveValue()) { 406 if (a.getMustHaveValue() && !c.getMustHaveValue()) { 407 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "mustHaveValue", a.getMustHaveValue(), c.getMustHaveValue(), c.getId()), IssueSeverity.ERROR)); 408 } 409 } 410 if (a.hasValueAlternatives()) { 411 for (CanonicalType ct : c.getValueAlternatives()) { 412 if (!hasCanonical(ct, a.getValueAlternatives())) { 413 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_BAD_ELEMENT, "valueAlternatives", ct.toString()), IssueSeverity.ERROR)); 414 } 415 } 416 } 417 for (ElementDefinitionConstraintComponent cc : a.getConstraint()) { 418 if (cc.getSeverity() == ConstraintSeverity.ERROR) { 419 if (!hasConstraint(cc, c.getConstraint())) { 420 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_MISSING_ELEMENT, "constraint", cc.getExpression()), IssueSeverity.ERROR)); 421 } 422 } 423 } 424 425 if (a.hasBinding() && a.getBinding().hasValueSet() && (a.getBinding().getStrength() == BindingStrength.REQUIRED || a.getBinding().getStrength() == BindingStrength.EXTENSIBLE)) { 426 if (!c.hasBinding()) { 427 if (isBindableType(c)) { 428 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding", a.getBinding().getValueSet(), "null", c.getId()), IssueSeverity.ERROR)); 429 } 430 } else if (c.getBinding().getStrength() != BindingStrength.REQUIRED && c.getBinding().getStrength() != BindingStrength.EXTENSIBLE) { 431 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding.strength", a.getBinding().getStrength(), c.getBinding().getStrength(), c.getId()), IssueSeverity.ERROR)); 432 } else if (c.getBinding().getStrength() == BindingStrength.EXTENSIBLE && a.getBinding().getStrength() == BindingStrength.REQUIRED) { 433 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NOT_VALID, "binding.strength", a.getBinding().getStrength(), c.getBinding().getStrength(), c.getId()), IssueSeverity.ERROR)); 434 } else if (!c.getBinding().getValueSet().equals(a.getBinding().getValueSet())) { 435 ValueSet cVS = context.fetchResource(ValueSet.class, c.getBinding().getValueSet()); 436 ValueSet aVS = context.fetchResource(ValueSet.class, a.getBinding().getValueSet()); 437 if (aVS == null || cVS == null) { 438 if (aVS == null) { 439 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS, a.getBinding().getValueSet()), IssueSeverity.WARNING)); 440 } else { 441 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS, c.getBinding().getValueSet()), IssueSeverity.WARNING)); 442 } 443 } else { 444 ValueSetExpansionOutcome cExp = context.expandVS(cVS, true, false); 445 ValueSetExpansionOutcome aExp = context.expandVS(aVS, true, false); 446 if (!cExp.isOk() || !aExp.isOk()) { 447 if (!aExp.isOk()) { 448 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_EXP, aVS.getVersionedUrl(), aExp.getError()), IssueSeverity.WARNING)); 449 } else { 450 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_EXP, cVS.getVersionedUrl(), cExp.getError()), IssueSeverity.WARNING)); 451 } 452 } else { 453 Set<String> wrong = ValueSetUtilities.checkExpansionSubset(aExp.getValueset(), cExp.getValueset()); 454 if (!wrong.isEmpty()) { 455 messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, context.formatMessage(I18nConstants.PROFILE_COMPLIES_WITH_NO_VS_NO, cVS.getVersionedUrl(), aVS.getVersionedUrl(), 456 CommaSeparatedStringBuilder.joinToLimit(", ", 5, "etc", wrong)), c.getBinding().getStrength() == BindingStrength.REQUIRED ? IssueSeverity.ERROR : IssueSeverity.WARNING)); 457 } 458 } 459 } 460 } 461 } 462 return doInner; 463 } 464 465 private boolean isBindableType(ElementDefinition c) { 466 for (TypeRefComponent t : c.getType()) { 467 if (isBindableType(t.getWorkingCode())) { 468 return true; 469 } 470 } 471 return false; 472 } 473 474 private boolean isBindableType(String type) { 475 if (Utilities.existsInList(type, "CodeableConcept", "Coding", "code", "CodeableReference", "string", "uri", "Quantity")) { 476 return true; 477 } 478 StructureDefinition sd = context.fetchTypeDefinition(type); 479 if (sd == null) { 480 return false; 481 } 482 for (Extension ex : sd.getExtensionsByUrl(ExtensionDefinitions.EXT_TYPE_CHARACTERISTICS)) { 483 if ("can-bind".equals(ex.getValue().primitiveValue())) { 484 return true; 485 } 486 } 487 return false; 488 } 489 490 private boolean typesIdentical(ElementDefinition c, ElementDefinition a) { 491 if (c.getType().size() != a.getType().size()) { 492 return false; 493 } 494 for (TypeRefComponent ct : c.getType()) { 495 TypeRefComponent at = getType(a.getType(), ct.getCode()); 496 if (at == null || !at.equalsDeep(ct)) { 497 return false; 498 } 499 } 500 return true; 501 } 502 503 private TypeRefComponent getType(List<TypeRefComponent> types, String code) { 504 for (TypeRefComponent t : types) { 505 if (code.equals(t.getCode())) { 506 return t; 507 } 508 } 509 return null; 510 } 511 512 private boolean hasConstraint(ElementDefinitionConstraintComponent cc, List<ElementDefinitionConstraintComponent> list) { 513 for (ElementDefinitionConstraintComponent t : list) { 514 if (t.getSeverity() == ConstraintSeverity.ERROR && t.getExpression().equals(cc.getExpression())) { 515 return true; 516 } 517 } 518 return false; 519 } 520 521 private boolean hasCanonical(CanonicalType ct, List<CanonicalType> list) { 522 for (CanonicalType t : list) { 523 if (t.hasValue() && t.getValue().equals(ct.getValue())) { 524 return true; 525 } 526 } 527 return false; 528 } 529 530 private boolean notLessThan(DataType v1, DataType v2) { 531 if (v2 == null) { 532 return true; 533 } 534 if (!v1.fhirType().equals(v2.fhirType())) { 535 return true; 536 } 537 switch (v1.fhirType()) { 538 case "date" : 539 case "dateTime": 540 case "instant": 541 return !((DateTimeType) v1).before((DateTimeType) v2); 542 case "time": 543 return v1.primitiveValue().compareTo(v2.primitiveValue()) >= 0; 544 case "decimal": 545 return ((DecimalType) v1).compareTo((DecimalType) v2) >= 0; 546 case "integer": 547 case "integer64": 548 case "positiveInt": 549 case "unsignedInt": 550 int i1 = Integer.parseInt(v1.toString()); 551 int i2 = Integer.parseInt(v2.toString()); 552 return i1 >= i2; 553 case "Quantity": 554 Quantity q1 = (Quantity) v1; 555 Quantity q2 = (Quantity) v2; 556 557 default: 558 return true; 559 } 560 } 561 562 private boolean notGreaterThan(DataType minValue, DataType minValue2) { 563 // TODO Auto-generated method stub 564 return false; 565 } 566 567 private boolean hasType(TypeRefComponent tr, List<TypeRefComponent> types) { 568 for (TypeRefComponent t : types) { 569 if (t.getWorkingCode().equals(tr.getWorkingCode())) { 570 boolean ok = t.getVersioning() == tr.getVersioning(); 571 // we don't care about profile - that's the whole point, we just go ehad and check that 572// for (CanonicalType ct : tr.getProfile()) { 573// if (!t.hasProfile(ct.asStringValue())) { 574// ok = false; 575// } 576// } 577 // todo: we have to check that the targets are compatible 578// for (CanonicalType ct : tr.getTargetProfile()) { 579// if (!t.hasTargetProfile(ct.asStringValue())) { 580// ok = false; 581// } 582// } 583 if (ok) { 584 return true; 585 } 586 } 587 } 588 return false; 589 } 590 591 private boolean compliesWith(DataType authority, DataType test) { 592 if (!authority.fhirType().equals(test.fhirType())) { 593 return false; 594 } 595 if (authority.isPrimitive()) { 596 if (!authority.primitiveValue().equals(test.primitiveValue())) { 597 return false; 598 } 599 } 600 for (Property p : authority.children()) { 601 if (p.hasValues()) { 602 Property pt = test.getNamedProperty(p.getName()); 603 if (p.getValues().size() > pt.getValues().size()) { 604 return false; 605 } else { 606 for (int i = 0; i < pt.getValues().size(); i++) { 607 DataType v = (DataType) p.getValues().get(i); 608 DataType vt = (DataType) pt.getValues().get(i); 609 if (!compliesWith(v, vt)) { 610 return false; 611 } 612 } 613 } 614 } 615 } 616 return true; 617 } 618}