001package org.hl7.fhir.r5.elementmodel; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032 033 034import java.util.ArrayList; 035import java.util.List; 036 037import org.hl7.fhir.exceptions.DefinitionException; 038import org.hl7.fhir.exceptions.FHIRException; 039import org.hl7.fhir.r5.conformance.ProfileUtilities; 040import org.hl7.fhir.r5.context.IWorkerContext; 041import org.hl7.fhir.r5.formats.FormatUtilities; 042import org.hl7.fhir.r5.model.ElementDefinition; 043import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; 044import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; 045import org.hl7.fhir.r5.model.StructureDefinition; 046import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; 047import org.hl7.fhir.r5.model.TypeDetails; 048import org.hl7.fhir.r5.utils.ToolingExtensions; 049import org.hl7.fhir.r5.utils.TypesUtilities; 050import org.hl7.fhir.utilities.Utilities; 051 052public class Property { 053 054 private IWorkerContext context; 055 private ElementDefinition definition; 056 private StructureDefinition structure; 057 private Boolean canBePrimitive; 058 private ProfileUtilities profileUtilities; 059 060 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities) { 061 this.context = context; 062 this.definition = definition; 063 this.structure = structure; 064 this.profileUtilities = profileUtilities; 065 } 066 067 068 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) { 069 this(context, definition, structure, new ProfileUtilities(context, null, null)); 070 } 071 072 public String getName() { 073 return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 074 } 075 076 public String getXmlName() { 077 if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-xml-name")) { 078 return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-xml-name"); 079 } else { 080 return getName(); 081 } 082 } 083 084 public String getXmlNamespace() { 085 if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 086 return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 087 } else if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) { 088 return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 089 } else { 090 return FormatUtilities.FHIR_NS; 091 } 092 } 093 094 public ElementDefinition getDefinition() { 095 return definition; 096 } 097 098 public String getType() { 099 if (definition.getType().size() == 0) 100 return null; 101 else if (definition.getType().size() > 1) { 102 String tn = definition.getType().get(0).getWorkingCode(); 103 for (int i = 1; i < definition.getType().size(); i++) { 104 if (!tn.equals(definition.getType().get(i).getWorkingCode())) 105 throw new Error("logic error, gettype when types > 1"); 106 } 107 return tn; 108 } else 109 return definition.getType().get(0).getWorkingCode(); 110 } 111 112 public String getType(String elementName) { 113 if (!definition.getPath().contains(".")) 114 return definition.getPath(); 115 ElementDefinition ed = definition; 116 if (definition.hasContentReference()) { 117 String url = null; 118 String path = definition.getContentReference(); 119 if (!path.startsWith("#")) { 120 if (path.contains("#")) { 121 url = path.substring(0, path.indexOf("#")); 122 path = path.substring(path.indexOf("#")+1); 123 } else { 124 throw new Error("Illegal content reference '"+path+"'"); 125 } 126 } else { 127 path = path.substring(1); 128 } 129 StructureDefinition sd = (url == null || url.equals(structure.getUrl())) ? structure : context.fetchResource(StructureDefinition.class, url, structure); 130 if (sd == null) { 131 throw new Error("Unknown Type in content reference '"+path+"'"); 132 } 133 boolean found = false; 134 for (ElementDefinition d : sd.getSnapshot().getElement()) { 135 if (d.hasId() && d.getId().equals(path)) { 136 found = true; 137 ed = d; 138 } 139 } 140 if (!found) 141 throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+sd.getUrl()); 142 } 143 if (ed.getType().size() == 0) 144 return null; 145 else if (ed.getType().size() > 1) { 146 String t = ed.getType().get(0).getCode(); 147 boolean all = true; 148 for (TypeRefComponent tr : ed.getType()) { 149 if (!t.equals(tr.getCode())) 150 all = false; 151 } 152 if (all) 153 return t; 154 String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1); 155 if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) { 156 String name = elementName.substring(tail.length()-3); 157 return isPrimitive(lowFirst(name)) ? lowFirst(name) : name; 158 } else { 159 if (ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 160 return ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 161 throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath()); 162 } 163 } else if (ed.getType().get(0).getCode() == null) { 164 if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url")) 165 return "string"; 166 else 167 return structure.getId(); 168 } else 169 return ed.getType().get(0).getWorkingCode(); 170 } 171 172 public boolean hasType(String elementName) { 173 if (definition.getType().size() == 0) 174 return false; 175 else if (definition.getType().size() > 1) { 176 String t = definition.getType().get(0).getCode(); 177 boolean all = true; 178 for (TypeRefComponent tr : definition.getType()) { 179 if (!t.equals(tr.getCode())) 180 all = false; 181 } 182 if (all) 183 return true; 184 String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 185 if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) { 186 String name = elementName.substring(tail.length()-3); 187 return true; 188 } else 189 return false; 190 } else 191 return true; 192 } 193 194 public StructureDefinition getStructure() { 195 return structure; 196 } 197 198 /** 199 * Is the given name a primitive 200 * 201 * @param E.g. "Observation.status" 202 */ 203 public boolean isPrimitiveName(String name) { 204 String code = getType(name); 205 return isPrimitive(code); 206 } 207 208 /** 209 * Is the given type a primitive 210 * 211 * @param E.g. "integer" 212 */ 213 public boolean isPrimitive(String code) { 214 return TypesUtilities.isPrimitive(code); 215 // was this... but this can be very inefficient compared to hard coding the list 216// StructureDefinition sd = context.fetchTypeDefinition(code); 217// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 218 } 219 220 private String lowFirst(String t) { 221 return t.substring(0, 1).toLowerCase()+t.substring(1); 222 } 223 224 public boolean isResource() { 225 if (definition.getType().size() > 0) 226 return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode())); 227 else 228 return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE); 229 } 230 231 public boolean isList() { 232 return !"1".equals(definition.getMax()); 233 } 234 235 public boolean isBaseList() { 236 return !"1".equals(definition.getBase().getMax()); 237 } 238 239 public String getScopedPropertyName() { 240 return definition.getBase().getPath(); 241 } 242 243 private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) { 244 boolean result = false; 245 if (!ed.getType().isEmpty()) { 246 result = true; 247 for (final ElementDefinition ele : children) { 248 if (!ele.getPath().contains("extension")) { 249 result = false; 250 break; 251 } 252 } 253 } 254 return result; 255 } 256 257 public boolean IsLogicalAndHasPrimitiveValue(String name) { 258// if (canBePrimitive!= null) 259// return canBePrimitive; 260 261 canBePrimitive = false; 262 if (structure.getKind() != StructureDefinitionKind.LOGICAL) 263 return false; 264 if (!hasType(name)) 265 return false; 266 StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name)); 267 if (sd == null) 268 sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs())); 269 if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) 270 return true; 271 if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL) 272 return false; 273 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 274 if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) { 275 canBePrimitive = true; 276 return true; 277 } 278 } 279 return false; 280 } 281 282 public boolean isChoice() { 283 if (definition.getType().size() <= 1) 284 return false; 285 String tn = definition.getType().get(0).getCode(); 286 for (int i = 1; i < definition.getType().size(); i++) 287 if (!definition.getType().get(i).getCode().equals(tn)) 288 return true; 289 return false; 290 } 291 292 293 protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException { 294 ElementDefinition ed = definition; 295 StructureDefinition sd = structure; 296 List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed); 297 String url = null; 298 if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) { 299 // ok, find the right definitions 300 String t = null; 301 if (ed.getType().size() == 1) 302 t = ed.getType().get(0).getWorkingCode(); 303 else if (ed.getType().size() == 0) 304 throw new Error("types == 0, and no children found on "+getDefinition().getPath()); 305 else { 306 t = ed.getType().get(0).getWorkingCode(); 307 boolean all = true; 308 for (TypeRefComponent tr : ed.getType()) { 309 if (!tr.getWorkingCode().equals(t)) { 310 all = false; 311 break; 312 } 313 } 314 if (!all) { 315 // ok, it's polymorphic 316 if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 317 t = statedType; 318 if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 319 t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 320 boolean ok = false; 321 for (TypeRefComponent tr : ed.getType()) { 322 if (tr.getWorkingCode().equals(t)) 323 ok = true; 324 if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) { 325 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode()); 326 if (sdt != null && sdt.getType().equals(t)) { 327 url = tr.getWorkingCode(); 328 ok = true; 329 } 330 } 331 if (ok) 332 break; 333 } 334 if (!ok) 335 throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); 336 337 } else { 338 t = elementName.substring(tail(ed.getPath()).length() - 3); 339 if (isPrimitive(lowFirst(t))) 340 t = lowFirst(t); 341 } 342 } 343 } 344 if (!"xhtml".equals(t)) { 345 for (TypeRefComponent aType: ed.getType()) { 346 if (aType.getWorkingCode().equals(t)) { 347 if (aType.hasProfile()) { 348 assert aType.getProfile().size() == 1; 349 url = aType.getProfile().get(0).getValue(); 350 } else { 351 url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs()); 352 } 353 break; 354 } 355 } 356 if (url==null) 357 throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath()); 358 sd = context.fetchResource(StructureDefinition.class, url); 359 if (sd == null) 360 throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); 361 children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 362 } 363 } 364 List<Property> properties = new ArrayList<Property>(); 365 for (ElementDefinition child : children) { 366 properties.add(new Property(context, child, sd, this.profileUtilities)); 367 } 368 return properties; 369 } 370 371 protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException { 372 ElementDefinition ed = definition; 373 StructureDefinition sd = structure; 374 List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed); 375 if (children.isEmpty()) { 376 // ok, find the right definitions 377 String t = null; 378 if (ed.getType().size() == 1) 379 t = ed.getType().get(0).getCode(); 380 else if (ed.getType().size() == 0) 381 throw new Error("types == 0, and no children found"); 382 else { 383 t = ed.getType().get(0).getCode(); 384 boolean all = true; 385 for (TypeRefComponent tr : ed.getType()) { 386 if (!tr.getCode().equals(t)) { 387 all = false; 388 break; 389 } 390 } 391 if (!all) { 392 // ok, it's polymorphic 393 t = type.getType(); 394 } 395 } 396 if (!"xhtml".equals(t)) { 397 sd = context.fetchResource(StructureDefinition.class, t); 398 if (sd == null) 399 throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); 400 children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 401 } 402 } 403 List<Property> properties = new ArrayList<Property>(); 404 for (ElementDefinition child : children) { 405 properties.add(new Property(context, child, sd, this.profileUtilities)); 406 } 407 return properties; 408 } 409 410 private String tail(String path) { 411 return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path; 412 } 413 414 public Property getChild(String elementName, String childName) throws FHIRException { 415 List<Property> children = getChildProperties(elementName, null); 416 for (Property p : children) { 417 if (p.getName().equals(childName)) { 418 return p; 419 } 420 } 421 return null; 422 } 423 424 public Property getChild(String name, TypeDetails type) throws DefinitionException { 425 List<Property> children = getChildProperties(type); 426 for (Property p : children) { 427 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 428 return p; 429 } 430 } 431 return null; 432 } 433 434 public Property getChild(String name) throws FHIRException { 435 List<Property> children = getChildProperties(name, null); 436 for (Property p : children) { 437 if (p.getName().equals(name)) { 438 return p; 439 } 440 } 441 return null; 442 } 443 444 public Property getChildSimpleName(String elementName, String name) throws FHIRException { 445 List<Property> children = getChildProperties(elementName, null); 446 for (Property p : children) { 447 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 448 return p; 449 } 450 } 451 return null; 452 } 453 454 public IWorkerContext getContext() { 455 return context; 456 } 457 458 @Override 459 public String toString() { 460 return definition.getPath(); 461 } 462 463 464}