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