001package org.hl7.fhir.r4.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.r4.conformance.ProfileUtilities; 040import org.hl7.fhir.r4.context.IWorkerContext; 041import org.hl7.fhir.r4.formats.FormatUtilities; 042import org.hl7.fhir.r4.model.ElementDefinition; 043import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation; 044import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent; 045import org.hl7.fhir.r4.model.StructureDefinition; 046import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind; 047import org.hl7.fhir.r4.model.TypeDetails; 048import org.hl7.fhir.r4.utils.ToolingExtensions; 049import org.hl7.fhir.r4.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 059 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) { 060 this.context = context; 061 this.definition = definition; 062 this.structure = structure; 063 } 064 065 public String getName() { 066 return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 067 } 068 069 public ElementDefinition getDefinition() { 070 return definition; 071 } 072 073 public String getType() { 074 if (definition.getType().size() == 0) 075 return null; 076 else if (definition.getType().size() > 1) { 077 String tn = definition.getType().get(0).getWorkingCode(); 078 for (int i = 1; i < definition.getType().size(); i++) { 079 if (!tn.equals(definition.getType().get(i).getWorkingCode())) 080 throw new Error("logic error, gettype when types > 1"); 081 } 082 return tn; 083 } else 084 return definition.getType().get(0).getWorkingCode(); 085 } 086 087 public String getType(String elementName) { 088 if (!definition.getPath().contains(".")) 089 return definition.getPath(); 090 ElementDefinition ed = definition; 091 if (definition.hasContentReference()) { 092 if (!definition.getContentReference().startsWith("#")) 093 throw new Error("not handled yet"); 094 boolean found = false; 095 for (ElementDefinition d : structure.getSnapshot().getElement()) { 096 if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) { 097 found = true; 098 ed = d; 099 } 100 } 101 if (!found) 102 throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+structure.getUrl()); 103 } 104 if (ed.getType().size() == 0) 105 return null; 106 else if (ed.getType().size() > 1) { 107 String t = ed.getType().get(0).getCode(); 108 boolean all = true; 109 for (TypeRefComponent tr : ed.getType()) { 110 if (!t.equals(tr.getCode())) 111 all = false; 112 } 113 if (all) 114 return t; 115 String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1); 116 if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) { 117 String name = elementName.substring(tail.length()-3); 118 return isPrimitive(lowFirst(name)) ? lowFirst(name) : name; 119 } else 120 throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath()); 121 } else if (ed.getType().get(0).getCode() == null) { 122 if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url")) 123 return "string"; 124 else 125 return structure.getId(); 126 } else 127 return ed.getType().get(0).getWorkingCode(); 128 } 129 130 public boolean hasType(String elementName) { 131 if (definition.getType().size() == 0) 132 return false; 133 else if (definition.getType().size() > 1) { 134 String t = definition.getType().get(0).getCode(); 135 boolean all = true; 136 for (TypeRefComponent tr : definition.getType()) { 137 if (!t.equals(tr.getCode())) 138 all = false; 139 } 140 if (all) 141 return true; 142 String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 143 if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) { 144 String name = elementName.substring(tail.length()-3); 145 return true; 146 } else 147 return false; 148 } else 149 return true; 150 } 151 152 public StructureDefinition getStructure() { 153 return structure; 154 } 155 156 /** 157 * Is the given name a primitive 158 * 159 * @param E.g. "Observation.status" 160 */ 161 public boolean isPrimitiveName(String name) { 162 String code = getType(name); 163 return isPrimitive(code); 164 } 165 166 /** 167 * Is the given type a primitive 168 * 169 * @param E.g. "integer" 170 */ 171 public boolean isPrimitive(String code) { 172 return TypesUtilities.isPrimitive(code); 173 // was this... but this can be very inefficient compared to hard coding the list 174// StructureDefinition sd = context.fetchTypeDefinition(code); 175// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 176 } 177 178 private String lowFirst(String t) { 179 return t.substring(0, 1).toLowerCase()+t.substring(1); 180 } 181 182 public boolean isResource() { 183 if (definition.getType().size() > 0) 184 return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode())); 185 else 186 return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE; 187 } 188 189 public boolean isList() { 190 return !"1".equals(definition.getMax()); 191 } 192 193 public String getScopedPropertyName() { 194 return definition.getBase().getPath(); 195 } 196 197 public String getNamespace() { 198 if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 199 return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 200 if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 201 return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 202 return FormatUtilities.FHIR_NS; 203 } 204 205 private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) { 206 boolean result = false; 207 if (!ed.getType().isEmpty()) { 208 result = true; 209 for (final ElementDefinition ele : children) { 210 if (!ele.getPath().contains("extension")) { 211 result = false; 212 break; 213 } 214 } 215 } 216 return result; 217 } 218 219 public boolean IsLogicalAndHasPrimitiveValue(String name) { 220// if (canBePrimitive!= null) 221// return canBePrimitive; 222 223 canBePrimitive = false; 224 if (structure.getKind() != StructureDefinitionKind.LOGICAL) 225 return false; 226 if (!hasType(name)) 227 return false; 228 StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name)); 229 if (sd == null) 230 sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs())); 231 if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) 232 return true; 233 if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL) 234 return false; 235 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 236 if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) { 237 canBePrimitive = true; 238 return true; 239 } 240 } 241 return false; 242 } 243 244 public boolean isChoice() { 245 if (definition.getType().size() <= 1) 246 return false; 247 String tn = definition.getType().get(0).getCode(); 248 for (int i = 1; i < definition.getType().size(); i++) 249 if (!definition.getType().get(i).getCode().equals(tn)) 250 return true; 251 return false; 252 } 253 254 255 protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException { 256 ElementDefinition ed = definition; 257 StructureDefinition sd = structure; 258 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 259 String url = null; 260 if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) { 261 // ok, find the right definitions 262 String t = null; 263 if (ed.getType().size() == 1) 264 t = ed.getType().get(0).getWorkingCode(); 265 else if (ed.getType().size() == 0) 266 throw new Error("types == 0, and no children found on "+getDefinition().getPath()); 267 else { 268 t = ed.getType().get(0).getWorkingCode(); 269 boolean all = true; 270 for (TypeRefComponent tr : ed.getType()) { 271 if (!tr.getWorkingCode().equals(t)) { 272 all = false; 273 break; 274 } 275 } 276 if (!all) { 277 // ok, it's polymorphic 278 if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 279 t = statedType; 280 if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 281 t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 282 boolean ok = false; 283 for (TypeRefComponent tr : ed.getType()) { 284 if (tr.getWorkingCode().equals(t)) 285 ok = true; 286 if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) { 287 StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode()); 288 if (sdt != null && sdt.getType().equals(t)) { 289 url = tr.getWorkingCode(); 290 ok = true; 291 } 292 } 293 if (ok) 294 break; 295 } 296 if (!ok) 297 throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); 298 299 } else { 300 t = elementName.substring(tail(ed.getPath()).length() - 3); 301 if (isPrimitive(lowFirst(t))) 302 t = lowFirst(t); 303 } 304 } 305 } 306 if (!"xhtml".equals(t)) { 307 for (TypeRefComponent aType: ed.getType()) { 308 if (aType.getWorkingCode().equals(t)) { 309 if (aType.hasProfile()) { 310 assert aType.getProfile().size() == 1; 311 url = aType.getProfile().get(0).getValue(); 312 } else { 313 url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs()); 314 } 315 break; 316 } 317 } 318 if (url==null) 319 throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath()); 320 sd = context.fetchResource(StructureDefinition.class, url); 321 if (sd == null) 322 throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath()); 323 children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 324 } 325 } 326 List<Property> properties = new ArrayList<Property>(); 327 for (ElementDefinition child : children) { 328 properties.add(new Property(context, child, sd)); 329 } 330 return properties; 331 } 332 333 protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException { 334 ElementDefinition ed = definition; 335 StructureDefinition sd = structure; 336 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 337 if (children.isEmpty()) { 338 // ok, find the right definitions 339 String t = null; 340 if (ed.getType().size() == 1) 341 t = ed.getType().get(0).getCode(); 342 else if (ed.getType().size() == 0) 343 throw new Error("types == 0, and no children found"); 344 else { 345 t = ed.getType().get(0).getCode(); 346 boolean all = true; 347 for (TypeRefComponent tr : ed.getType()) { 348 if (!tr.getCode().equals(t)) { 349 all = false; 350 break; 351 } 352 } 353 if (!all) { 354 // ok, it's polymorphic 355 t = type.getType(); 356 } 357 } 358 if (!"xhtml".equals(t)) { 359 sd = context.fetchResource(StructureDefinition.class, t); 360 if (sd == null) 361 throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath()); 362 children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0)); 363 } 364 } 365 List<Property> properties = new ArrayList<Property>(); 366 for (ElementDefinition child : children) { 367 properties.add(new Property(context, child, sd)); 368 } 369 return properties; 370 } 371 372 private String tail(String path) { 373 return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path; 374 } 375 376 public Property getChild(String elementName, String childName) throws FHIRException { 377 List<Property> children = getChildProperties(elementName, null); 378 for (Property p : children) { 379 if (p.getName().equals(childName)) { 380 return p; 381 } 382 } 383 return null; 384 } 385 386 public Property getChild(String name, TypeDetails type) throws DefinitionException { 387 List<Property> children = getChildProperties(type); 388 for (Property p : children) { 389 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 390 return p; 391 } 392 } 393 return null; 394 } 395 396 public Property getChild(String name) throws FHIRException { 397 List<Property> children = getChildProperties(name, null); 398 for (Property p : children) { 399 if (p.getName().equals(name)) { 400 return p; 401 } 402 } 403 return null; 404 } 405 406 public Property getChildSimpleName(String elementName, String name) throws FHIRException { 407 List<Property> children = getChildProperties(elementName, null); 408 for (Property p : children) { 409 if (p.getName().equals(name) || p.getName().equals(name+"[x]")) { 410 return p; 411 } 412 } 413 return null; 414 } 415 416 public IWorkerContext getContext() { 417 return context; 418 } 419 420 @Override 421 public String toString() { 422 return definition.getPath(); 423 } 424 425 426}