Merge pull request #48 from wdebeau1/master

ProvidedResources
This commit is contained in:
James Agnew 2014-11-03 13:41:29 -05:00
commit e9497c019a
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.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
@ -132,7 +133,7 @@ public class RestfulServer extends HttpServlet {
private void assertProviderIsValid(Object theNext) throws ConfigurationException { private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) { 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"); ourLog.trace("No security manager has been provided");
} }
ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
providedResourceScanner.scanForProvidedResources(this);
Collection<IResourceProvider> resourceProvider = getResourceProviders(); Collection<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) { if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>(); 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()); throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
} }
typeToProvider.put(resourceType, nextProvider); typeToProvider.put(resourceType, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider);
} }
ourLog.info("Got {} resource providers", typeToProvider.size()); ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) { 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. * Created by Bill de Beaubien on 10/31/2014.
*/ */
@ResourceDef(name="Observation", id="customobservation") @ResourceDef(name="Observation", id="customobservation")
public class CustomObservation extends Observation { class CustomObservation extends Observation {
@Child(name = "valueUnits", order = 3) @Child(name = "valueUnits", order = 3)
@Extension(url = "http://hapi.test.com/profile/customobservation#valueUnits", definedLocally = true, isModifier = false) @Extension(url = "http://hapi.test.com/profile/customobservation#valueUnits", definedLocally = true, isModifier = false)
@Description(shortDefinition = "Units on an observation whose type is of valueString") @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;
}
}
}