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