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