001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2020 University Health Network
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                                if (!isSpecialization) {
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 (datatypeAttributeNameToDefinition.containsKey(attrName)) {
174                                                BaseRuntimeElementDefinition<?> existing = datatypeAttributeNameToDefinition.get(attrName);
175                                                throw new ConfigurationException("More than one child of " + getElementName() + " matches attribute name " + attrName + ". Found [" + existing.getImplementingClass().getName() + "] and [" + next.getImplementingClass().getName() + "]");
176                                        }
177                                        datatypeAttributeNameToDefinition.put(attrName, next);
178                                        datatypeAttributeNameToDefinition.put(attrName.toLowerCase(), next);
179                                        myDatatypeToAttributeName.put(next.getImplementingClass(), attrName);
180                                }
181                        }
182                }
183
184                myAttributeNameToDefinition = datatypeAttributeNameToDefinition;
185
186
187                /*
188                 * Resource reference - The correct name is 'valueReference' in DSTU2 and 'valueResource' in DSTU1
189                 */
190                addReferenceBinding(theContext, theClassToElementDefinitions, VALUE_RESOURCE);
191                addReferenceBinding(theContext, theClassToElementDefinitions, VALUE_REFERENCE);
192        }
193
194        public static String createExtensionChildName(BaseRuntimeElementDefinition<?> next) {
195                return "value" + WordUtils.capitalize(next.getName());
196        }
197
198}