More documentation and profile export working
This commit is contained in:
parent
53882b2c23
commit
fefd0e9a57
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ public @interface ResourceDef {
|
||||||
|
|
||||||
String name();
|
String name();
|
||||||
|
|
||||||
String id() default "";
|
String id();
|
||||||
|
|
||||||
String profile() default "";
|
String profile() default "";
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue