001package org.hl7.fhir.dstu3.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.apache.commons.lang3.StringUtils; 038import org.hl7.fhir.dstu3.conformance.ProfileUtilities; 039import org.hl7.fhir.dstu3.context.IWorkerContext; 040import org.hl7.fhir.dstu3.fhirpath.TypeDetails; 041import org.hl7.fhir.dstu3.formats.FormatUtilities; 042import org.hl7.fhir.dstu3.model.ElementDefinition; 043import org.hl7.fhir.dstu3.model.ElementDefinition.PropertyRepresentation; 044import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent; 045import org.hl7.fhir.dstu3.model.StructureDefinition; 046import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind; 047import org.hl7.fhir.dstu3.utils.ToolingExtensions; 048import org.hl7.fhir.exceptions.DefinitionException; 049 050 051public class Property { 052 053 private IWorkerContext context; 054 private ElementDefinition definition; 055 private StructureDefinition structure; 056 private Boolean canBePrimitive; 057 058 public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) { 059 this.context = context; 060 this.definition = definition; 061 this.structure = structure; 062 } 063 064 public String getName() { 065 return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 066 } 067 068 public ElementDefinition getDefinition() { 069 return definition; 070 } 071 072 public String getType() { 073 if (definition.getType().size() == 0) 074 return null; 075 else if (definition.getType().size() > 1) { 076 String tn = definition.getType().get(0).getCode(); 077 for (int i = 1; i < definition.getType().size(); i++) { 078 if (!tn.equals(definition.getType().get(i).getCode())) 079 throw new Error("logic error, gettype when types > 1"); 080 } 081 return tn; 082 } else 083 return definition.getType().get(0).getCode(); 084 } 085 086 public String getType(String elementName) { 087 if (!definition.getPath().contains(".")) 088 return definition.getPath(); 089 ElementDefinition ed = definition; 090 if (definition.hasContentReference()) { 091 if (!definition.getContentReference().startsWith("#")) 092 throw new Error("not handled yet"); 093 boolean found = false; 094 for (ElementDefinition d : structure.getSnapshot().getElement()) { 095 if (d.hasId() && d.getId().equals(definition.getContentReference().substring(1))) { 096 found = true; 097 ed = d; 098 } 099 } 100 if (!found) 101 throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+structure.getUrl()); 102 } 103 if (ed.getType().size() == 0) 104 return null; 105 else if (ed.getType().size() > 1) { 106 String t = ed.getType().get(0).getCode(); 107 boolean all = true; 108 for (TypeRefComponent tr : ed.getType()) { 109 if (!t.equals(tr.getCode())) 110 all = false; 111 } 112 if (all) 113 return t; 114 String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1); 115 if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) { 116 String name = elementName.substring(tail.length()-3); 117 return isPrimitive(lowFirst(name)) ? lowFirst(name) : name; 118 } else 119 throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath()); 120 } else if (ed.getType().get(0).getCode() == null) { 121 return structure.getId(); 122 } else 123 return ed.getType().get(0).getCode(); 124 } 125 126 public boolean hasType(String elementName) { 127 if (definition.getType().size() == 0) 128 return false; 129 else if (definition.getType().size() > 1) { 130 String t = definition.getType().get(0).getCode(); 131 boolean all = true; 132 for (TypeRefComponent tr : definition.getType()) { 133 if (!t.equals(tr.getCode())) 134 all = false; 135 } 136 if (all) 137 return true; 138 String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1); 139 if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) { 140 String name = elementName.substring(tail.length()-3); 141 return true; 142 } else 143 return false; 144 } else 145 return true; 146 } 147 148 public StructureDefinition getStructure() { 149 return structure; 150 } 151 152 /** 153 * Is the given name a primitive 154 * 155 * @param E.g. "Observation.status" 156 */ 157 public boolean isPrimitiveName(String name) { 158 String code = getType(name); 159 return isPrimitive(code); 160 } 161 162 /** 163 * Is the given type a primitive 164 * 165 * @param E.g. "integer" 166 */ 167 public boolean isPrimitive(String code) { 168 return org.hl7.fhir.dstu3.utils.TypesUtilities.isPrimitive(code); 169 // was this... but this can be very inefficient compared to hard coding the list 170// StructureDefinition sd = context.fetchTypeDefinition(code); 171// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE; 172 } 173 174 private String lowFirst(String t) { 175 return t.substring(0, 1).toLowerCase()+t.substring(1); 176 } 177 178 public boolean isResource() { 179 if (definition.getType().size() > 0) 180 return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode())); 181 else 182 return !definition.getPath().contains(".") && structure.getKind() == StructureDefinitionKind.RESOURCE; 183 } 184 185 public boolean isList() { 186 return !"1".equals(definition.getMax()); 187 } 188 189 public String getScopedPropertyName() { 190 return definition.getBase().getPath(); 191 } 192 193 public String getNamespace() { 194 if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 195 return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 196 if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) 197 return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"); 198 return FormatUtilities.FHIR_NS; 199 } 200 201 public boolean IsLogicalAndHasPrimitiveValue(String name) { 202// if (canBePrimitive!= null) 203// return canBePrimitive; 204 205 canBePrimitive = false; 206 if (structure.getKind() != StructureDefinitionKind.LOGICAL) 207 return false; 208 if (!hasType(name)) 209 return false; 210 StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name)); 211 if (sd == null) 212 sd = context.fetchTypeDefinition(getType(name)); 213 if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) 214 return true; 215 if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL) 216 return false; 217 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 218 if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) { 219 canBePrimitive = true; 220 return true; 221 } 222 } 223 return false; 224 } 225 226 public boolean isChoice() { 227 if (definition.getType().size() <= 1) 228 return false; 229 String tn = definition.getType().get(0).getCode(); 230 for (int i = 1; i < definition.getType().size(); i++) 231 if (!definition.getType().get(i).getCode().equals(tn)) 232 return true; 233 return false; 234 } 235 236 237 protected List<Property> getChildProperties(String elementName, String statedType) throws DefinitionException { 238 ElementDefinition ed = definition; 239 StructureDefinition sd = structure; 240 List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed); 241 if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) { 242 // ok, find the right definitions 243 String t = null; 244 if (ed.getType().size() == 1) 245 t = ed.getType().get(0).getCode(); 246 else if (ed.getType().size() == 0) 247 throw new Error("types == 0, and no children found"); 248 else { 249 t = ed.getType().get(0).getCode(); 250 boolean all = true; 251 for (TypeRefComponent tr : ed.getType()) { 252 if (!tr.getCode().equals(t)) { 253 all = false; 254 break; 255 } 256 } 257 if (!all) { 258 // ok, it's polymorphic 259 if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) { 260 t = statedType; 261 if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) 262 t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); 263 boolean ok = false; 264 for (TypeRefComponent tr : ed.getType()) 265 if (tr.getCode().equals(t)) 266 ok = true; 267 if (!ok) 268 throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); 269 270 } else { 271 t = elementName.substring(tail(ed.getPath()).length() - 3); 272 if (isPrimitive(lowFirst(t))) 273 t = lowFirst(t); 274 } 275 } 276 } 277 if (!"xhtml".equals(t)) { 278 final String url; 279 if (StringUtils.isNotBlank(ed.getType().get(0).getProfile())) { 280 url = ed.getType().get(0).getProfile(); 281 } else { 282 url = "http://hl7.org/fhir/StructureDefinition/" + t; 283 } 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 DefinitionException { 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 DefinitionException { 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 DefinitionException { 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 private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) { 385 boolean result = false; 386 if (!ed.getType().isEmpty()) { 387 result = true; 388 for (final ElementDefinition ele : children) { 389 if (!ele.getPath().contains("extension")) { 390 result = false; 391 break; 392 } 393 } 394 } 395 return result; 396 } 397 398}