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