View Javadoc
1   package ca.uhn.fhir.context;
2   
3   /*
4    * #%L
5    * HAPI FHIR - Core Library
6    * %%
7    * Copyright (C) 2014 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.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.lang3.Validate;
31  import org.apache.commons.lang3.text.WordUtils;
32  
33  import ca.uhn.fhir.i18n.HapiLocalizer;
34  import ca.uhn.fhir.model.api.IElement;
35  import ca.uhn.fhir.model.api.IResource;
36  import ca.uhn.fhir.model.view.ViewGenerator;
37  import ca.uhn.fhir.narrative.INarrativeGenerator;
38  import ca.uhn.fhir.parser.DataFormatException;
39  import ca.uhn.fhir.parser.IParser;
40  import ca.uhn.fhir.parser.JsonParser;
41  import ca.uhn.fhir.parser.XmlParser;
42  import ca.uhn.fhir.rest.client.IGenericClient;
43  import ca.uhn.fhir.rest.client.IRestfulClientFactory;
44  import ca.uhn.fhir.rest.client.RestfulClientFactory;
45  import ca.uhn.fhir.rest.client.api.IBasicClient;
46  import ca.uhn.fhir.rest.client.api.IRestfulClient;
47  import ca.uhn.fhir.util.FhirTerser;
48  import ca.uhn.fhir.validation.FhirValidator;
49  
50  /**
51   * The FHIR context is the central starting point for the use of the HAPI FHIR API. It should be created once, and then used as a factory for various other types of objects (parsers, clients, etc.).
52   * 
53   * <p>
54   * Important usage notes:
55   * <ul>
56   * <li>Thread safety: <b>This class is thread safe</b> and may be shared between multiple processing threads.</li>
57   * <li>
58   * Performance: <b>This class is expensive</b> to create, as it scans every resource class it needs to parse or encode to build up an internal model of those classes. For that reason, you should try
59   * to create one FhirContext instance which remains for the life of your application and reuse that instance. Note that it will not cause problems to create multiple instances (ie. resources
60   * originating from one FhirContext may be passed to parsers originating from another) but you will incur a performance penalty if a new FhirContext is created for every message you parse/encode.</li>
61   * </ul>
62   * </p>
63   */
64  public class FhirContext {
65  
66  	private static final List<Class<? extends IResource>> EMPTY_LIST = Collections.emptyList();
67  	private volatile Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition = Collections.emptyMap();
68  	private volatile Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = Collections.emptyMap();
69  	private HapiLocalizer myLocalizer = new HapiLocalizer();
70  	private volatile Map<String, RuntimeResourceDefinition> myNameToElementDefinition = Collections.emptyMap();
71  	private Map<String, String> myNameToResourceType;
72  	private volatile INarrativeGenerator myNarrativeGenerator;
73  	private volatile IRestfulClientFactory myRestfulClientFactory;
74  	private volatile RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
75  
76  	/**
77  	 * Default constructor. In most cases this is the right constructor to use.
78  	 */
79  	public FhirContext() {
80  		this(EMPTY_LIST);
81  	}
82  
83  	public FhirContext(Class<? extends IResource> theResourceType) {
84  		this(toCollection(theResourceType));
85  	}
86  
87  	public FhirContext(Class<?>... theResourceTypes) {
88  		this(toCollection(theResourceTypes));
89  	}
90  
91  	public FhirContext(Collection<Class<? extends IResource>> theResourceTypes) {
92  		scanResourceTypes(theResourceTypes);
93  	}
94  
95  	/**
96  	 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
97  	 */
98  	public BaseRuntimeElementDefinition<?> getElementDefinition(Class<? extends IElement> theElementType) {
99  		return myClassToElementDefinition.get(theElementType);
100 	}
101 
102 	/** For unit tests only */
103 	int getElementDefinitionCount() {
104 		return myClassToElementDefinition.size();
105 	}
106 
107 	/**
108 	 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with caution
109 	 */
110 	public HapiLocalizer getLocalizer() {
111 		if (myLocalizer == null) {
112 			myLocalizer = new HapiLocalizer();
113 		}
114 		return myLocalizer;
115 	}
116 
117 	public INarrativeGenerator getNarrativeGenerator() {
118 		return myNarrativeGenerator;
119 	}
120 
121 	/**
122 	 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
123 	 */
124 	public RuntimeResourceDefinition getResourceDefinition(Class<? extends IResource> theResourceType) {
125 		RuntimeResourceDefinition retVal = (RuntimeResourceDefinition) myClassToElementDefinition.get(theResourceType);
126 		if (retVal == null) {
127 			retVal = scanResourceType(theResourceType);
128 		}
129 		return retVal;
130 	}
131 
132 	/**
133 	 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
134 	 */
135 	public RuntimeResourceDefinition getResourceDefinition(IResource theResource) {
136 		return getResourceDefinition(theResource.getClass());
137 	}
138 
139 	/**
140 	 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
141 	 */
142 	@SuppressWarnings("unchecked")
143 	public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
144 		String resourceName = theResourceName;
145 
146 		/*
147 		 * TODO: this is a bit of a hack, really we should have a translation table based on a property file or something so that we can detect names like diagnosticreport
148 		 */
149 		if (Character.isLowerCase(resourceName.charAt(0))) {
150 			resourceName = WordUtils.capitalize(resourceName);
151 		}
152 
153 		Validate.notBlank(resourceName, "Resource name must not be blank");
154 
155 		RuntimeResourceDefinition retVal = myNameToElementDefinition.get(resourceName);
156 
157 		if (retVal == null) {
158 			try {
159 				String className = myNameToResourceType.get(resourceName.toLowerCase());
160 				if (className == null) {
161 					throw new DataFormatException("Unknown resource name[" + resourceName + "]");
162 				}
163 				Class<?> clazz = Class.forName(className);
164 				if (IResource.class.isAssignableFrom(clazz)) {
165 					retVal = scanResourceType((Class<? extends IResource>) clazz);
166 				}
167 			} catch (ClassNotFoundException e) {
168 				throw new DataFormatException("Unknown resource name[" + resourceName + "]");
169 			}
170 		}
171 
172 		return retVal;
173 	}
174 
175 	/**
176 	 * Returns the scanned runtime model for the given type. This is an advanced feature which is generally only needed for extending the core library.
177 	 */
178 	public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
179 		return myIdToResourceDefinition.get(theId);
180 	}
181 
182 	/**
183 	 * Returns the scanned runtime models. This is an advanced feature which is generally only needed for extending the core library.
184 	 */
185 	public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
186 		return myIdToResourceDefinition.values();
187 	}
188 
189 	public IRestfulClientFactory getRestfulClientFactory() {
190 		if (myRestfulClientFactory == null) {
191 			myRestfulClientFactory = new RestfulClientFactory(this);
192 		}
193 		return myRestfulClientFactory;
194 	}
195 
196 	public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
197 		return myRuntimeChildUndeclaredExtensionDefinition;
198 	}
199 
200 	/**
201 	 * Create and return a new JSON parser.
202 	 * 
203 	 * <p>
204 	 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
205 	 * </p>
206 	 * <p>
207 	 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
208 	 * </p>
209 	 */
210 	public IParser newJsonParser() {
211 		return new JsonParser(this);
212 	}
213 
214 	/**
215 	 * Instantiates a new client instance. This method requires an interface which is defined specifically for your use cases to contain methods for each of the RESTful operations you wish to
216 	 * implement (e.g. "read ImagingStudy", "search Patient by identifier", etc.). This interface must extend {@link IRestfulClient} (or commonly its sub-interface {@link IBasicClient}). See the <a
217 	 * href="http://hl7api.sourceforge.net/hapi-fhir/doc_rest_client.html">RESTful Client</a> documentation for more information on how to define this interface.
218 	 * 
219 	 * <p>
220 	 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation without incurring any performance penalty
221 	 * </p>
222 	 * 
223 	 * @param theClientType
224 	 *            The client type, which is an interface type to be instantiated
225 	 * @param theServerBase
226 	 *            The URL of the base for the restful FHIR server to connect to
227 	 * @return A newly created client
228 	 * @throws ConfigurationException
229 	 *             If the interface type is not an interface
230 	 */
231 	public <T extends IRestfulClient> T newRestfulClient(Class<T> theClientType, String theServerBase) {
232 		return getRestfulClientFactory().newClient(theClientType, theServerBase);
233 	}
234 
235 	/**
236 	 * Instantiates a new generic client. A generic client is able to perform any of the FHIR RESTful operations against a compliant server, but does not have methods defining the specific
237 	 * functionality required (as is the case with {@link #newRestfulClient(Class, String) non-generic clients}).
238 	 * 
239 	 * <p>
240 	 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every operation invocation without incurring any performance penalty
241 	 * </p>
242 	 * 
243 	 * @param theServerBase
244 	 *            The URL of the base for the restful FHIR server to connect to
245 	 * @return
246 	 */
247 	public IGenericClient newRestfulGenericClient(String theServerBase) {
248 		return getRestfulClientFactory().newGenericClient(theServerBase);
249 	}
250 
251 	public FhirTerser newTerser() {
252 		return new FhirTerser(this);
253 	}
254 
255 	public FhirValidator newValidator() {
256 		return new FhirValidator(this);
257 	}
258 
259 	public ViewGenerator newViewGenerator() {
260 		return new ViewGenerator(this);
261 	}
262 
263 	/**
264 	 * Create and return a new XML parser.
265 	 * 
266 	 * <p>
267 	 * Thread safety: <b>Parsers are not guaranteed to be thread safe</b>. Create a new parser instance for every thread or every message being parsed/encoded.
268 	 * </p>
269 	 * <p>
270 	 * Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed without incurring any performance penalty
271 	 * </p>
272 	 */
273 	public IParser newXmlParser() {
274 		return new XmlParser(this);
275 	}
276 
277 	private RuntimeResourceDefinition scanResourceType(Class<? extends IResource> theResourceType) {
278 		ArrayList<Class<? extends IResource>> resourceTypes = new ArrayList<Class<? extends IResource>>();
279 		resourceTypes.add(theResourceType);
280 		Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> defs = scanResourceTypes(resourceTypes);
281 		return (RuntimeResourceDefinition) defs.get(theResourceType);
282 	}
283 
284 	private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> scanResourceTypes(Collection<Class<? extends IResource>> theResourceTypes) {
285 		ModelScanner scanner = new ModelScanner(myClassToElementDefinition, theResourceTypes);
286 		if (myRuntimeChildUndeclaredExtensionDefinition == null) {
287 			myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
288 		}
289 
290 		Map<String, RuntimeResourceDefinition> nameToElementDefinition = new HashMap<String, RuntimeResourceDefinition>();
291 		nameToElementDefinition.putAll(myNameToElementDefinition);
292 		nameToElementDefinition.putAll(scanner.getNameToResourceDefinitions());
293 
294 		Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> classToElementDefinition = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
295 		classToElementDefinition.putAll(myClassToElementDefinition);
296 		classToElementDefinition.putAll(scanner.getClassToElementDefinitions());
297 
298 		Map<String, RuntimeResourceDefinition> idToElementDefinition = new HashMap<String, RuntimeResourceDefinition>();
299 		idToElementDefinition.putAll(myIdToResourceDefinition);
300 		idToElementDefinition.putAll(scanner.getIdToResourceDefinition());
301 
302 		myNameToElementDefinition = nameToElementDefinition;
303 		myClassToElementDefinition = classToElementDefinition;
304 		myIdToResourceDefinition = idToElementDefinition;
305 
306 		myNameToResourceType = scanner.getNameToResourceType();
307 
308 		return classToElementDefinition;
309 	}
310 
311 	/**
312 	 * This feature is not yet in its final state and should be considered an internal part of HAPI for now - use with caution
313 	 */
314 	public void setLocalizer(HapiLocalizer theMessages) {
315 		myLocalizer = theMessages;
316 	}
317 
318 	public void setNarrativeGenerator(INarrativeGenerator theNarrativeGenerator) {
319 		myNarrativeGenerator = theNarrativeGenerator;
320 	}
321 
322 	private static Collection<Class<? extends IResource>> toCollection(Class<? extends IResource> theResourceType) {
323 		ArrayList<Class<? extends IResource>> retVal = new ArrayList<Class<? extends IResource>>(1);
324 		retVal.add(theResourceType);
325 		return retVal;
326 	}
327 
328 	@SuppressWarnings("unchecked")
329 	private static List<Class<? extends IResource>> toCollection(Class<?>[] theResourceTypes) {
330 		ArrayList<Class<? extends IResource>> retVal = new ArrayList<Class<? extends IResource>>(1);
331 		for (Class<?> clazz : theResourceTypes) {
332 			if (!IResource.class.isAssignableFrom(clazz)) {
333 				throw new IllegalArgumentException(clazz.getCanonicalName() + " is not an instance of " + IResource.class.getSimpleName());
334 			}
335 			retVal.add((Class<? extends IResource>) clazz);
336 		}
337 		return retVal;
338 	}
339 
340 }