Added the ProvidesResources annotation and supporting classes, which allows server authors to specify resources that should be added to the initial resource list (and thus show up in the list of profiles returned from a search)

This commit is contained in:
b.debeaubien 2014-11-03 09:04:08 -05:00
parent ece0e83609
commit 4a4dd90f6e
6 changed files with 250 additions and 2 deletions

View File

@ -0,0 +1,70 @@
package ca.uhn.fhir.context;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
/**
* Scans a class tagged with {@code ProvidesResources} and adds any resources listed to its FhirContext's resource
* definition list. This makes the profile generator find the classes.
*
* @see ca.uhn.fhir.model.api.annotation.ProvidesResources
*/
public class ProvidedResourceScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
private FhirContext myContext;
/**
* Constructor
* @param theContext - context whose resource definition list is to be updated by the scanner
*/
public ProvidedResourceScanner(FhirContext theContext) {
myContext = theContext;
}
/**
* If {@code theProvider} is tagged with the {@code ProvidesResources} annotation, this method will add every resource listed
* by the {@code resources} method.
* <p/>
* notes:
* <ul>
* <li>if {@code theProvider} isn't annotated with {@code resources} nothing is done; it's expected that most RestfulServers and
* ResourceProviders won't be annotated.</li>
* <li>any object listed in {@code resources} that doesn't implement {@code IResource} will generate a warning in the log.</li>
* </ul>
*
* @param theProvider - Normally, either a {@link ca.uhn.fhir.rest.server.RestfulServer} or a {@link ca.uhn.fhir.rest.server.IResourceProvider}
* that might be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
*/
public void scanForProvidedResources(Object theProvider) {
ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
if (annotation == null)
return;
for (Class clazz : annotation.resources()) {
if (IResource.class.isAssignableFrom(clazz)) {
myContext.getResourceDefinition(clazz);
} else {
ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
}
}
}
}

View File

@ -0,0 +1,44 @@
package ca.uhn.fhir.model.api.annotation;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* IResourceProvider and RestfulServer subclasses can use this annotation to designate which custom resources they can provide.
* These resources will automatically be added to the resource list used for profile generation.
* <pre>
* Examples:
* {@code
* @literal@ProvidesResources(resource=CustomObservation.class)
* class CustomObservationResourceProvider implements IResourceProvider{...}
*
* @literal@ProvidesResources(resource={CustomPatient.class,CustomObservation.class}){...}
* class FhirServer extends RestfulServer
* }
* </pre>
* Note that you needn't annotate both the IResourceProvider and the RestfulServer for a given resource; either one will suffice.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface ProvidesResources {
Class[] resources();
}

View File

@ -50,6 +50,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
@ -132,7 +133,7 @@ public class RestfulServer extends HttpServlet {
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class ust be public");
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Class must be public");
}
}
@ -695,6 +696,9 @@ public class RestfulServer extends HttpServlet {
ourLog.trace("No security manager has been provided");
}
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
providedResourceScanner.scanForProvidedResources(this);
Collection<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
@ -707,6 +711,7 @@ public class RestfulServer extends HttpServlet {
throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
}
typeToProvider.put(resourceType, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider);
}
ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) {

View File

@ -11,7 +11,7 @@ import ca.uhn.fhir.model.primitive.StringDt;
* Created by Bill de Beaubien on 10/31/2014.
*/
@ResourceDef(name="Observation", id="customobservation")
public class CustomObservation extends Observation {
class CustomObservation extends Observation {
@Child(name = "valueUnits", order = 3)
@Extension(url = "http://hapi.test.com/profile/customobservation#valueUnits", definedLocally = true, isModifier = false)
@Description(shortDefinition = "Units on an observation whose type is of valueString")

View File

@ -0,0 +1,35 @@
package ca.uhn.fhir.context;
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.resource.Patient;
import junit.framework.TestCase;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class ProvidedResourceScannerTest extends TestCase {
@Test
public void testScannerShouldAddProvidedResources() {
FhirContext ctx = new FhirContext();
assertNull(ctx.getElementDefinition(CustomPatient.class));
ProvidedResourceScanner scanner = new ProvidedResourceScanner(ctx);
scanner.scanForProvidedResources(new TestResourceProviderB());
assertNotNull(ctx.getElementDefinition(CustomPatient.class));
}
@ProvidesResources(resources=CustomObservation.class)
class TestResourceProviderA {
}
@ProvidesResources(resources={CustomPatient.class,ResourceWithExtensionsA.class})
class TestResourceProviderB {
}
@ResourceDef(name = "Patient", id="CustomPatient")
class CustomPatient extends Patient {
}
}

View File

@ -0,0 +1,94 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.ProvidesResources;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import junit.framework.TestCase;
import org.junit.Test;
import javax.servlet.ServletException;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Bill de Beaubien on 11/1/2014.
*/
public class ServerProvidedResourceScannerTest extends TestCase {
@Test
public void testWhenRestfulServerInitialized_annotatedResources_shouldBeAddedToContext() throws ServletException {
// Given
MyServer server = new MyServer();
// When
server.init();
// Then
assertNotNull(server.getFhirContext().getElementDefinition(CustomObservation.class));
assertNotNull(server.getFhirContext().getElementDefinition(CustomPatient.class));
}
@Test
public void testWhenUnannotatedServerInitialized_annotatedResources_shouldNotBeAddedToContext() throws ServletException {
// Given
RestfulServer server = new RestfulServer();
// When
server.init();
// Then
assertNull(server.getFhirContext().getElementDefinition(CustomObservation.class));
assertNull(server.getFhirContext().getElementDefinition(CustomPatient.class));
}
@Test
public void testWhenServletWithAnnotatedProviderInitialized_annotatedResource_shouldBeAddedToContext() throws ServletException {
// Given
MyServerWithProvider server = new MyServerWithProvider();
// When
server.init();
// Then
assertNotNull(server.getFhirContext().getElementDefinition(CustomObservation.class));
}
@ResourceDef(name = "Patient", id="CustomPatient")
class CustomPatient extends Patient {
}
@ResourceDef(name = "Observation", id="CustomObservation")
class CustomObservation extends Observation {
}
@ProvidesResources(resources={CustomPatient.class,CustomObservation.class})
class MyServer extends RestfulServer {
}
class MyServerWithProvider extends RestfulServer {
@Override
protected void initialize() throws ServletException {
List<IResourceProvider> providers = new ArrayList<IResourceProvider>();
providers.add(new MyObservationProvider());
setResourceProviders(providers);
}
}
@ProvidesResources(resources=CustomObservation.class)
public static class MyObservationProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return CustomObservation.class;
}
@Read(version = false)
public CustomObservation readObservation(@IdParam IdDt theId) {
return null;
}
}
}