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}