001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2022 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.i18n.Msg;
024import ca.uhn.fhir.util.UrlUtil;
025import org.apache.commons.lang3.StringUtils;
026import org.apache.commons.lang3.Validate;
027import org.hl7.fhir.instance.model.api.IBase;
028
029import javax.annotation.Nonnull;
030import javax.annotation.Nullable;
031import java.lang.reflect.Constructor;
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
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 Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<>());
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(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
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.get(argumentType);
089                if (retVal == null) {
090                        for (Constructor<?> next : getImplementingClass().getConstructors()) {
091                                if (argumentType == VOID_CLASS) {
092                                        if (next.getParameterTypes().length == 0) {
093                                                retVal = (Constructor<T>) next;
094                                                break;
095                                        }
096                                } else if (next.getParameterTypes().length == 1) {
097                                        if (next.getParameterTypes()[0].isAssignableFrom(argumentType)) {
098                                                retVal = (Constructor<T>) next;
099                                                break;
100                                        }
101                                }
102                        }
103                        if (retVal == null) {
104                                throw new ConfigurationException(Msg.code(1695) + "Class " + getImplementingClass() + " 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(String theExtensionUrl, final String serverBaseUrl) {
115                validateSealed();
116                RuntimeChildDeclaredExtensionDefinition definition = myUrlToExtension.get(theExtensionUrl);
117                if (definition == null && StringUtils.isNotBlank(serverBaseUrl)) {
118                        for (final Map.Entry<String, RuntimeChildDeclaredExtensionDefinition> entry : myUrlToExtension.entrySet()) {
119                                final String key = (!UrlUtil.isValid(entry.getKey()) && StringUtils.isNotBlank(serverBaseUrl)) ? serverBaseUrl + entry.getKey() : entry.getKey();
120                                if (key.equals(theExtensionUrl)) {
121                                        definition = entry.getValue();
122                                        break;
123                                }
124                        }
125                }
126                return definition;
127        }
128
129        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
130                validateSealed();
131                return myExtensions;
132        }
133
134        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
135                validateSealed();
136                return myExtensionsModifier;
137        }
138
139        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
140                validateSealed();
141                return myExtensionsNonModifier;
142        }
143
144        public Class<? extends T> getImplementingClass() {
145                return myImplementingClass;
146        }
147
148        /**
149         * @return Returns the runtime name for this resource (i.e. the name that
150         * will be used in encoded messages)
151         */
152        public String getName() {
153                return myName;
154        }
155
156        public boolean hasExtensions() {
157                validateSealed();
158                return myExtensions.size() > 0;
159        }
160
161        public boolean isStandardType() {
162                return myStandardType;
163        }
164
165        public T newInstance() {
166                return newInstance(null);
167        }
168
169        public T newInstance(Object theArgument) {
170                try {
171                        if (theArgument == null) {
172                                return getConstructor(null).newInstance();
173                        }
174                        return getConstructor(theArgument).newInstance(theArgument);
175
176                } catch (Exception e) {
177                        throw new ConfigurationException(Msg.code(1696) + "Failed to instantiate type:" + getImplementingClass().getName(), e);
178                }
179        }
180
181        public BaseRuntimeElementDefinition<?> getRootParentDefinition() {
182                return myRootParentDefinition;
183        }
184
185        /**
186         * Invoked prior to use to perform any initialization and make object
187         * mutable.
188         *
189         * @param theContext TODO
190         */
191        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
192                for (BaseRuntimeChildDefinition next : myExtensions) {
193                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
194                }
195
196                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
197                        String extUrl = next.getExtensionUrl();
198                        if (myUrlToExtension.containsKey(extUrl)) {
199                                throw new ConfigurationException(Msg.code(1697) + "Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
200                        }
201                        myUrlToExtension.put(extUrl, next);
202                        if (next.isModifier()) {
203                                myExtensionsModifier.add(next);
204                        } else {
205                                myExtensionsNonModifier.add(next);
206                        }
207
208                }
209
210                myExtensions = Collections.unmodifiableList(myExtensions);
211
212                Class parent = myImplementingClass;
213                do {
214                        BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent);
215                        if (parentDefinition != null) {
216                                myRootParentDefinition = parentDefinition;
217                        }
218                        parent = parent.getSuperclass();
219                } while (!parent.equals(Object.class));
220
221        }
222
223        @Override
224        public String toString() {
225                return getClass().getSimpleName() + "[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
226        }
227
228        protected void validateSealed() {
229                /*
230                 * this does nothing, but BaseRuntimeElementCompositeDefinition
231                 * overrides this method to provide functionality because that class
232                 * defers the sealing process
233                 */
234        }
235
236        public BaseRuntimeChildDefinition getChildByName(String theChildName) {
237                return null;
238        }
239
240        public enum ChildTypeEnum {
241                COMPOSITE_DATATYPE,
242                /**
243                 * HL7.org structure style.
244                 */
245                CONTAINED_RESOURCE_LIST,
246                /**
247                 * HAPI structure style.
248                 */
249                CONTAINED_RESOURCES, EXTENSION_DECLARED,
250                ID_DATATYPE,
251                PRIMITIVE_DATATYPE,
252                /**
253                 * HAPI style.
254                 */
255                PRIMITIVE_XHTML,
256                /**
257                 * HL7.org style.
258                 */
259                PRIMITIVE_XHTML_HL7ORG,
260                RESOURCE,
261                RESOURCE_BLOCK,
262
263                UNDECL_EXT,
264
265        }
266
267}