
001package ca.uhn.fhir.context; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 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 023import ca.uhn.fhir.i18n.Msg; 024import ca.uhn.fhir.model.api.annotation.Child; 025import ca.uhn.fhir.model.api.annotation.Description; 026import org.apache.commons.lang3.StringUtils; 027import org.hl7.fhir.instance.model.api.IBase; 028import org.hl7.fhir.instance.model.api.IBaseDatatype; 029import org.hl7.fhir.instance.model.api.IBaseReference; 030import org.hl7.fhir.instance.model.api.IBaseResource; 031import org.hl7.fhir.instance.model.api.IPrimitiveType; 032 033import java.lang.reflect.Field; 034import java.util.ArrayList; 035import java.util.Collections; 036import java.util.HashMap; 037import java.util.List; 038import java.util.Map; 039import java.util.Set; 040 041public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefinition { 042 043 private List<Class<? extends IBase>> myChoiceTypes; 044 private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition; 045 private Map<Class<? extends IBase>, String> myDatatypeToElementName; 046 private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition; 047 private String myReferenceSuffix; 048 private List<Class<? extends IBaseResource>> myResourceTypes; 049 050 /** 051 * Constructor 052 */ 053 public RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation, List<Class<? extends IBase>> theChoiceTypes) { 054 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 055 056 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 057 } 058 059 /** 060 * Constructor 061 * 062 * For extension, if myChoiceTypes will be set some other way 063 */ 064 RuntimeChildChoiceDefinition(Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) { 065 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 066 } 067 068 void setChoiceTypes(List<Class<? extends IBase>> theChoiceTypes) { 069 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 070 } 071 072 public List<Class<? extends IBase>> getChoices() { 073 return myChoiceTypes; 074 } 075 076 @Override 077 public Set<String> getValidChildNames() { 078 return myNameToChildDefinition.keySet(); 079 } 080 081 @Override 082 public BaseRuntimeElementDefinition<?> getChildByName(String theName) { 083 assert myNameToChildDefinition.containsKey(theName); 084 085 return myNameToChildDefinition.get(theName); 086 } 087 088 @SuppressWarnings("unchecked") 089 @Override 090 void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 091 myNameToChildDefinition = new HashMap<String, BaseRuntimeElementDefinition<?>>(); 092 myDatatypeToElementName = new HashMap<Class<? extends IBase>, String>(); 093 myDatatypeToElementDefinition = new HashMap<Class<? extends IBase>, BaseRuntimeElementDefinition<?>>(); 094 myResourceTypes = new ArrayList<Class<? extends IBaseResource>>(); 095 096 myReferenceSuffix = "Reference"; 097 098 for (Class<? extends IBase> next : myChoiceTypes) { 099 100 String elementName = null; 101 BaseRuntimeElementDefinition<?> nextDef; 102 boolean nonPreferred = false; 103 if (IBaseResource.class.isAssignableFrom(next)) { 104 elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); 105 nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); 106 107 myNameToChildDefinition.put(getElementName() + "Reference", nextDef); 108 myNameToChildDefinition.put(getElementName() + "Resource", nextDef); 109 110 myResourceTypes.add((Class<? extends IBaseResource>) next); 111 112 } else { 113 nextDef = theClassToElementDefinitions.get(next); 114 BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef; 115 116 /* 117 * In HAPI 1.3 the following applied: 118 * Elements which are called foo[x] and have a choice which is a profiled datatype must use the 119 * unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the 120 * element fooString when encoded, because markdown is a profile of string. This is according to the 121 * FHIR spec 122 * 123 * Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion 124 * with Grahame. 125 */ 126 if (nextDef instanceof IRuntimeDatatypeDefinition) { 127 IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef; 128 if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) { 129 nextDefForChoice = null; 130 nonPreferred = true; 131 Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf(); 132 BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType); 133 elementName = getElementName() + StringUtils.capitalize(elementDef.getName()); 134 } 135 } 136 if (nextDefForChoice != null) { 137 elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName()); 138 } 139 } 140 141 // I don't see how elementName could be null here, but eclipse complains.. 142 if (elementName != null) { 143 if (myNameToChildDefinition.containsKey(elementName) == false || !nonPreferred) { 144 myNameToChildDefinition.put(elementName, nextDef); 145 } 146 } 147 148 /* 149 * If this is a resource reference, the element name is "fooNameReference" 150 */ 151 if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) { 152 next = theContext.getVersion().getResourceReferenceType(); 153 elementName = getElementName() + myReferenceSuffix; 154 myNameToChildDefinition.put(elementName, nextDef); 155 } 156 157 myDatatypeToElementDefinition.put(next, nextDef); 158 159 if (myDatatypeToElementName.containsKey(next)) { 160 String existing = myDatatypeToElementName.get(next); 161 if (!existing.equals(elementName)) { 162 throw new ConfigurationException(Msg.code(1693) + "Already have element name " + existing + " for datatype " + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName); 163 } 164 } else { 165 myDatatypeToElementName.put(next, elementName); 166 } 167 } 168 169 myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition); 170 myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName); 171 myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition); 172 myResourceTypes = Collections.unmodifiableList(myResourceTypes); 173 } 174 175 176 public List<Class<? extends IBaseResource>> getResourceTypes() { 177 return myResourceTypes; 178 } 179 180 @Override 181 public String getChildNameByDatatype(Class<? extends IBase> theDatatype) { 182 String retVal = myDatatypeToElementName.get(theDatatype); 183 return retVal; 184 } 185 186 @Override 187 public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) { 188 return myDatatypeToElementDefinition.get(theDatatype); 189 } 190 191 public Set<Class<? extends IBase>> getValidChildTypes() { 192 return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet())); 193 } 194 195}