View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 - 2018 University Health Network
8    * %%
9    * Licensed under the Apache License, Version 2.0 (the "License");
10   * you may not use this file except in compliance with the License.
11   * You may obtain a copy of the License at
12   * 
13   *      http://www.apache.org/licenses/LICENSE-2.0
14   * 
15   * Unless required by applicable law or agreed to in writing, software
16   * distributed under the License is distributed on an "AS IS" BASIS,
17   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18   * See the License for the specific language governing permissions and
19   * limitations under the License.
20   * #L%
21   */
22  
23  import java.lang.reflect.Constructor;
24  import java.util.*;
25  import ca.uhn.fhir.util.UrlUtil;
26  
27  import org.apache.commons.lang3.StringUtils;
28  import org.hl7.fhir.instance.model.api.IBase;
29  
30  public abstract class BaseRuntimeElementDefinition<T extends IBase> {
31  
32  	private static final Class<Void> VOID_CLASS = Void.class;
33  	
34  	private Map<Class<?>, Constructor<T>> myConstructors = Collections.synchronizedMap(new HashMap<Class<?>, Constructor<T>>());
35  	private List<RuntimeChildDeclaredExtensionDefinition> myExtensions = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
36  	private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
37  	private List<RuntimeChildDeclaredExtensionDefinition> myExtensionsNonModifier = new ArrayList<RuntimeChildDeclaredExtensionDefinition>();
38  	private final Class<? extends T> myImplementingClass;
39  	private final String myName;
40  	private final boolean myStandardType;
41  	private Map<String, RuntimeChildDeclaredExtensionDefinition> myUrlToExtension = new HashMap<String, RuntimeChildDeclaredExtensionDefinition>();
42  
43  	public BaseRuntimeElementDefinition(String theName, Class<? extends T> theImplementingClass, boolean theStandardType) {
44  		assert StringUtils.isNotBlank(theName);
45  		assert theImplementingClass != null;
46  
47  		String name = theName;
48  		// TODO: remove this and fix for the model
49  		if (name.endsWith("Dt")) {
50  			name = name.substring(0, name.length() - 2);
51  		}
52  		
53  		
54  		myName = name;
55  		myStandardType = theStandardType;
56  		myImplementingClass = theImplementingClass;
57  	}
58  
59  	public void addExtension(RuntimeChildDeclaredExtensionDefinition theExtension) {
60  		if (theExtension == null) {
61  			throw new NullPointerException();
62  		}
63  		myExtensions.add(theExtension);
64  	}
65  
66  	public abstract ChildTypeEnum getChildType();
67  
68  	@SuppressWarnings("unchecked")
69  	private Constructor<T> getConstructor(Object theArgument) {
70  		
71  		Class<? extends Object> argumentType;
72  		if (theArgument == null) {
73  			argumentType = VOID_CLASS;
74  		} else {
75  			argumentType = theArgument.getClass();
76  		}
77  		
78  		Constructor<T> retVal = myConstructors.get(argumentType);
79  		if (retVal == null) {
80  			for (Constructor<?> next : getImplementingClass().getConstructors()) {
81  				if (argumentType == VOID_CLASS) {
82  					if (next.getParameterTypes().length == 0) {
83  						retVal = (Constructor<T>) next;
84  						break;
85  					}
86  				} else if (next.getParameterTypes().length == 1) {
87  					if (next.getParameterTypes()[0].isAssignableFrom(argumentType)) {
88  						retVal = (Constructor<T>) next;
89  						break;
90  					}
91  				}
92  			}
93  			if (retVal == null) {
94  				throw new ConfigurationException("Class " + getImplementingClass() + " has no constructor with a single argument of type " + argumentType);
95  			}
96  			myConstructors.put(argumentType, retVal);
97  		}
98  		return retVal;
99  	}
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 }