001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2021 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.model.api.ExtensionDt;
024import ca.uhn.fhir.model.api.IDatatype;
025import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
026import org.apache.commons.text.WordUtils;
027import org.hl7.fhir.instance.model.api.IBase;
028
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.List;
033import java.util.Map;
034import java.util.Set;
035
036public class RuntimeChildUndeclaredExtensionDefinition extends BaseRuntimeChildDefinition {
037
038        private static final String VALUE_REFERENCE = "valueReference";
039        private static final String VALUE_RESOURCE = "valueResource";
040        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RuntimeChildUndeclaredExtensionDefinition.class);
041        private Map<String, BaseRuntimeElementDefinition<?>> myAttributeNameToDefinition;
042        private Map<Class<? extends IBase>, String> myDatatypeToAttributeName;
043        private Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> myDatatypeToDefinition;
044
045        public RuntimeChildUndeclaredExtensionDefinition() {
046                // nothing
047        }
048
049        private void addReferenceBinding(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions, String value) {
050                BaseRuntimeElementDefinition<?> def = findResourceReferenceDefinition(theClassToElementDefinitions);
051
052                myAttributeNameToDefinition.put(value, def);
053                /*
054                 * Resource reference - The correct name is 'valueReference' in DSTU2 and 'valueResource' in DSTU1
055                 */
056                if (!value.equals(VALUE_RESOURCE)) {
057                        myDatatypeToAttributeName.put(theContext.getVersion().getResourceReferenceType(), value);
058                        myDatatypeToDefinition.put(BaseResourceReferenceDt.class, def);
059                        myDatatypeToDefinition.put(theContext.getVersion().getResourceReferenceType(), def);
060                }
061
062        }
063
064        @Override
065        public IAccessor getAccessor() {
066                return new IAccessor() {
067                        @Override
068                        public List<IBase> getValues(IBase theTarget) {
069                                ExtensionDt target = (ExtensionDt) theTarget;
070                                if (target.getValue() != null) {
071                                        return Collections.singletonList(target.getValue());
072                                }
073                                return new ArrayList<>(target.getUndeclaredExtensions());
074                        }
075
076                };
077        }
078
079        @Override
080        public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
081                return myAttributeNameToDefinition.get(theName);
082        }
083
084        @Override
085        public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theType) {
086                return myDatatypeToDefinition.get(theType);
087        }
088
089        @Override
090        public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
091                return myDatatypeToAttributeName.get(theDatatype);
092        }
093
094        @Override
095        public String getElementName() {
096                return "extension";
097        }
098
099        @Override
100        public int getMax() {
101                return 1;
102        }
103
104        @Override
105        public int getMin() {
106                return 0;
107        }
108
109        @Override
110        public IMutator getMutator() {
111                return new IMutator() {
112                        @Override
113                        public void addValue(IBase theTarget, IBase theValue) {
114                                ExtensionDt target = (ExtensionDt) theTarget;
115                                target.setValue((IDatatype) theTarget);
116                        }
117
118                        @Override
119                        public void setValue(IBase theTarget, IBase theValue) {
120                                ExtensionDt target = (ExtensionDt) theTarget;
121                                target.setValue((IDatatype) theTarget);
122                        }
123                };
124        }
125
126        @Override
127        public Set<String> getValidChildNames() {
128                return myAttributeNameToDefinition.keySet();
129        }
130
131        @Override
132        public boolean isSummary() {
133                return false;
134        }
135
136        @Override
137        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
138                Map<String, BaseRuntimeElementDefinition<?>> datatypeAttributeNameToDefinition = new HashMap<>();
139                myDatatypeToAttributeName = new HashMap<>();
140                myDatatypeToDefinition = new HashMap<>();
141
142                for (BaseRuntimeElementDefinition<?> next : theClassToElementDefinitions.values()) {
143                        if (next instanceof IRuntimeDatatypeDefinition) {
144
145                                myDatatypeToDefinition.put(next.getImplementingClass(), next);
146
147                                boolean isSpecialization = ((IRuntimeDatatypeDefinition) next).isSpecialization();
148                                if (isSpecialization) {
149                                        ourLog.trace("Not adding specialization: {}", next.getImplementingClass());
150                                }
151
152
153                                if (!next.isStandardType()) {
154                                        continue;
155                                }
156
157                                String qualifiedName = next.getImplementingClass().getName();
158
159                                /*
160                                 * We don't want user-defined custom datatypes ending up overriding the built in
161                                 * types here. It would probably be better for there to be a way for
162                                 * a datatype to indicate via its annotation that it's a built in
163                                 * type.
164                                 */
165                                if (!qualifiedName.startsWith("ca.uhn.fhir.model")) {
166                                        if (!qualifiedName.startsWith("org.hl7.fhir")) {
167                                                continue;
168                                        }
169                                }
170
171                                String attrName = createExtensionChildName(next);
172                                if (isSpecialization && datatypeAttributeNameToDefinition.containsKey(attrName)) {
173                                        continue;
174                                }
175
176                                if (datatypeAttributeNameToDefinition.containsKey(attrName)) {
177                                        BaseRuntimeElementDefinition<?> existing = datatypeAttributeNameToDefinition.get(attrName);
178                                        // We do allow built-in standard types to override extension types with the same element name,
179                                        // e.g. how EnumerationType extends CodeType but both serialize to "code". In this case,
180                                        // CodeType should win. If we aren't in a situation like that, there is a problem with the
181                                        // model so we should bail.
182                                        if (!existing.isStandardType()) {
183                                                throw new ConfigurationException("More than one child of " + getElementName() + " matches attribute name " + attrName + ". Found [" + existing.getImplementingClass().getName() + "] and [" + next.getImplementingClass().getName() + "]");
184                                        }
185                                }
186
187                                datatypeAttributeNameToDefinition.put(attrName, next);
188                                datatypeAttributeNameToDefinition.put(attrName.toLowerCase(), next);
189                                myDatatypeToAttributeName.put(next.getImplementingClass(), attrName);
190                        }
191                }
192
193                myAttributeNameToDefinition = datatypeAttributeNameToDefinition;
194
195
196                /*
197                 * Resource reference - The correct name is 'valueReference' in DSTU2 and 'valueResource' in DSTU1
198                 */
199                addReferenceBinding(theContext, theClassToElementDefinitions, VALUE_RESOURCE);
200                addReferenceBinding(theContext, theClassToElementDefinitions, VALUE_REFERENCE);
201        }
202
203        public static String createExtensionChildName(BaseRuntimeElementDefinition<?> next) {
204                return "value" + WordUtils.capitalize(next.getName());
205        }
206
207}