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}