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