001package ca.uhn.fhir.context; 002 003/* 004 * #%L 005 * HAPI FHIR - Core Library 006 * %% 007 * Copyright (C) 2014 - 2020 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 ca.uhn.fhir.util.UrlUtil; 024import org.apache.commons.lang3.StringUtils; 025import org.hl7.fhir.instance.model.api.IBase; 026 027import java.lang.reflect.Constructor; 028import java.util.*; 029 030public abstract class BaseRuntimeElementDefinition<T extends IBase> { 031 032 private static final Class<Void> VOID_CLASS = Void.class; 033 private final Class<? extends T> myImplementingClass; 034 private final String myName; 035 private final boolean myStandardType; 036 private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>()); 037 private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>(); 038 private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>(); 039 private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>(); 040 private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>(); 041 private BaseRuntimeElementDefinition<?> myRootParentDefinition; 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 public BaseRuntimeElementDefinition<?> getRootParentDefinition() { 172 return myRootParentDefinition; 173 } 174 175 /** 176 * Invoked prior to use to perform any initialization and make object 177 * mutable. 178 * 179 * @param theContext TODO 180 */ 181 void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) { 182 for (BaseRuntimeChildDefinition next : myExtensions) { 183 next.sealAndInitialize(theContext, theClassToElementDefinitions); 184 } 185 186 for (RuntimeChildDeclaredExtensionDefinition next : myExtensions) { 187 String extUrl = next.getExtensionUrl(); 188 if (myUrlToExtension.containsKey(extUrl)) { 189 throw new ConfigurationException("Duplicate extension URL[" + extUrl + "] in Element[" + getName() + "]"); 190 } 191 myUrlToExtension.put(extUrl, next); 192 if (next.isModifier()) { 193 myExtensionsModifier.add(next); 194 } else { 195 myExtensionsNonModifier.add(next); 196 } 197 198 } 199 200 myExtensions = Collections.unmodifiableList(myExtensions); 201 202 Class parent = myImplementingClass; 203 do { 204 BaseRuntimeElementDefinition<?> parentDefinition = theClassToElementDefinitions.get(parent); 205 if (parentDefinition != null) { 206 myRootParentDefinition = parentDefinition; 207 } 208 parent = parent.getSuperclass(); 209 } while (!parent.equals(Object.class)); 210 211 } 212 213 @Override 214 public String toString() { 215 return getClass().getSimpleName() + "[" + getName() + ", " + getImplementingClass().getSimpleName() + "]"; 216 } 217 218 protected void validateSealed() { 219 /* 220 * this does nothing, but BaseRuntimeElementCompositeDefinition 221 * overrides this method to provide functionality because that class 222 * defers the dealing process 223 */ 224 225 } 226 227 public enum ChildTypeEnum { 228 COMPOSITE_DATATYPE, 229 /** 230 * HL7.org structure style. 231 */ 232 CONTAINED_RESOURCE_LIST, 233 /** 234 * HAPI structure style. 235 */ 236 CONTAINED_RESOURCES, EXTENSION_DECLARED, 237 ID_DATATYPE, 238 PRIMITIVE_DATATYPE, 239 /** 240 * HAPI style. 241 */ 242 PRIMITIVE_XHTML, 243 /** 244 * HL7.org style. 245 */ 246 PRIMITIVE_XHTML_HL7ORG, 247 RESOURCE, 248 RESOURCE_BLOCK, 249 250 UNDECL_EXT, 251 252 } 253 254}