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