More documentation and profile export working

This commit is contained in:
jamesagnew 2014-03-11 08:39:55 -04:00
parent 53882b2c23
commit fefd0e9a57
18 changed files with 260 additions and 125 deletions

View File

@ -18,6 +18,7 @@ public class FhirContext {
private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition; private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinition;
private final Map<String, RuntimeResourceDefinition> myNameToElementDefinition; private final Map<String, RuntimeResourceDefinition> myNameToElementDefinition;
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition;
public FhirContext(Class<?>... theResourceTypes) { public FhirContext(Class<?>... theResourceTypes) {
this(toCollection(theResourceTypes)); this(toCollection(theResourceTypes));
@ -32,6 +33,11 @@ public class FhirContext {
myNameToElementDefinition = Collections.unmodifiableMap(scanner.getNameToResourceDefinitions()); myNameToElementDefinition = Collections.unmodifiableMap(scanner.getNameToResourceDefinitions());
myClassToElementDefinition = Collections.unmodifiableMap(scanner.getClassToElementDefinitions()); myClassToElementDefinition = Collections.unmodifiableMap(scanner.getClassToElementDefinitions());
myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition(); myRuntimeChildUndeclaredExtensionDefinition = scanner.getRuntimeChildUndeclaredExtensionDefinition();
myIdToResourceDefinition = scanner.getIdToResourceDefinition();
}
public RuntimeResourceDefinition getResourceDefinitionById(String theId) {
return myIdToResourceDefinition.get(theId);
} }
public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinition() { public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinition() {
@ -82,4 +88,8 @@ public class FhirContext {
return retVal; return retVal;
} }
public Collection<RuntimeResourceDefinition> getResourceDefinitions() {
return myIdToResourceDefinition.values();
}
} }

View File

@ -47,17 +47,19 @@ class ModelScanner {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ModelScanner.class);
private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>(); private Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> myClassToElementDefinitions = new HashMap<Class<? extends IElement>, BaseRuntimeElementDefinition<?>>();
private Map<String, RuntimeResourceDefinition> myIdToResourceDefinition = new HashMap<String, RuntimeResourceDefinition>();
private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>(); private Map<String, RuntimeResourceDefinition> myNameToResourceDefinitions = new HashMap<String, RuntimeResourceDefinition>();
private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>();
// private Map<String, RuntimeResourceDefinition> // private Map<String, RuntimeResourceDefinition>
// myNameToDatatypeDefinitions = new HashMap<String, // myNameToDatatypeDefinitions = new HashMap<String,
// RuntimeDatatypeDefinition>(); // RuntimeDatatypeDefinition>();
private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>();
private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition; private RuntimeChildUndeclaredExtensionDefinition myRuntimeChildUndeclaredExtensionDefinition;
private Set<Class<? extends IElement>> myScanAlso = new HashSet<Class<? extends IElement>>();
private Set<Class<? extends ICodeEnum>> myScanAlsoCodeTable = new HashSet<Class<? extends ICodeEnum>>();
ModelScanner(Class<? extends IResource> theResourceTypes) throws ConfigurationException { ModelScanner(Class<? extends IResource> theResourceTypes) throws ConfigurationException {
Set<Class<? extends IElement>> singleton = new HashSet<Class<? extends IElement>>(); Set<Class<? extends IElement>> singleton = new HashSet<Class<? extends IElement>>();
singleton.add(theResourceTypes); singleton.add(theResourceTypes);
@ -68,6 +70,56 @@ class ModelScanner {
init(new HashSet<Class<? extends IElement>>(theResourceTypes)); init(new HashSet<Class<? extends IElement>>(theResourceTypes));
} }
public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
return myClassToElementDefinitions;
}
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
return (myNameToResourceDefinitions);
}
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
return myRuntimeChildUndeclaredExtensionDefinition;
}
private void addScanAlso(Class<? extends IElement> theType) {
if (theType.isInterface()) {
return;
}
myScanAlso.add(theType);
}
private Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = getGenericCollectionTypeOfField(next);
} else if (Collection.class.isAssignableFrom(nextElementType)) {
throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
}
return nextElementType;
}
@SuppressWarnings("unchecked")
private IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
}
try {
Field bindingField = bound.getField("VALUESET_BINDER");
return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (IllegalAccessException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (NoSuchFieldException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (SecurityException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
}
}
private void init(Set<Class<? extends IElement>> toScan) { private void init(Set<Class<? extends IElement>> toScan) {
toScan.add(DateDt.class); toScan.add(DateDt.class);
toScan.add(CodeDt.class); toScan.add(CodeDt.class);
@ -96,25 +148,6 @@ class ModelScanner {
ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size()); ourLog.info("Done scanning FHIR library, found {} model entries", myClassToElementDefinitions.size());
} }
public RuntimeChildUndeclaredExtensionDefinition getRuntimeChildUndeclaredExtensionDefinition() {
return myRuntimeChildUndeclaredExtensionDefinition;
}
public Map<Class<? extends IElement>, BaseRuntimeElementDefinition<?>> getClassToElementDefinitions() {
return myClassToElementDefinitions;
}
public Map<String, RuntimeResourceDefinition> getNameToResourceDefinitions() {
return (myNameToResourceDefinitions);
}
private void addScanAlso(Class<? extends IElement> theType) {
if (theType.isInterface()) {
return;
}
myScanAlso.add(theType);
}
private void scan(Class<? extends IElement> theClass) throws ConfigurationException { private void scan(Class<? extends IElement> theClass) throws ConfigurationException {
BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass); BaseRuntimeElementDefinition<?> existingDef = myClassToElementDefinitions.get(theClass);
if (existingDef != null) { if (existingDef != null) {
@ -393,37 +426,6 @@ class ModelScanner {
} }
} }
@SuppressWarnings("unchecked")
private IValueSetEnumBinder<Enum<?>> getBoundCodeBinder(Field theNext) {
Class<?> bound = getGenericCollectionTypeOfCodedField(theNext);
if (bound == null) {
throw new ConfigurationException("Field '" + theNext + "' has no parameter for " + BoundCodeDt.class.getSimpleName() + " to determine enum type");
}
try {
Field bindingField = bound.getField("VALUESET_BINDER");
return (IValueSetEnumBinder<Enum<?>>) bindingField.get(null);
} catch (IllegalArgumentException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch ( IllegalAccessException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (NoSuchFieldException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
} catch (SecurityException e) {
throw new ConfigurationException("Field '" + theNext + "' has type parameter " + bound.getCanonicalName() + " but this class has no valueset binding field", e);
}
}
private Class<?> determineElementType(Field next) {
Class<?> nextElementType = next.getType();
if (List.class.equals(nextElementType)) {
nextElementType = getGenericCollectionTypeOfField(next);
} else if (Collection.class.isAssignableFrom(nextElementType)) {
throw new ConfigurationException("Field '" + next.getName() + "' in type '" + next.getClass().getCanonicalName() + "' is a Collection - Only java.util.List curently supported");
}
return nextElementType;
}
private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype<?>> theClass, DatatypeDef theDatatypeDefinition) { private String scanPrimitiveDatatype(Class<? extends IPrimitiveDatatype<?>> theClass, DatatypeDef theDatatypeDefinition) {
ourLog.debug("Scanning resource class: {}", theClass.getName()); ourLog.debug("Scanning resource class: {}", theClass.getName());
@ -460,15 +462,42 @@ class ModelScanner {
return resourceName; return resourceName;
} }
String resourceId = resourceDefinition.id();
if (isBlank(resourceId)) {
throw new ConfigurationException("Resource type @" + ResourceDef.class.getSimpleName() + " annotation contains no resource ID: " + theClass.getCanonicalName());
}
if (myIdToResourceDefinition.containsKey(resourceId)) {
throw new ConfigurationException("The following resource types have the same ID of '" + resourceId + "' - " + theClass.getCanonicalName() + " and " + myIdToResourceDefinition.get(resourceId).getImplementingClass().getCanonicalName());
}
RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceDefinition); RuntimeResourceDefinition resourceDef = new RuntimeResourceDefinition(theClass, resourceDefinition);
myClassToElementDefinitions.put(theClass, resourceDef); myClassToElementDefinitions.put(theClass, resourceDef);
myNameToResourceDefinitions.put(resourceName, resourceDef); myNameToResourceDefinitions.put(resourceName, resourceDef);
scanCompositeElementForChildren(theClass, resourceDef, resourceDefinition.identifierOrder()); scanCompositeElementForChildren(theClass, resourceDef, resourceDefinition.identifierOrder());
myIdToResourceDefinition.put(resourceId, resourceDef);
return resourceName; return resourceName;
} }
public Map<String, RuntimeResourceDefinition> getIdToResourceDefinition() {
return myIdToResourceDefinition;
}
private static Class<?> getGenericCollectionTypeOfCodedField(Field next) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType) firstArg);
firstArg = pt.getActualTypeArguments()[0];
type = (Class<?>) firstArg;
} else {
type = (Class<?>) firstArg;
}
return type;
}
private static Class<?> getGenericCollectionTypeOfField(Field next) { private static Class<?> getGenericCollectionTypeOfField(Field next) {
// Type genericSuperclass = next.getType().getGenericSuperclass(); // Type genericSuperclass = next.getType().getGenericSuperclass();
Class<?> type; Class<?> type;
@ -491,18 +520,4 @@ class ModelScanner {
return type; return type;
} }
private static Class<?> getGenericCollectionTypeOfCodedField(Field next) {
Class<?> type;
ParameterizedType collectionType = (ParameterizedType) next.getGenericType();
Type firstArg = collectionType.getActualTypeArguments()[0];
if (ParameterizedType.class.isAssignableFrom(firstArg.getClass())) {
ParameterizedType pt = ((ParameterizedType)firstArg);
firstArg = pt.getActualTypeArguments()[0];
type = (Class<?>) firstArg;
}else {
type = (Class<?>) firstArg;
}
return type;
}
} }

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.model.dstu.valueset.SlicingRulesEnum;
public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IResource> { public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition<IResource> {
private String myResourceProfile; private String myResourceProfile;
private Profile myProfileDef;
public RuntimeResourceDefinition(Class<? extends IResource> theClass, ResourceDef theResourceAnnotation) { public RuntimeResourceDefinition(Class<? extends IResource> theClass, ResourceDef theResourceAnnotation) {
super(theResourceAnnotation.name(), theClass); super(theResourceAnnotation.name(), theClass);
@ -44,6 +45,10 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
} }
public synchronized Profile toProfile() { public synchronized Profile toProfile() {
if (myProfileDef != null) {
return myProfileDef;
}
Profile retVal = new Profile(); Profile retVal = new Profile();
RuntimeResourceDefinition def = this; RuntimeResourceDefinition def = this;
@ -69,6 +74,8 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
retVal.getStructure().get(0).getElement().get(0).getDefinition().addType().getCode().setValue("Resource"); retVal.getStructure().get(0).getElement().get(0).getDefinition().addType().getCode().setValue("Resource");
myProfileDef = retVal;
return retVal; return retVal;
} }

View File

@ -13,7 +13,7 @@ public @interface ResourceDef {
String name(); String name();
String id() default ""; String id();
String profile() default ""; String profile() default "";

View File

@ -12,10 +12,13 @@ import ca.uhn.fhir.model.api.IResource;
public @interface Read { public @interface Read {
/** /**
* Returns the resource type that is returned by the method annotated * The return type for this search method. This generally does not need
* with this annotation * to be populated for a server implementation, since servers will return
* only one resource per class, but generally does need to be populated
* for client implementations.
*/ */
Class<? extends IResource> value(); // NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
Class<? extends IResource> type() default IResource.class;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)

View File

@ -11,6 +11,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.common.BaseMethodBinding; import ca.uhn.fhir.rest.common.BaseMethodBinding;
@ -70,7 +71,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
ClientInvocationHandler theInvocationHandler = new ClientInvocationHandler(client, myContext, serverBase); ClientInvocationHandler theInvocationHandler = new ClientInvocationHandler(client, myContext, serverBase);
for (Method nextMethod : theClientType.getMethods()) { for (Method nextMethod : theClientType.getMethods()) {
BaseMethodBinding binding = BaseMethodBinding.bindMethod(nextMethod); BaseMethodBinding binding = BaseMethodBinding.bindMethod(null, nextMethod);
theInvocationHandler.addBinding(nextMethod, binding); theInvocationHandler.addBinding(nextMethod, binding);
} }

View File

@ -26,10 +26,10 @@ public abstract class BaseMethodBinding {
private String myResourceName; private String myResourceName;
private MethodReturnTypeEnum myMethodReturnType; private MethodReturnTypeEnum myMethodReturnType;
public BaseMethodBinding(MethodReturnTypeEnum theMethodReturnType, Class<? extends IResource> theAnnotatedResourceType) { public BaseMethodBinding(MethodReturnTypeEnum theMethodReturnType, Class<? extends IResource> theReturnResourceType) {
ResourceDef resourceDefAnnotation = theAnnotatedResourceType.getAnnotation(ResourceDef.class); ResourceDef resourceDefAnnotation = theReturnResourceType.getAnnotation(ResourceDef.class);
if (resourceDefAnnotation == null) { if (resourceDefAnnotation == null) {
throw new ConfigurationException(theAnnotatedResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation"); throw new ConfigurationException(theReturnResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName() + " annotation");
} }
myResourceName = resourceDefAnnotation.name(); myResourceName = resourceDefAnnotation.name();
myMethodReturnType = theMethodReturnType; myMethodReturnType = theMethodReturnType;
@ -39,8 +39,7 @@ public abstract class BaseMethodBinding {
public abstract GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException; public abstract GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, public abstract List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
InternalErrorException;
public abstract boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames); public abstract boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames);
@ -48,25 +47,18 @@ public abstract class BaseMethodBinding {
return myResourceName; return myResourceName;
} }
public static BaseMethodBinding bindMethod(Method theMethod) { public static BaseMethodBinding bindMethod(Class<? extends IResource> theReturnType, Method theMethod) {
Read read = theMethod.getAnnotation(Read.class); Read read = theMethod.getAnnotation(Read.class);
Search search = theMethod.getAnnotation(Search.class); Search search = theMethod.getAnnotation(Search.class);
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search)) { if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search)) {
return null; return null;
} }
Class<? extends IResource> annotatedResourceType;
if (read != null) {
annotatedResourceType = read.value();
} else {
annotatedResourceType = search.value();
}
Class<?> methodReturnType = theMethod.getReturnType(); Class<?> methodReturnType = theMethod.getReturnType();
MethodReturnTypeEnum methodReturnTypeEnum; MethodReturnTypeEnum methodReturnTypeEnum;
if (methodReturnType.equals(List.class)) { if (methodReturnType.equals(List.class)) {
methodReturnTypeEnum = MethodReturnTypeEnum.LIST_OF_RESOURCES; methodReturnTypeEnum = MethodReturnTypeEnum.LIST_OF_RESOURCES;
} else if (methodReturnType.isAssignableFrom(annotatedResourceType)) { } else if (IResource.class.isAssignableFrom(methodReturnType)) {
methodReturnTypeEnum = MethodReturnTypeEnum.RESOURCE; methodReturnTypeEnum = MethodReturnTypeEnum.RESOURCE;
} else if (Bundle.class.isAssignableFrom(methodReturnType)) { } else if (Bundle.class.isAssignableFrom(methodReturnType)) {
methodReturnTypeEnum = MethodReturnTypeEnum.LIST_OF_RESOURCES; methodReturnTypeEnum = MethodReturnTypeEnum.LIST_OF_RESOURCES;
@ -74,10 +66,23 @@ public abstract class BaseMethodBinding {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }
Class<? extends IResource> returnType = theReturnType;
if (returnType == null) {
if (read != null) { if (read != null) {
return new ReadMethodBinding(methodReturnTypeEnum, annotatedResourceType, theMethod); returnType = read.type();
} else if (search != null) { } else if (search != null) {
return new SearchMethodBinding(methodReturnTypeEnum, annotatedResourceType, theMethod); returnType = search.type();
}
if (returnType == null) {
throw new ConfigurationException("Could not determine return type for method '" + theMethod.getName() + "'. Try explicitly specifying one in the operation annotation.");
}
}
if (read != null) {
return new ReadMethodBinding(methodReturnTypeEnum, returnType, theMethod);
} else if (search != null) {
return new SearchMethodBinding(methodReturnTypeEnum, returnType, theMethod);
} else { } else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
} }
@ -111,15 +116,18 @@ public abstract class BaseMethodBinding {
if (obj1 == null) { if (obj1 == null) {
obj1 = object; obj1 = object;
} else { } else {
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName()
+ obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both."); + ". Can not have both.");
} }
} }
} }
if (obj1 == null) { if (obj1 == null) {
return false; return false;
// throw new ConfigurationException("Method '" + theNextMethod.getName() + "' on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has no FHIR method annotations."); // throw new ConfigurationException("Method '" +
// theNextMethod.getName() + "' on type '" +
// theNextMethod.getDeclaringClass().getSimpleName() +
// " has no FHIR method annotations.");
} }
return true; return true;
} }

