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