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.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        public T newInstance() {
167                return newInstance(null);
168        }
169
170        public T newInstance(Object theArgument) {
171                try {
172                        if (theArgument == null) {
173                                return getConstructor(null).newInstance();
174                        }
175                        return getConstructor(theArgument).newInstance(theArgument);
176
177                } catch (Exception e) {
178                        throw new ConfigurationException(
179                                        Msg.code(1696) + "Failed to instantiate type:"
180                                                        + getImplementingClass().getName(),
181                                        e);
182                }
183        }
184
185        public BaseRuntimeElementDefinition<?> getRootParentDefinition() {
186                return myRootParentDefinition;
187        }
188
189        /**
190         * Invoked prior to use to perform any initialization and make object
191         * mutable.
192         *
193         * @param theContext TODO
194         */
195        void sealAndInitialize(
196                        FhirContext theContext,
197                        Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
198                for (BaseRuntimeChildDefinition next : myExtensions) {
199                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
200                }
201
202                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
203                        String extUrl = next.getExtensionUrl();
204                        if (myUrlToExtension.containsKey(extUrl)) {
205                                throw new ConfigurationException(
206                                                Msg.code(1697) + "Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
207                        }
208                        myUrlToExtension.put(extUrl, next);
209                        if (next.isModifier()) {
210                                myExtensionsModifier.add(next);
211                        } else {
212                                myExtensionsNonModifier.add(next);
213                        }
214                }
215
216                myExtensions = Collections.unmodifiableList(myExtensions);
217
218                Class parent = myImplementingClass;
219                do {
220                        BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent);
221                        if (parentDefinition != null) {
222                                myRootParentDefinition = parentDefinition;
223                        }
224                        parent = parent.getSuperclass();
225                } while (!parent.equals(Object.class));
226        }
227
228        @Override
229        public String toString() {
230                return getClass().getSimpleName() + "[" + getName() + ", "
231                                + getImplementingClass().getSimpleName() + "]";
232        }
233
234        protected void validateSealed() {
235                /*
236                 * this does nothing, but BaseRuntimeElementCompositeDefinition
237                 * overrides this method to provide functionality because that class
238                 * defers the sealing process
239                 */
240        }
241
242        public BaseRuntimeChildDefinition getChildByName(String theChildName) {
243                return null;
244        }
245
246        public enum ChildTypeEnum {
247                COMPOSITE_DATATYPE,
248                /**
249                 * HL7.org structure style.
250                 */
251                CONTAINED_RESOURCE_LIST,
252                /**
253                 * HAPI structure style.
254                 */
255                CONTAINED_RESOURCES,
256                EXTENSION_DECLARED,
257                ID_DATATYPE,
258                PRIMITIVE_DATATYPE,
259                /**
260                 * HAPI style.
261                 */
262                PRIMITIVE_XHTML,
263                /**
264                 * HL7.org style.
265                 */
266                PRIMITIVE_XHTML_HL7ORG,
267                RESOURCE,
268                RESOURCE_BLOCK,
269
270                UNDECL_EXT,
271        }
272}