Providers can bedynamicaly registered at runtime

This commit is contained in:
bdenton 2015-08-03 00:38:18 -07:00 committed by jamesagnew
parent daedf63c46
commit 9de81a1bd8
2 changed files with 260 additions and 64 deletions

View File

@ -75,4 +75,26 @@ public class ProvidedResourceScanner {
}
}
}
/**
* Remove any metadata that was added by any {@code ProvidesResources} annotation
* present in {@code theProvider}. This method is callled from {@code RestfulService}
* when it is unregistering a Resource Provider.
*
* @param theProvider
* - Normally a {@link ca.uhn.fhir.rest.server.IResourceProvider} that might
* be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
*/
public void removeProvidedResources(Object theProvider) {
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
if (annotation == null)
return;
for (Class<?> clazz : annotation.resources()) {
if (IBaseResource.class.isAssignableFrom(clazz)) {
// TODO -- not currently used but should be finished for completeness
} else {
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
}
}
}
}

View File

@ -37,6 +37,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import javax.servlet.ServletException;
import javax.servlet.UnavailableException;
@ -91,9 +93,10 @@ public class RestfulServer extends HttpServlet {
private String myImplementationDescription;
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
private IPagingProvider myPagingProvider;
private Collection<Object> myPlainProviders;
private Collection<Object> myPlainProviders = new ArrayList<Object>();
private Map<String, ResourceBinding> myResourceNameToBinding = new HashMap<String, ResourceBinding>();
private Collection<IResourceProvider> myResourceProviders;
private Collection<IResourceProvider> myResourceProviders = new ArrayList<IResourceProvider>();
private Map<String,IResourceProvider> myTypeToProvider = new HashMap<String, IResourceProvider>();
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
private ResourceBinding myServerBinding = new ResourceBinding();
private BaseMethodBinding<?> myServerConformanceMethod;
@ -103,6 +106,7 @@ public class RestfulServer extends HttpServlet {
private String myServerVersion = VersionUtil.getVersion();
private boolean myStarted;
private boolean myUseBrowserFriendlyContentTypes;
private Lock myProviderRegistrationMutex = new ReentrantLock();
/**
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
@ -193,6 +197,46 @@ public class RestfulServer extends HttpServlet {
return theServletPath.length() + delta;
}
/*
* Remove registered RESTful methods for a Provider
* (and all superclasses) when it is being unregistered
*/
private void removeResourceMethods (Object theProvider) throws Exception {
ourLog.info("Removing RESTful methods for: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass();
Class<?> supertype = clazz.getSuperclass();
Collection<String> resourceNames = new ArrayList<String>();
while (!Object.class.equals(supertype)) {
removeResourceMethods(theProvider, supertype, resourceNames);
supertype = supertype.getSuperclass();
}
removeResourceMethods(theProvider, clazz, resourceNames);
for (String resourceName : resourceNames) {
myResourceNameToBinding.remove(resourceName);
}
}
/*
* Collect the set of RESTful methods for a single class
* when it is being unregistered
*/
private void removeResourceMethods(Object theProvider, Class<?> clazz, Collection<String> resourceNames) throws ConfigurationException {
for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
BaseMethodBinding<?> foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider);
if (foundMethodBinding == null) {
continue; // not a bound method
}
if (foundMethodBinding instanceof ConformanceMethodBinding) {
myServerConformanceMethod = null;
continue;
}
String resourceName = foundMethodBinding.getResourceName();
if (!resourceNames.contains(resourceName)) {
resourceNames.add(resourceName);
}
}
}
private void findResourceMethods(Object theProvider) throws Exception {
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
@ -756,78 +800,61 @@ public class RestfulServer extends HttpServlet {
*/
@Override
public final void init() throws ServletException {
initialize();
Object confProvider;
myProviderRegistrationMutex.lock();
try {
ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode");
initialize();
Object confProvider;
try {
ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode");
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
providedResourceScanner.scanForProvidedResources(this);
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
providedResourceScanner.scanForProvidedResources(this);
Collection<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<String, IResourceProvider> typeToProvider = new HashMap<String, IResourceProvider>();
for (IResourceProvider nextProvider : resourceProvider) {
Collection<IResourceProvider> resourceProvider = getResourceProviders();
// 'true' tells registerProviders() that
// this call is part of initialization
registerProviders(resourceProvider, true);
Class<? extends IBaseResource> resourceType = nextProvider.getResourceType();
if (resourceType == null) {
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
}
Collection<Object> providers = getPlainProviders();
// 'true' tells registerProviders() that
// this call is part of initialization
registerProviders(providers, true);
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (typeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
}
typeToProvider.put(resourceName, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider);
findResourceMethods(getServerProfilesProvider());
confProvider = getServerConformanceProvider();
if (confProvider == null) {
confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
}
ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) {
assertProviderIsValid(provider);
findResourceMethods(provider);
// findSystemMethods(confProvider);
findResourceMethods(confProvider);
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
ourLog.trace("Invoking provider initialize methods");
if (getResourceProviders() != null) {
for (IResourceProvider iResourceProvider : getResourceProviders()) {
invokeInitialize(iResourceProvider);
}
}
Collection<Object> providers = getPlainProviders();
if (providers != null) {
for (Object next : providers) {
assertProviderIsValid(next);
findResourceMethods(next);
if (confProvider != null) {
invokeInitialize(confProvider);
}
if (getPlainProviders() != null) {
for (Object next : getPlainProviders()) {
invokeInitialize(next);
}
}
findResourceMethods(getServerProfilesProvider());
confProvider = getServerConformanceProvider();
if (confProvider == null) {
confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
}
// findSystemMethods(confProvider);
findResourceMethods(confProvider);
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
} finally {
myProviderRegistrationMutex.unlock();
}
ourLog.trace("Invoking provider initialize methods");
if (getResourceProviders() != null) {
for (IResourceProvider iResourceProvider : getResourceProviders()) {
invokeInitialize(iResourceProvider);
}
}
if (confProvider != null) {
invokeInitialize(confProvider);
}
if (getPlainProviders() != null) {
for (Object next : getPlainProviders()) {
invokeInitialize(next);
}
}
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
}
/**
@ -844,6 +871,153 @@ public class RestfulServer extends HttpServlet {
// nothing by default
}
/**
* Register a single provider. This could be a Resource Provider
* or a "plain" provider not associated with any resource.
*
* @param provider
* @throws Exception
*/
public void registerProvider (Object provider) throws Exception {
if (provider != null) {
Collection<Object> providerList = new ArrayList<Object>(1);
providerList.add(provider);
registerProviders(providerList);
}
}
/**
* Register a group of providers. These could be Resource Providers,
* "plain" providers or a mixture of the two.
*
* @param providers a {@code Collection} of providers. The parameter
* could be null or an empty {@code Collection}
* @throws Exception
*/
public void registerProviders (Collection<? extends Object> providers) throws Exception {
myProviderRegistrationMutex.lock();
try {
if (!myStarted) {
for (Object provider : providers) {
ourLog.info("Registration of provider ["+provider.getClass().getName()+"] will be delayed until FHIR server startup");
if (provider instanceof IResourceProvider) {
myResourceProviders.add((IResourceProvider)provider);
} else {
myPlainProviders.add(provider);
}
}
return;
}
} finally {
myProviderRegistrationMutex.unlock();
}
registerProviders(providers, false);
}
/*
* Inner method to actually register providers
*/
protected void registerProviders (Collection<? extends Object> providers, boolean inInit) throws Exception {
List<IResourceProvider> newResourceProviders = new ArrayList<IResourceProvider>();
List<Object> newPlainProviders = new ArrayList<Object>();
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) {
for (Object provider : providers) {
if (provider instanceof IResourceProvider) {
IResourceProvider rsrcProvider = (IResourceProvider)provider;
Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
if (resourceType == null) {
throw new NullPointerException("getResourceType() on class '" + rsrcProvider.getClass().getCanonicalName() + "' returned null");
}
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (myTypeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + myTypeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + rsrcProvider.getClass().getCanonicalName() + "]");
}
if (!inInit) {
myResourceProviders.add(rsrcProvider);
}
myTypeToProvider.put(resourceName, rsrcProvider);
providedResourceScanner.scanForProvidedResources(rsrcProvider);
newResourceProviders.add(rsrcProvider);
} else {
if (!inInit) {
myPlainProviders.add(provider);
}
newPlainProviders.add(provider);
}
}
if (!newResourceProviders.isEmpty()) {
ourLog.info("Added {} resource provider(s). Total {}", newResourceProviders.size(), myTypeToProvider.size());
for (IResourceProvider provider : newResourceProviders) {
assertProviderIsValid(provider);
findResourceMethods(provider);
}
}
if (!newPlainProviders.isEmpty()) {
ourLog.info("Added {} plain provider(s). Total {}", newPlainProviders.size());
for (Object provider : newPlainProviders) {
assertProviderIsValid(provider);
findResourceMethods(provider);
}
}
if (!inInit) {
ourLog.trace("Invoking provider initialize methods");
if (!newResourceProviders.isEmpty()) {
for (IResourceProvider provider : newResourceProviders) {
invokeInitialize(provider);
}
}
if (!newPlainProviders.isEmpty()) {
for (Object provider : newPlainProviders) {
invokeInitialize(provider);
}
}
}
}
}
/**
* Unregister one provider (either a Resource provider or a plain provider)
*
* @param provider
* @throws Exception
*/
public void unregisterProvider (Object provider) throws Exception {
if (provider != null) {
Collection<Object> providerList = new ArrayList<Object>(1);
providerList.add(provider);
unregisterProviders(providerList);
}
}
/**
* Unregister a {@code Collection} of providers
*
* @param providers
* @throws Exception
*/
public void unregisterProviders (Collection<? extends Object> providers) throws Exception {
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) {
for (Object provider : providers) {
removeResourceMethods(provider);
if (provider instanceof IResourceProvider) {
myResourceProviders.remove(provider);
IResourceProvider rsrcProvider = (IResourceProvider)provider;
Class<? extends IBaseResource> resourceType = rsrcProvider.getResourceType();
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
myTypeToProvider.remove(resourceName);
providedResourceScanner.removeProvidedResources(rsrcProvider);
} else {
myPlainProviders.remove(provider);
}
invokeDestroy(provider);
}
}
}
private void invokeDestroy(Object theProvider) {
invokeDestroy(theProvider, theProvider.getClass());
}
@ -1096,7 +1270,7 @@ public class RestfulServer extends HttpServlet {
if (setRestfulServer != null) {
setRestfulServer.invoke(theServerConformanceProvider, new Object[]{this});
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
} catch (Exception e) {
ourLog.warn("Error calling IServerConformanceProvider.setRestfulServer", e);
}
myServerConformanceProvider = theServerConformanceProvider;