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