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