001/* 002 * #%L 003 * HAPI FHIR - Core Library 004 * %% 005 * Copyright (C) 2014 - 2024 Smile CDR, Inc. 006 * %% 007 * Licensed under the Apache License, Version 2.0 (the "License"); 008 * you may not use this file except in compliance with the License. 009 * You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 * #L% 019 */ 020package ca.uhn.fhir.context; 021 022import ca.uhn.fhir.i18n.Msg; 023import ca.uhn.fhir.model.api.annotation.Child; 024import ca.uhn.fhir.model.api.annotation.Description; 025import jakarta.annotation.Nonnull; 026import org.apache.commons.lang3.StringUtils; 027import org.apache.commons.lang3.Validate; 028import org.hl7.fhir.instance.model.api.IBase; 029import org.hl7.fhir.instance.model.api.IBaseDatatype; 030import org.hl7.fhir.instance.model.api.IBaseReference; 031import org.hl7.fhir.instance.model.api.IBaseResource; 032import org.hl7.fhir.instance.model.api.IPrimitiveType; 033 034import java.lang.reflect.Field; 035import java.util.ArrayList; 036import java.util.Collections; 037import java.util.HashMap; 038import java.util.List; 039import java.util.Map; 040import java.util.Set; 041 042public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefinition { 043 044 private List<Class<? extends IBase>> myChoiceTypes; 045 private Map<String, BaseRuntimeElementDefinition<?>> myNameToChildDefinition; 046 private Map<Class<? extends IBase>, String> myDatatypeToElementName; 047 private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToElementDefinition; 048 private String myReferenceSuffix; 049 private List<Class<? extends IBaseResource>> myResourceTypes; 050 private List<Class<? extends IBase>> mySpecializationChoiceTypes = Collections.emptyList(); 051 052 /** 053 * Constructor 054 */ 055 public RuntimeChildChoiceDefinition( 056 Field theField, 057 String theElementName, 058 Child theChildAnnotation, 059 Description theDescriptionAnnotation, 060 List<Class<? extends IBase>> theChoiceTypes) { 061 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 062 063 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 064 } 065 066 /** 067 * Constructor 068 * 069 * For extension, if myChoiceTypes will be set some other way 070 */ 071 RuntimeChildChoiceDefinition( 072 Field theField, String theElementName, Child theChildAnnotation, Description theDescriptionAnnotation) { 073 super(theField, theChildAnnotation, theDescriptionAnnotation, theElementName); 074 } 075 076 void setChoiceTypes( 077 @Nonnull List<Class<? extends IBase>> theChoiceTypes, 078 @Nonnull List<Class<? extends IBase>> theSpecializationChoiceTypes) { 079 Validate.notNull(theChoiceTypes, "theChoiceTypes must not be null"); 080 Validate.notNull(theSpecializationChoiceTypes, "theSpecializationChoiceTypes must not be null"); 081 myChoiceTypes = Collections.unmodifiableList(theChoiceTypes); 082 mySpecializationChoiceTypes = Collections.unmodifiableList(theSpecializationChoiceTypes); 083 } 084 085 public List<Class<? extends IBase>> getChoices() { 086 return myChoiceTypes; 087 } 088 089 @Override 090 public Set<String> getValidChildNames() { 091 return myNameToChildDefinition.keySet(); 092 } 093 094 @Override 095 public BaseRuntimeElementDefinition<?> getChildByName(String theName) { 096 assert myNameToChildDefinition.containsKey(theName) 097 : "Can't find child '" + theName + "' in names: " + myNameToChildDefinition.keySet(); 098 099 return myNameToChildDefinition.get(theName); 100 } 101 102 @SuppressWarnings("unchecked") 103 @Override 104 void sealAndInitialize( 105 FhirContext theContext, 106 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 107 myNameToChildDefinition = new HashMap<>(); 108 myDatatypeToElementName = new HashMap<>(); 109 myDatatypeToElementDefinition = new HashMap<>(); 110 myResourceTypes = new ArrayList<>(); 111 112 myReferenceSuffix = "Reference"; 113 114 sealAndInitializeChoiceTypes(theContext, theClassToElementDefinitions, mySpecializationChoiceTypes, true); 115 sealAndInitializeChoiceTypes(theContext, theClassToElementDefinitions, myChoiceTypes, false); 116 117 myNameToChildDefinition = Collections.unmodifiableMap(myNameToChildDefinition); 118 myDatatypeToElementName = Collections.unmodifiableMap(myDatatypeToElementName); 119 myDatatypeToElementDefinition = Collections.unmodifiableMap(myDatatypeToElementDefinition); 120 myResourceTypes = Collections.unmodifiableList(myResourceTypes); 121 } 122 123 private void sealAndInitializeChoiceTypes( 124 FhirContext theContext, 125 Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions, 126 List<Class<? extends IBase>> choiceTypes, 127 boolean theIsSpecilization) { 128 for (Class<? extends IBase> next : choiceTypes) { 129 130 String elementName = null; 131 BaseRuntimeElementDefinition<?> nextDef; 132 boolean nonPreferred = false; 133 if (IBaseResource.class.isAssignableFrom(next)) { 134 elementName = getElementName() + StringUtils.capitalize(next.getSimpleName()); 135 nextDef = findResourceReferenceDefinition(theClassToElementDefinitions); 136 137 if (!theIsSpecilization) { 138 myNameToChildDefinition.put(getElementName() + "Reference", nextDef); 139 myNameToChildDefinition.put(getElementName() + "Resource", nextDef); 140 } 141 142 myResourceTypes.add((Class<? extends IBaseResource>) next); 143 144 } else { 145 nextDef = theClassToElementDefinitions.get(next); 146 BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef; 147 148 /* 149 * In HAPI 1.3 the following applied: 150 * Elements which are called foo[x] and have a choice which is a profiled datatype must use the 151 * unprofiled datatype as the element name. E.g. if foo[x] allows markdown as a datatype, it calls the 152 * element fooString when encoded, because markdown is a profile of string. This is according to the 153 * FHIR spec 154 * 155 * Note that as of HAPI 1.4 this applies only to non-primitive datatypes after discussion 156 * with Grahame. 157 */ 158 if (nextDef instanceof IRuntimeDatatypeDefinition) { 159 IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef; 160 if (nextDefDatatype.getProfileOf() != null && !IPrimitiveType.class.isAssignableFrom(next)) { 161 nextDefForChoice = null; 162 nonPreferred = true; 163 Class<? extends IBaseDatatype> profileType = nextDefDatatype.getProfileOf(); 164 BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(profileType); 165 elementName = getElementName() + StringUtils.capitalize(elementDef.getName()); 166 } 167 } 168 if (nextDefForChoice != null) { 169 elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName()); 170 } 171 } 172 173 // I don't see how elementName could be null here, but eclipse complains.. 174 if (!theIsSpecilization) { 175 if (elementName != null) { 176 if (!myNameToChildDefinition.containsKey(elementName) || !nonPreferred) { 177 myNameToChildDefinition.put(elementName, nextDef); 178 } 179 } 180 181 /* 182 * If this is a resource reference, the element name is "fooNameReference" 183 */ 184 if (IBaseResource.class.isAssignableFrom(next) || IBaseReference.class.isAssignableFrom(next)) { 185 next = theContext.getVersion().getResourceReferenceType(); 186 elementName = getElementName() + myReferenceSuffix; 187 myNameToChildDefinition.put(elementName, nextDef); 188 } 189 } 190 191 myDatatypeToElementDefinition.put(next, nextDef); 192 193 if (myDatatypeToElementName.containsKey(next)) { 194 String existing = myDatatypeToElementName.get(next); 195 if (!existing.equals(elementName)) { 196 throw new ConfigurationException( 197 Msg.code(1693) + "Already have element name " + existing + " for datatype " 198 + next.getSimpleName() + " in " + getElementName() + ", cannot add " + elementName); 199 } 200 } else { 201 myDatatypeToElementName.put(next, elementName); 202 } 203 } 204 } 205 206 public List<Class<? extends IBaseResource>> getResourceTypes() { 207 return myResourceTypes; 208 } 209 210 @Override 211 public String getChildNameByDatatype(Class<? extends IBase> theDatatype) { 212 return myDatatypeToElementName.get(theDatatype); 213 } 214 215 @Override 216 public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) { 217 return myDatatypeToElementDefinition.get(theDatatype); 218 } 219 220 public Set<Class<? extends IBase>> getValidChildTypes() { 221 return Collections.unmodifiableSet((myDatatypeToElementDefinition.keySet())); 222 } 223}