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.model.api.IElement;
023import ca.uhn.fhir.model.api.annotation.Child;
024import ca.uhn.fhir.model.api.annotation.Description;
025import ca.uhn.fhir.model.api.annotation.Extension;
026import ca.uhn.fhir.util.ReflectionUtil;
027import org.hl7.fhir.instance.model.api.IBase;
028
029import java.lang.reflect.Field;
030import java.lang.reflect.Modifier;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036
037import static org.apache.commons.lang3.StringUtils.isNotBlank;
038
039public class RuntimeChildDeclaredExtensionDefinition extends RuntimeChildChoiceDefinition {
040
041        private boolean myDefinedLocally;
042        private String myExtensionUrl;
043        private boolean myModifier;
044        private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToChildExtension;
045        private volatile Object myInstanceConstructorArguments;
046        private Class<?> myEnumerationType;
047        private Class<? extends IBase> myChildType;
048        private RuntimeResourceBlockDefinition myChildResourceBlock;
049        private BaseRuntimeElementDefinition<?> myChildDef;
050
051        /**
052         * @param theBoundTypeBinder
053         *           If the child is of a type that requires a constructor argument to instantiate, this is the argument to
054         *           use
055         * @param theDefinedLocally
056         *           See {@link Extension#definedLocally()}
057         */
058        RuntimeChildDeclaredExtensionDefinition(
059                        Field theField,
060                        Child theChild,
061                        Description theDescriptionAnnotation,
062                        Extension theExtension,
063                        String theElementName,
064                        String theExtensionUrl,
065                        Class<? extends IBase> theChildType,
066                        Object theBoundTypeBinder)
067                        throws ConfigurationException {
068                super(theField, theElementName, theChild, theDescriptionAnnotation);
069                assert isNotBlank(theExtensionUrl);
070                myExtensionUrl = theExtensionUrl;
071                myChildType = theChildType;
072                myDefinedLocally = theExtension.definedLocally();
073                myModifier = theExtension.isModifier();
074                myInstanceConstructorArguments = theBoundTypeBinder;
075
076                List<Class<? extends IBase>> choiceTypes = new ArrayList<Class<? extends IBase>>();
077                for (Class<? extends IElement> next : theChild.type()) {
078                        choiceTypes.add(next);
079                }
080
081                if (Modifier.isAbstract(theChildType.getModifiers()) == false) {
082                        choiceTypes.add(theChildType);
083                }
084
085                setChoiceTypes(choiceTypes, Collections.emptyList());
086        }
087
088        @Override
089        public String getElementName() {
090                return "value";
091        }
092
093        @Override
094        public Object getInstanceConstructorArguments() {
095                Object retVal = myInstanceConstructorArguments;
096                if (retVal == null && myEnumerationType != null) {
097                        retVal = RuntimeChildPrimitiveEnumerationDatatypeDefinition.toEnumFactory(myEnumerationType);
098                        myInstanceConstructorArguments = retVal;
099                }
100                return retVal;
101        }
102
103        public void setEnumerationType(Class<?> theEnumerationType) {
104                myEnumerationType = theEnumerationType;
105        }
106
107        @Override
108        public String getChildNameByDatatype(Class<? extends IBase> theDatatype) {
109
110                String retVal = super.getChildNameByDatatype(theDatatype);
111
112                if (retVal != null) {
113                        BaseRuntimeElementDefinition<?> childDef = super.getChildElementDefinitionByDatatype(theDatatype);
114                        if (childDef instanceof RuntimeResourceBlockDefinition) {
115                                // Child is a newted extension
116                                retVal = null;
117                        }
118                }
119
120                if (retVal == null) {
121                        if (myModifier) {
122                                return "modifierExtension";
123                        }
124                        return "extension";
125                }
126                return retVal;
127        }
128
129        @Override
130        public BaseRuntimeElementDefinition<?> getChildByName(String theName) {
131                String name = theName;
132                if ("extension".equals(name) || "modifierExtension".equals(name)) {
133                        if (myChildResourceBlock != null) {
134                                return myChildResourceBlock;
135                        }
136                        if (myChildDef != null) {
137                                return myChildDef;
138                        }
139                }
140
141                if (getValidChildNames().contains(name) == false) {
142                        return null;
143                }
144
145                return super.getChildByName(name);
146        }
147
148        @Override
149        public BaseRuntimeElementDefinition<?> getChildElementDefinitionByDatatype(Class<? extends IBase> theDatatype) {
150                if (myChildResourceBlock != null) {
151                        if (myChildResourceBlock.getImplementingClass().equals(theDatatype)) {
152                                return myChildResourceBlock;
153                        }
154                }
155                return super.getChildElementDefinitionByDatatype(theDatatype);
156        }
157
158        public RuntimeChildDeclaredExtensionDefinition getChildExtensionForUrl(String theUrl) {
159                return myUrlToChildExtension.get(theUrl);
160        }
161
162        @Override
163        public String getExtensionUrl() {
164                return myExtensionUrl;
165        }
166
167        public boolean isDefinedLocally() {
168                return myDefinedLocally;
169        }
170
171        @Override
172        public boolean isModifier() {
173                return myModifier;
174        }
175
176        @Override
177        void sealAndInitialize(
178                        FhirContext theContext,
179                        Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
180                myUrlToChildExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
181
182                BaseRuntimeElementDefinition<?> elementDef = theClassToElementDefinitions.get(myChildType);
183
184                /*
185                 * This will happen for any type that isn't defined in the base set of
186                 * built-in types, e.g. custom structures or custom extensions
187                 */
188                if (elementDef == null) {
189                        if (Modifier.isAbstract(myChildType.getModifiers()) == false) {
190                                elementDef = theContext.getElementDefinition(myChildType);
191                        }
192                }
193
194                if (elementDef instanceof RuntimePrimitiveDatatypeDefinition
195                                || elementDef instanceof RuntimeCompositeDatatypeDefinition) {
196                        //                      myDatatypeChildName = "value" + elementDef.getName().substring(0, 1).toUpperCase() +
197                        // elementDef.getName().substring(1);
198                        //                      if ("valueResourceReference".equals(myDatatypeChildName)) {
199                        // Per one of the examples here: http://hl7.org/implement/standards/fhir/extensibility.html#extension
200                        //                              myDatatypeChildName = "valueResource";
201                        //                              List<Class<? extends IBaseResource>> types = new ArrayList<Class<? extends IBaseResource>>();
202                        //                              types.add(IBaseResource.class);
203                        //                              myChildDef = findResourceReferenceDefinition(theClassToElementDefinitions);
204                        //                      } else {
205                        myChildDef = elementDef;
206                        //                      }
207                } else if (elementDef instanceof RuntimeResourceBlockDefinition) {
208                        RuntimeResourceBlockDefinition extDef = ((RuntimeResourceBlockDefinition) elementDef);
209                        for (RuntimeChildDeclaredExtensionDefinition next : extDef.getExtensions()) {
210                                myUrlToChildExtension.put(next.getExtensionUrl(), next);
211                        }
212                        myChildResourceBlock = (RuntimeResourceBlockDefinition) elementDef;
213                }
214
215                myUrlToChildExtension = Collections.unmodifiableMap(myUrlToChildExtension);
216
217                super.sealAndInitialize(theContext, theClassToElementDefinitions);
218        }
219
220        public IBase newInstance() {
221                return ReflectionUtil.newInstance(myChildType);
222        }
223
224        public Class<? extends IBase> getChildType() {
225                return myChildType;
226        }
227}