View File

@ -31,8 +31,8 @@ public class SearchMethodBinding extends BaseMethodBinding {
private RequestType requestType; private RequestType requestType;
private Class<?> myDeclaredResourceType; private Class<?> myDeclaredResourceType;
public SearchMethodBinding(MethodReturnTypeEnum theMethodReturnTypeEnum, Class<? extends IResource> theAnnotatedResourceType, Method theMethod) { public SearchMethodBinding(MethodReturnTypeEnum theMethodReturnTypeEnum, Class<? extends IResource> theReturnResourceType, Method theMethod) {
super(theMethodReturnTypeEnum, theAnnotatedResourceType); super(theMethodReturnTypeEnum, theReturnResourceType);
this.method = theMethod; this.method = theMethod;
this.myParameters = Util.getResourceParameters(theMethod); this.myParameters = Util.getResourceParameters(theMethod);

View File

@ -88,7 +88,7 @@ public abstract class RestfulServer extends HttpServlet {
if (Modifier.isPublic(m.getModifiers())) { if (Modifier.isPublic(m.getModifiers())) {
ourLog.info("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); ourLog.info("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m); BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m);
if (foundMethodBinding != null) { if (foundMethodBinding != null) {
r.addMethod(foundMethodBinding); r.addMethod(foundMethodBinding);
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());

View File

@ -10,9 +10,12 @@ import ca.uhn.fhir.model.api.IResource;
public @interface Search { public @interface Search {
/** /**
* Returns the resource type that is returned by the method annotated * The return type for this search method. This generally does not need
* with this annotation * to be populated for a server implementation, since servers will return
* only one resource per class, but generally does need to be populated
* for client implementations.
*/ */
Class<? extends IResource> value(); // NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
Class<? extends IResource> type() default IResource.class;
} }

View File

@ -1,20 +1,58 @@
package ca.uhn.fhir.rest.server.provider; package ca.uhn.fhir.rest.server.provider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Profile; import ca.uhn.fhir.model.dstu.resource.Profile;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.operations.Search;
public class ProfileProvider implements IResourceProvider { public class ProfileProvider implements IResourceProvider {
private FhirContext myContext;
public ProfileProvider(FhirContext theContext) {
myContext = theContext;
}
@Override @Override
public Class<? extends IResource> getResourceType() { public Class<? extends IResource> getResourceType() {
return Profile.class; return Profile.class;
} }
public ProfileProvider(FhirContext theCtx) { @Read()
Profile p = new Profile(); public Profile getProfileById(@Read.IdParam IdDt theId) {
// p. RuntimeResourceDefinition retVal = myContext.getResourceDefinitionById(theId.getValue());
if (retVal==null) {
return null;
}
return retVal.toProfile();
}
@Search()
public List<Profile> getAllProfiles() {
List<RuntimeResourceDefinition> defs = new ArrayList<RuntimeResourceDefinition>(myContext.getResourceDefinitions());
Collections.sort(defs, new Comparator<RuntimeResourceDefinition>() {
@Override
public int compare(RuntimeResourceDefinition theO1, RuntimeResourceDefinition theO2) {
int cmp = theO1.getName().compareTo(theO2.getName());
if (cmp==0) {
cmp=theO1.getResourceProfile().compareTo(theO2.getResourceProfile());
}
return cmp;
}});
ArrayList<Profile> retVal = new ArrayList<Profile>();
for (RuntimeResourceDefinition next : defs) {
retVal.add(next.toProfile());
}
return retVal;
} }
} }

View File

@ -40,7 +40,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider {
* @return * @return
* Returns a resource matching this identifier, or null if none exists. * Returns a resource matching this identifier, or null if none exists.
*/ */
@Read(Patient.class) @Read()
public Patient getResourceById(@Read.IdParam IdDt theId) { public Patient getResourceById(@Read.IdParam IdDt theId) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier(); patient.addIdentifier();
@ -68,7 +68,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider {
* This method returns a list of Patients. This list may contain multiple * This method returns a list of Patients. This list may contain multiple
* matching resources, or it may also be empty. * matching resources, or it may also be empty.
*/ */
@Search(Patient.class) @Search()
public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) { public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier(); patient.addIdentifier();

View File

@ -32,7 +32,8 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
/** /**
* The "@Read" annotation indicates that this method supports the * The "@Read" annotation indicates that this method supports the
* read operation. It takes one argument, the Resource type being returned. * read operation. Read operations should return a single resource
* instance.
* *
* @param theId * @param theId
* The read operation takes one parameter, which must be of type * The read operation takes one parameter, which must be of type
@ -40,7 +41,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
* @return * @return
* Returns a resource matching this identifier, or null if none exists. * Returns a resource matching this identifier, or null if none exists.
*/ */
@Read(Patient.class) @Read()
public Patient getResourceById(@Read.IdParam IdDt theId) { public Patient getResourceById(@Read.IdParam IdDt theId) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier(); patient.addIdentifier();
@ -68,7 +69,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
* This method returns a list of Patients. This list may contain multiple * This method returns a list of Patients. This list may contain multiple
* matching resources, or it may also be empty. * matching resources, or it may also be empty.
*/ */
@Search(Patient.class) @Search()
public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) { public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) {
Patient patient = new Patient(); Patient patient = new Patient();
patient.addIdentifier(); patient.addIdentifier();
@ -76,7 +77,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns")); patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001"); patient.getIdentifier().get(0).setValue("00001");
patient.addName(); patient.addName();
patient.getName().get(0).addFamily("Test"); patient.getName().get(0).addFamily(theFamilyName.getValue());
patient.getName().get(0).addGiven("PatientOne"); patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M"); patient.getGender().setText("M");
return Collections.singletonList(patient); return Collections.singletonList(patient);
@ -84,3 +85,5 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
} }
//END SNIPPET: provider //END SNIPPET: provider

View File

@ -0,0 +1,28 @@
package example;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.operations.Search;
public class RestfulPatientResourceProviderMore implements IResourceProvider {
//START SNIPPET: searchAll
@Search
public List<Organization> getAllOrganizations() {
List<Organization> retVal=new ArrayList<Organization>(); // populate this
return retVal;
}
//END SNIPPET: searchAll
@Override
public Class<? extends IResource> getResourceType() {
return null;
}
}

View File

@ -94,6 +94,25 @@
</section> </section>
<section name="Resource Providers">
<p>
Resource providers can support a wide variety of operations.
</p>
<p>
The following example shows a search with no parameters. This operation
should return all resources of a given type (this obviously doesn't make
sense in all contexts, but does for some resource types).
</p>
<macro name="snippet">
<param name="id" value="searchAll" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
</body> </body>
</document> </document>

View File

@ -12,7 +12,7 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.primitive.DateDt; import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.StringDt;
@ResourceDef(name = "ResourceWithExtensionsA") @ResourceDef(name = "ResourceWithExtensionsA", id="0001")
public class ResourceWithExtensionsA implements IResource { public class ResourceWithExtensionsA implements IResource {
/* /*

View File

@ -11,13 +11,13 @@ import ca.uhn.fhir.rest.server.parameters.Required;
public interface ITestClient extends IRestfulClient { public interface ITestClient extends IRestfulClient {
@Read(value=Patient.class) @Read(type=Patient.class)
Patient getPatientById(@Read.IdParam IdDt theId); Patient getPatientById(@Read.IdParam IdDt theId);
@Search(value=Patient.class) @Search(type=Patient.class)
Patient findPatientByMrn(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theId); Patient findPatientByMrn(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theId);
@Search(value=Patient.class) @Search(type=Patient.class)
Bundle findPatientByLastName(@Required(name = Patient.SP_FAMILY) IdentifierDt theId); Bundle findPatientByLastName(@Required(name = Patient.SP_FAMILY) IdentifierDt theId);

View File

@ -49,7 +49,7 @@ public class DummyPatientResourceProvider implements IResourceProvider {
} }
} }
@Search(Patient.class) @Search()
public Patient getPatient(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { public Patient getPatient(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : myIdToPatient.values()) { for (Patient next : myIdToPatient.values()) {
for (IdentifierDt nextId : next.getIdentifier()) { for (IdentifierDt nextId : next.getIdentifier()) {
@ -73,7 +73,7 @@ public class DummyPatientResourceProvider implements IResourceProvider {
* The resource identity * The resource identity
* @return The resource * @return The resource
*/ */
@Read(Patient.class) @Read()
public Patient getResourceById(@Read.IdParam IdDt theId) { public Patient getResourceById(@Read.IdParam IdDt theId) {
return myIdToPatient.get(theId.getValue()); return myIdToPatient.get(theId.getValue());
} }