001package ca.uhn.fhir.context;
002
003/*
004 * #%L
005 * HAPI FHIR - Core Library
006 * %%
007 * Copyright (C) 2014 - 2017 University Health Network
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 java.lang.reflect.Constructor;
024import java.util.*;
025import ca.uhn.fhir.util.UrlUtil;
026
027import org.apache.commons.lang3.StringUtils;
028import org.hl7.fhir.instance.model.api.IBase;
029
030public abstract class BaseRuntimeElementDefinition<T extends IBase> {
031
032        private static final Class<Void> VOID_CLASS = Void.class;
033        
034        private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
035        private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
036        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
037        private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
038        private final Class<? extends T> myImplementingClass;
039        private final String myName;
040        private final boolean myStandardType;
041        private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
042
043        public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
044                assert StringUtils.isNotBlank(theName);
045                assert theImplementingClass != null;
046
047                String name = theName;
048                // TODO: remove this and fix for the model
049                if (name.endsWith("Dt")) {
050                        name = name.substring(0, name.length() - 2);
051                }
052                
053                
054                myName = name;
055                myStandardType = theStandardType;
056                myImplementingClass = theImplementingClass;
057        }
058
059        public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
060                if (theExtension == null) {
061                        throw new NullPointerException();
062                }
063                myExtensions.add(theExtension);
064        }
065
066        public abstract ChildTypeEnum getChildType();
067
068        @SuppressWarnings("unchecked")
069        private Constructor<T> getConstructor(Object theArgument) {
070                
071                Class<? extends Object> argumentType;
072                if (theArgument == null) {
073                        argumentType = VOID_CLASS;
074                } else {
075                        argumentType = theArgument.getClass();
076                }
077                
078                Constructor<T> retVal = myConstructors.get(argumentType);
079                if (retVal == null) {
080                        for (Constructor<?> next : getImplementingClass().getConstructors()) {
081                                if (argumentType == VOID_CLASS) {
082                                        if (next.getParameterTypes().length == 0) {
083                                                retVal = (Constructor<T>) next;
084                                                break;
085                                        }
086                                } else if (next.getParameterTypes().length == 1) {
087                                        if (next.getParameterTypes()[0].isAssignableFrom(argumentType)) {
088                                                retVal = (Constructor<T>) next;
089                                                break;
090                                        }
091                                }
092                        }
093                        if (retVal == null) {
094                                throw new ConfigurationException("Class " + getImplementingClass() + " has no constructor with a single argument of type " + argumentType);
095                        }
096                        myConstructors.put(argumentType, retVal);
097                }
098                return retVal;
099        }
100
101        /**
102         * @return Returns null if none
103         */
104        public RuntimeChildDeclaredExtensionDefinition getDeclaredExtension(String theExtensionUrl, final String serverBaseUrl) {
105                validateSealed();
106                RuntimeChildDeclaredExtensionDefinition definition = myUrlToExtension.get(theExtensionUrl);
107                if (definition == null && StringUtils.isNotBlank(serverBaseUrl)) {
108                        for (final Map.Entry<String, RuntimeChildDeclaredExtensionDefinition> entry : myUrlToExtension.entrySet()) {
109                                final String key = (!UrlUtil.isValid(entry.getKey()) && StringUtils.isNotBlank(serverBaseUrl)) ? serverBaseUrl + entry.getKey() : entry.getKey();
110                                if (key.equals(theExtensionUrl)) {
111                                        definition = entry.getValue();
112                                        break;
113                                }
114                        }
115                }
116                return definition;
117        }
118
119        public List<RuntimeChildDeclaredExtensionDefinition> getExtensions() {
120                validateSealed();
121                return myExtensions;
122        }
123
124        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsModifier() {
125                validateSealed();
126                return myExtensionsModifier;
127        }
128
129        public List<RuntimeChildDeclaredExtensionDefinition> getExtensionsNonModifier() {
130                validateSealed();
131                return myExtensionsNonModifier;
132        }
133
134        public Class<? extends T> getImplementingClass() {
135                return myImplementingClass;
136        }
137
138        /**
139         * @return Returns the runtime name for this resource (i.e. the name that
140         *         will be used in encoded messages)
141         */
142        public String getName() {
143                return myName;
144        }
145
146        public boolean hasExtensions() {
147                validateSealed();
148                return myExtensions.size() > 0;
149        }
150
151        public boolean isStandardType() {
152                return myStandardType;
153        }
154
155        public T newInstance() {
156                return newInstance(null);
157        }
158
159        public T newInstance(Object theArgument) {
160                try {
161                        if (theArgument == null) {
162                                return getConstructor(null).newInstance();
163                        }
164                        return getConstructor(theArgument).newInstance(theArgument);
165
166                } catch (Exception e) {
167                        throw new ConfigurationException("Failed to instantiate type:" + getImplementingClass().getName(), e);
168                }
169        }
170
171        /**
172         * Invoked prior to use to perform any initialization and make object
173         * mutable.
174         * @param theContext TODO
175         */
176        void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
177                for (BaseRuntimeChildDefinition next : myExtensions) {
178                        next.sealAndInitialize(theContext, theClassToElementDefinitions);
179                }
180
181                for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) {
182                        String extUrl = next.getExtensionUrl();
183                        if (myUrlToExtension.containsKey(extUrl)) {
184                                throw new ConfigurationException("Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]");
185                        }
186                        myUrlToExtension.put(extUrl, next);
187                        if (next.isModifier()) {
188                                myExtensionsModifier.add(next);
189                        } else {
190                                myExtensionsNonModifier.add(next);
191                        }
192
193                }
194
195                myExtensions = Collections.unmodifiableList(myExtensions);
196        }
197
198        @Override
199        public String toString() {
200                return getClass().getSimpleName()+"[" + getName() + ", " + getImplementingClass().getSimpleName() + "]";
201        }
202
203        protected void validateSealed() {
204                /* 
205                 * this does nothing, but BaseRuntimeElementCompositeDefinition
206                 * overrides this method to provide functionality because that class
207                 * defers the dealing process
208                 */
209                
210        }
211
212        public enum ChildTypeEnum {
213                COMPOSITE_DATATYPE, /**
214                 * HL7.org structure style.
215                 */
216                CONTAINED_RESOURCE_LIST, /**
217                 * HAPI structure style.
218                 */
219                CONTAINED_RESOURCES, EXTENSION_DECLARED, 
220                ID_DATATYPE, 
221                PRIMITIVE_DATATYPE, /**
222                 * HAPI style.
223                 */
224                PRIMITIVE_XHTML, 
225                /**
226                 * HL7.org style.
227                 */
228                PRIMITIVE_XHTML_HL7ORG, 
229                RESOURCE, 
230                RESOURCE_BLOCK, 
231                
232                UNDECL_EXT, 
233                
234        }
235
236}