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