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