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;
036
037public abstract class BaseRuntimeElementDefinition<T extends IBase> {
038
039        private static final Class<Void> VOID_CLASS = Void.class;
040        private final Class<? extends T> myImplementingClass;
041        private final String myName;
042        private final boolean myStandardType;
043        private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<>());
044        private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<>();
045        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<>();
046        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<>();
047        private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<>();
048        private BaseRuntimeElementDefinition<?> myRootParentDefinition;
049
050        public BaseRuntimeElementDefinition(
051                        String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
052                assert StringUtils.isNotBlank(theName);
053                assert theImplementingClass != null;
054
055                String name = theName;
056                // TODO: remove this and fix for the model
057                if (name.endsWith("Dt")) {
058                        name = name.substring(0, name.length() - 2);
059                }
060
061                myName = name;
062                myStandardType = theStandardType;
063                myImplementingClass = theImplementingClass;
064        }
065
066        public void addExtension(@Nonnull RuntimeChildDeclaredExtensionDefinition theExtension) {
067                Validate.notNull(theExtension, "theExtension must not be null");
068                myExtensions.add(theExtension);
069        }
070
071        public abstract ChildTypeEnum getChildType();
072
073        public List<BaseRuntimeChildDefinition> getChildren() {
074                return Collections.emptyList();
075        }
076
077        @SuppressWarnings("unchecked")
078        private Constructor<T> getConstructor(@Nullable Object theArgument) {
079
080                Class<?> argumentType;
081                if (theArgument == null) {
082                        argumentType = VOID_CLASS;
083                } else {
084                        argumentType = theArgument.getClass();
085                }
086
087                Constructor<T> retVal = myConstructors.get(argumentType);
088                if (retVal == null) {
089                        for (Constructor<?> next : getImplementingClass().getConstructors()) {
090                                if (argumentType == VOID_CLASS) {
091                                        if (next.getParameterTypes().length == 0) {
092                                                retVal = (Constructor<T>) next;
093                                                break;
094                                        }
095                                } else if (next.getParameterTypes().length == 1) {
096                                        if (next.getParameterTypes()[0].isAssignableFrom(argumentType)) {
097                                                retVal = (Constructor<T>) next;
098                                                break;
099                                        }
100                                }
101                        }
102                        if (retVal == null) {
103                                throw new ConfigurationException(Msg.code(1695) + "Class " + getImplementingClass()
104                                                + " has no constructor with a single argument of type " + argumentType);
105                        }
106                        myConstructors.put(argumentType, retVal);
107                }
108                return retVal;
109        }
110
111        /**
112         * @return Returns null if none
113         */
114        public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(
115                        String theExtensionUrl, final String serverBaseUrl) {
116                validateSealed();
117                RuntimeChildDeclaredExtensionDefinition definition = myUrlToExtension.get(theExtensionUrl);
118                if (definition == null && StringUtils.isNotBlank(serverBaseUrl)) {
119                        for (final Map.Entry<String, RuntimeChildDeclaredExtensionDefinition> entry : myUrlToExtension.entrySet()) {
120                                final String key = (!UrlUtil.isValid(entry.getKey()) && StringUtils.isNotBlank(serverBaseUrl))
121                                                ? serverBaseUrl + entry.getKey()
122                                                : entry.getKey();
123                                if (key.equals(theExtensionUrl)) {
124                                        definition = entry.getValue();
125                                        break;
126                                }
127                        }
128                }
129                return definition;
130        }
131
132        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
133                validateSealed();
134                return myExtensions;
135        }
136
137        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
138                validateSealed();
139                return myExtensionsModifier;
140        }
141
142        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
143                validateSealed();
144                return myExtensionsNonModifier;
145        }
146
147        public Class<? extends T> getImplementingClass() {
148                return myImplementingClass;
149        }
150
151        /**
152         * @return Returns the runtime name for this resource (i.e. the name that
153         * will be used in encoded messages)
154         */
155        public String getName() {
156                return myName;
157        }
158
159        public boolean hasExtensions() {
160                validateSealed();
161                return myExtensions.size() > 0;
162        }
163
164        public boolean isStandardType() {
165                return myStandardType;
166        }
167
168        public T newInstance() {
169                return newInstance(null);
170        }
171
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}