001/*
002 * #%L
003 * HAPI FHIR - Core Library
004 * %%
005 * Copyright (C) 2014 - 2023 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 org.apache.commons.lang3.StringUtils;
025import org.apache.commons.lang3.Validate;
026import org.hl7.fhir.instance.model.api.IBase;
027
028import javax.annotation.Nonnull;
029import javax.annotation.Nullable;
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(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
051                assert StringUtils.isNotBlank(theName);
052                assert theImplementingClass != null;
053
054                String name = theName;
055                // TODO: remove this and fix for the model
056                if (name.endsWith("Dt")) {
057                        name = name.substring(0, name.length() - 2);
058                }
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() + " has no constructor with a single argument of type " + argumentType);
104                        }
105                        myConstructors.put(argumentType, retVal);
106                }
107                return retVal;
108        }
109
110        /**
111         * @return Returns null if none
112         */
113        public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(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)) ? serverBaseUrl + entry.getKey() : entry.getKey();
119                                if (key.equals(theExtensionUrl)) {
120                                        definition = entry.getValue();
121                                        break;
122                                }
123                        }
124                }
125                return definition;
126        }
127
128        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
129                validateSealed();
130                return myExtensions;
131        }
132
133        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
134                validateSealed();
135                return myExtensionsModifier;
136        }
137
138        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
139                validateSealed();
140                return myExtensionsNonModifier;
141        }
142
143        public Class<? extends T> getImplementingClass() {
144                return myImplementingClass;
145        }
146
147        /**
148         * @return Returns the runtime name for this resource (i.e. the name that
149         * will be used in encoded messages)
150         */
151        public String getName() {
152                return myName;
153        }
154
155        public boolean hasExtensions() {
156                validateSealed();
157                return myExtensions.size() > 0;
158        }
159
160        public boolean isStandardType() {
161                return myStandardType;
162        }
163
164        public T newInstance() {
165                return newInstance(null);
166        }
167
168        public T newInstance(Object theArgument) {
169                try {
170                        if (theArgument == null) {
171                                return getConstructor(null).newInstance();
172                        }
173                        return getConstructor(theArgument).newInstance(theArgument);
174
175                } catch (Exception e) {
176                        throw new ConfigurationException(Msg.code(1696) + "Failed to instantiate type:" + getImplementingClass().getName(), e);
177                }
178        }
179
180        public BaseRuntimeElementDefinition<?> getRootParentDefinition() {
181                return myRootParentDefinition;
182        }
183
184        /**
185         * Invoked prior to use to perform any initialization and make object
186         * mutable.
187         *
188         * @param theContext TODO
189         */
190        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
191                for (BaseRuntimeChildDefinition next : myExtensions) {
192                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
193                }
194
195                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
196                        String extUrl = next.getExtensionUrl();
197                        if (myUrlToExtension.containsKey(extUrl)) {
198                                throw new ConfigurationException(Msg.code(1697) + "Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
199                        }
200                        myUrlToExtension.put(extUrl, next);
201                        if (next.isModifier()) {
202                                myExtensionsModifier.add(next);
203                        } else {
204                                myExtensionsNonModifier.add(next);
205                        }
206
207                }
208
209                myExtensions = Collections.unmodifiableList(myExtensions);
210
211                Class parent = myImplementingClass;
212                do {
213                        BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent);
214                        if (parentDefinition != null) {
215                                myRootParentDefinition = parentDefinition;
216                        }
217                        parent = parent.getSuperclass();
218                } while (!parent.equals(Object.class));
219
220        }
221
222        @Override
223        public String toString() {
224                return getClass().getSimpleName() + "[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
225        }
226
227        protected void validateSealed() {
228                /*
229                 * this does nothing, but BaseRuntimeElementCompositeDefinition
230                 * overrides this method to provide functionality because that class
231                 * defers the sealing process
232                 */
233        }
234
235        public BaseRuntimeChildDefinition getChildByName(String theChildName) {
236                return null;
237        }
238
239        public enum ChildTypeEnum {
240                COMPOSITE_DATATYPE,
241                /**
242                 * HL7.org structure style.
243                 */
244                CONTAINED_RESOURCE_LIST,
245                /**
246                 * HAPI structure style.
247                 */
248                CONTAINED_RESOURCES, EXTENSION_DECLARED,
249                ID_DATATYPE,
250                PRIMITIVE_DATATYPE,
251                /**
252                 * HAPI style.
253                 */
254                PRIMITIVE_XHTML,
255                /**
256                 * HL7.org style.
257                 */
258                PRIMITIVE_XHTML_HL7ORG,
259                RESOURCE,
260                RESOURCE_BLOCK,
261
262                UNDECL_EXT,
263
264        }
265
266}