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