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