001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2025 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.util.UrlUtil;
024import jakarta.annotation.Nonnull;
025import jakarta.annotation.Nullable;
026import org.apache.commons.lang3.StringUtils;
027import org.apache.commons.lang3.Validate;
028import org.hl7.fhir.instance.model.api.IBase;
029
030import java.lang.reflect.Constructor;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.List;
035import java.util.Map;
036import java.util.concurrent.ConcurrentHashMap;
037
038public abstract class BaseRuntimeElementDefinition<T extends IBase> {
039
040        private static final Class<Void> VOID_CLASS = Void.class;
041        private final Class<? extends T> myImplementingClass;
042        private final String myName;
043        private final boolean myStandardType;
044        private final Map<Class<?>, Constructor<T>> myConstructors = new ConcurrentHashMap<>();
045        private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<>();
046        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<>();
047        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<>();
048        private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<>();
049        private BaseRuntimeElementDefinition<?> myRootParentDefinition;
050
051        public BaseRuntimeElementDefinition(
052                        String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
053                assert StringUtils.isNotBlank(theName);
054                assert theImplementingClass != null;
055
056                String name = theName;
057                // TODO: remove this and fix for the model
058                if (name.endsWith("Dt")) {
059                        name = name.substring(0, name.length() - 2);
060                }
061
062                myName = name;
063                myStandardType = theStandardType;
064                myImplementingClass = theImplementingClass;
065        }
066
067        public void addExtension(@Nonnull RuntimeChildDeclaredExtensionDefinition theExtension) {
068                Validate.notNull(theExtension, "theExtension must not be null");
069                myExtensions.add(theExtension);
070        }
071
072        public abstract ChildTypeEnum getChildType();
073
074        public List<BaseRuntimeChildDefinition> getChildren() {
075                return Collections.emptyList();
076        }
077
078        @SuppressWarnings("unchecked")
079        private Constructor<T> getConstructor(@Nullable Object theArgument) {
080
081                Class<?> argumentType;
082                if (theArgument == null) {
083                        argumentType = VOID_CLASS;
084                } else {
085                        argumentType = theArgument.getClass();
086                }
087
088                Constructor<T> retVal = myConstructors.computeIfAbsent(argumentType, type -> {
089                        for (Constructor<?> next : getImplementingClass().getConstructors()) {
090                                if (type == VOID_CLASS) {
091                                        if (next.getParameterTypes().length == 0) {
092                                                return (Constructor<T>) next;
093                                        }
094                                } else if (next.getParameterTypes().length == 1 && next.getParameterTypes()[0].isAssignableFrom(type)) {
095                                        return (Constructor<T>) next;
096                                }
097                        }
098                        return null;
099                });
100
101                if (retVal == null) {
102                        throw new ConfigurationException(Msg.code(1695) + "Class " + getImplementingClass()
103                                        + " has no constructor with a single argument of type " + argumentType);
104                }
105
106                return retVal;
107        }
108
109        /**
110         * @return Returns null if none
111         */
112        public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(
113                        String theExtensionUrl, final String serverBaseUrl) {
114                validateSealed();
115                RuntimeChildDeclaredExtensionDefinition definition = myUrlToExtension.get(theExtensionUrl);
116                if (definition == null && StringUtils.isNotBlank(serverBaseUrl)) {
117                        for (final Map.Entry<String, RuntimeChildDeclaredExtensionDefinition> entry : myUrlToExtension.entrySet()) {
118                                final String key = (!UrlUtil.isValid(entry.getKey()) && StringUtils.isNotBlank(serverBaseUrl))
119                                                ? serverBaseUrl + entry.getKey()
120                                                : entry.getKey();
121                                if (key.equals(theExtensionUrl)) {
122                                        definition = entry.getValue();
123                                        break;
124                                }
125                        }
126                }
127                return definition;
128        }
129
130        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
131                validateSealed();
132                return myExtensions;
133        }
134
135        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
136                validateSealed();
137                return myExtensionsModifier;
138        }
139
140        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
141                validateSealed();
142                return myExtensionsNonModifier;
143        }
144
145        public Class<? extends T> getImplementingClass() {
146                return myImplementingClass;
147        }
148
149        /**
150         * @return Returns the runtime name for this resource (i.e. the name that
151         * will be used in encoded messages)
152         */
153        public String getName() {
154                return myName;
155        }
156
157        public boolean hasExtensions() {
158                validateSealed();
159                return myExtensions.size() > 0;
160        }
161
162        public boolean isStandardType() {
163                return myStandardType;
164        }
165
166        @Nonnull
167        public T newInstance() {
168                return newInstance(null);
169        }
170
171        @Nonnull
172        public T newInstance(Object theArgument) {
173                try {
174                        if (theArgument == null) {
175                                return getConstructor(null).newInstance();
176                        }
177                        return getConstructor(theArgument).newInstance(theArgument);
178
179                } catch (Exception e) {
180                        throw new ConfigurationException(
181                                        Msg.code(1696) + "Failed to instantiate type:"
182                                                        + getImplementingClass().getName(),
183                                        e);
184                }
185        }
186
187        public BaseRuntimeElementDefinition<?> getRootParentDefinition() {
188                return myRootParentDefinition;
189        }
190
191        /**
192         * Invoked prior to use to perform any initialization and make object
193         * mutable.
194         *
195         * @param theContext TODO
196         */
197        void sealAndInitialize(
198                        FhirContext theContext,
199                        Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
200                for (BaseRuntimeChildDefinition next : myExtensions) {
201                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
202                }
203
204                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
205                        String extUrl = next.getExtensionUrl();
206                        if (myUrlToExtension.containsKey(extUrl)) {
207                                throw new ConfigurationException(
208                                                Msg.code(1697) + "Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
209                        }
210                        myUrlToExtension.put(extUrl, next);
211                        if (next.isModifier()) {
212                                myExtensionsModifier.add(next);
213                        } else {
214                                myExtensionsNonModifier.add(next);
215                        }
216                }
217
218                myExtensions = Collections.unmodifiableList(myExtensions);
219
220                Class parent = myImplementingClass;
221                do {
222                        BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent);
223                        if (parentDefinition != null) {
224                                myRootParentDefinition = parentDefinition;
225                        }
226                        parent = parent.getSuperclass();
227                } while (!parent.equals(Object.class));
228        }
229
230        @Override
231        public String toString() {
232                return getClass().getSimpleName() + "[" + getName() + ", "
233                                + getImplementingClass().getSimpleName() + "]";
234        }
235
236        protected void validateSealed() {
237                /*
238                 * this does nothing, but BaseRuntimeElementCompositeDefinition
239                 * overrides this method to provide functionality because that class
240                 * defers the sealing process
241                 */
242        }
243
244        public BaseRuntimeChildDefinition getChildByName(String theChildName) {
245                return null;
246        }
247
248        public enum ChildTypeEnum {
249                COMPOSITE_DATATYPE,
250                /**
251                 * HL7.org structure style.
252                 */
253                CONTAINED_RESOURCE_LIST,
254                /**
255                 * HAPI structure style.
256                 */
257                CONTAINED_RESOURCES,
258                EXTENSION_DECLARED,
259                ID_DATATYPE,
260                PRIMITIVE_DATATYPE,
261                /**
262                 * HAPI style.
263                 */
264                PRIMITIVE_XHTML,
265                /**
266                 * HL7.org style.
267                 */
268                PRIMITIVE_XHTML_HL7ORG,
269                RESOURCE,
270                RESOURCE_BLOCK,
271
272                UNDECL_EXT,
273        }
274}