Server logging interceptor enhancements

This commit is contained in:
James Agnew 2014-08-26 16:10:27 -04:00
parent 84c479927e
commit f5823a8e2f
17 changed files with 821 additions and 455 deletions

View File

@ -36,4 +36,9 @@ class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
return false;
}
@Override
public OtherOperationTypeEnum getOtherOperationType() {
return OtherOperationTypeEnum.ADD_TAGS;
}
}

View File

@ -135,6 +135,10 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
public abstract RestfulOperationSystemEnum getSystemOperationType();
public OtherOperationTypeEnum getOtherOperationType() {
return null;
}
public abstract boolean incomingServerRequestMatchesMethod(Request theRequest);
public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;

View File

@ -93,4 +93,9 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
return null;
}
@Override
public OtherOperationTypeEnum getOtherOperationType() {
return OtherOperationTypeEnum.METADATA;
}
}

View File

@ -36,4 +36,9 @@ public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
return true;
}
@Override
public OtherOperationTypeEnum getOtherOperationType() {
return OtherOperationTypeEnum.DELETE_TAGS;
}
}

View File

@ -207,4 +207,9 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
return true;
}
@Override
public OtherOperationTypeEnum getOtherOperationType() {
return OtherOperationTypeEnum.GET_TAGS;
}
}

View File

@ -0,0 +1,23 @@
package ca.uhn.fhir.rest.method;
public enum OtherOperationTypeEnum {
METADATA("metadata"),
ADD_TAGS("add-tags"),
DELETE_TAGS("delete-tags"),
GET_TAGS("get-tags");
private String myCode;
OtherOperationTypeEnum(String theName) {
myCode=theName;
}
public String getCode() {
return myCode;
}
}

View File

@ -41,7 +41,7 @@ public class Request extends RequestDetails {
private String myFhirServerBase;
private String myOperation;
private RequestType myRequestType;
private String myResourceName;
private boolean myRespondGzip;
private String mySecondaryOperation;
private HttpServletRequest myServletRequest;
@ -64,9 +64,6 @@ public class Request extends RequestDetails {
return myRequestType;
}
public String getResourceName() {
return myResourceName;
}
public String getSecondaryOperation() {
return mySecondaryOperation;
@ -133,9 +130,6 @@ public class Request extends RequestDetails {
myRequestType = theRequestType;
}
public void setResourceName(String theResourceName) {
myResourceName = theResourceName;
}
public void setRespondGzip(boolean theRespondGzip) {
myRespondGzip = theRespondGzip;

View File

@ -9,7 +9,9 @@ import ca.uhn.fhir.model.primitive.IdDt;
public class RequestDetails {
private IdDt myId;
private OtherOperationTypeEnum myOtherOperationType;
private Map<String, String[]> myParameters;
private String myResourceName;
private RestfulOperationTypeEnum myResourceOperationType;
private RestfulOperationSystemEnum mySystemOperationType;
@ -17,10 +19,17 @@ public class RequestDetails {
return myId;
}
public OtherOperationTypeEnum getOtherOperationType() {
return myOtherOperationType;
}
public Map<String, String[]> getParameters() {
return myParameters;
}
public String getResourceName() {
return myResourceName;
}
public RestfulOperationTypeEnum getResourceOperationType() {
return myResourceOperationType;
@ -34,10 +43,17 @@ public class RequestDetails {
myId = theId;
}
public void setOtherOperationType(OtherOperationTypeEnum theOtherOperationType) {
myOtherOperationType = theOtherOperationType;
}
public void setParameters(Map<String, String[]> theParams) {
myParameters = theParams;
}
public void setResourceName(String theResourceName) {
myResourceName = theResourceName;
}
public void setResourceOperationType(RestfulOperationTypeEnum theResourceOperationType) {
myResourceOperationType = theResourceOperationType;

View File

@ -132,332 +132,36 @@ public class RestfulServer extends HttpServlet {
theHttpResponse.addHeader("X-Powered-By", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
}
/**
* Returns the setting for automatically adding profile tags
*
* @see #setAddProfileTag(AddProfileTagEnum)
*/
public AddProfileTagEnum getAddProfileTag() {
return myAddProfileTag;
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to creating their own.
*/
public FhirContext getFhirContext() {
return myFhirContext;
}
public String getImplementationDescription() {
return myImplementationDescription;
}
/**
* Returns a ist of all registered server interceptors
*/
public List<IServerInterceptor> getInterceptors() {
return Collections.unmodifiableList(myInterceptors);
}
public IPagingProvider getPagingProvider() {
return myPagingProvider;
}
/**
* Provides the non-resource specific providers which implement method calls on this server
*
* @see #getResourceProviders()
*/
public Collection<Object> getPlainProviders() {
return myPlainProviders;
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
/**
* Provides the resource providers for this server
*/
public Collection<IResourceProvider> getResourceProviders() {
return myResourceProviders;
}
/**
* Provides the security manager, or <code>null</code> if none
*/
public ISecurityManager getSecurityManager() {
return mySecurityManager;
}
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
*/
public Object getServerConformanceProvider() {
return myServerConformanceProvider;
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
public String getServerName() {
return myServerName;
}
public IResourceProvider getServerProfilesProvider() {
return new ServerProfileProvider(getFhirContext());
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
initialize();
try {
ourLog.info("Initializing HAPI FHIR restful server");
mySecurityManager = getSecurityManager();
if (null == mySecurityManager) {
ourLog.trace("No security manager has been provided");
}
Collection<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> resourceType = nextProvider.getResourceType();
if (resourceType == null) {
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
}
if (typeToProvider.containsKey(resourceType)) {
throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
}
typeToProvider.put(resourceType, nextProvider);
}
ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) {
assertProviderIsValid(provider);
findResourceMethods(provider);
}
}
Collection<Object> providers = getPlainProviders();
if (providers != null) {
for (Object next : providers) {
assertProviderIsValid(next);
findResourceMethods(next);
}
}
findResourceMethods(getServerProfilesProvider());
findSystemMethods(getServerConformanceProvider());
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
/**
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
*/
public void setAddProfileTag(AddProfileTagEnum theAddProfileTag) {
Validate.notNull(theAddProfileTag, "theAddProfileTag must not be null");
myAddProfileTag = theAddProfileTag;
}
public void setFhirContext(FhirContext theFhirContext) {
Validate.notNull(theFhirContext, "FhirContext must not be null");
myFhirContext = theFhirContext;
}
public void setImplementationDescription(String theImplementationDescription) {
myImplementationDescription = theImplementationDescription;
}
/**
* Sets (or clears) the list of interceptors
*
* @param theList
* The list of interceptors (may be null)
*/
public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear();
if (theList != null) {
myInterceptors.addAll(theList);
}
}
/**
* Sets the paging provider to use, or <code>null</code> to use no paging (which is the default)
*/
public void setPagingProvider(IPagingProvider thePagingProvider) {
myPagingProvider = thePagingProvider;
}
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Collection<Object> theProviders) {
myPlainProviders = theProviders;
}
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
}
/**
* Sets the non-resource specific providers which implement method calls on this server
*
* @see #setResourceProviders(Collection)
*/
public void setProviders(Object... theProviders) {
myPlainProviders = Arrays.asList(theProviders);
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
myResourceProviders = theResourceProviders;
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders = Arrays.asList(theResourceProviders);
}
/**
* Sets the security manager, or <code>null</code> if none
*/
public void setSecurityManager(ISecurityManager theSecurityManager) {
mySecurityManager = theSecurityManager;
}
/**
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
myServerAddressStrategy = theServerAddressStrategy;
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
throw new IllegalStateException("Server is already started");
}
myServerConformanceProvider = theServerConformanceProvider;
}
/**
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}
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");
}
}
// /**
// * Sets the {@link INarrativeGenerator Narrative Generator} to use when
// serializing responses from this server, or <code>null</code> (which is
// the default) to disable narrative generation.
// * Note that this method can only be called before the server is
// initialized.
// *
// * @throws IllegalStateException
// * Note that this method can only be called prior to {@link #init()
// initialization} and will throw an {@link IllegalStateException} if called
// after that.
// */
// public void setNarrativeGenerator(INarrativeGenerator
// theNarrativeGenerator) {
// myNarrativeGenerator = theNarrativeGenerator;
// }
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.GET, request, response);
}
@Override
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.POST, request, response);
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
}
/**
* Count length of URL string, but treating unescaped sequences (e.g. ' ') as their unescaped equivalent (%20)
@ -580,6 +284,107 @@ public class RestfulServer extends HttpServlet {
}
}
/**
* Returns the setting for automatically adding profile tags
*
* @see #setAddProfileTag(AddProfileTagEnum)
*/
public AddProfileTagEnum getAddProfileTag() {
return myAddProfileTag;
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* providers should generally use this context if one is needed, as opposed to creating their own.
*/
public FhirContext getFhirContext() {
return myFhirContext;
}
public String getImplementationDescription() {
return myImplementationDescription;
}
/**
* Returns a ist of all registered server interceptors
*/
public List<IServerInterceptor> getInterceptors() {
return Collections.unmodifiableList(myInterceptors);
}
public IPagingProvider getPagingProvider() {
return myPagingProvider;
}
/**
* Provides the non-resource specific providers which implement method calls on this server
*
* @see #getResourceProviders()
*/
public Collection<Object> getPlainProviders() {
return myPlainProviders;
}
public Collection<ResourceBinding> getResourceBindings() {
return myResourceNameToProvider.values();
}
/**
* Provides the resource providers for this server
*/
public Collection<IResourceProvider> getResourceProviders() {
return myResourceProviders;
}
/**
* Provides the security manager, or <code>null</code> if none
*/
public ISecurityManager getSecurityManager() {
return mySecurityManager;
}
/**
* Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
*/
public Object getServerConformanceProvider() {
return myServerConformanceProvider;
}
/**
* Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
public String getServerName() {
return myServerName;
}
public IResourceProvider getServerProfilesProvider() {
return new ServerProfileProvider(getFhirContext());
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
}
private void handlePagingRequest(Request theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException {
IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction);
if (resultList == null) {
@ -626,36 +431,6 @@ public class RestfulServer extends HttpServlet {
}
private boolean requestIsBrowser(HttpServletRequest theRequest) {
String userAgent = theRequest.getHeader("User-Agent");
return userAgent != null && userAgent.contains("Mozilla");
}
@Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.GET, request, response);
}
@Override
protected void doOptions(HttpServletRequest theReq, HttpServletResponse theResp) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.OPTIONS, theReq, theResp);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.POST, request, response);
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
}
protected void handleRequest(SearchMethodBinding.RequestType theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException {
for (IServerInterceptor next : myInterceptors) {
boolean continueProcessing = next.incomingRequest(theRequest, theResponse);
@ -830,6 +605,7 @@ public class RestfulServer extends HttpServlet {
RequestDetails requestDetails = r;
requestDetails.setResourceOperationType(resourceMethod.getResourceOperationType());
requestDetails.setSystemOperationType(resourceMethod.getSystemOperationType());
requestDetails.setOtherOperationType(resourceMethod.getOtherOperationType());
for (IServerInterceptor next : myInterceptors) {
boolean continueProcessing = next.incomingRequest(requestDetails, theRequest, theResponse);
@ -891,6 +667,62 @@ public class RestfulServer extends HttpServlet {
}
/**
* Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
initialize();
try {
ourLog.info("Initializing HAPI FHIR restful server");
mySecurityManager = getSecurityManager();
if (null == mySecurityManager) {
ourLog.trace("No security manager has been provided");
}
Collection<IResourceProvider> resourceProvider = getResourceProviders();
if (resourceProvider != null) {
Map<Class<? extends IResource>, IResourceProvider> typeToProvider = new HashMap<Class<? extends IResource>, IResourceProvider>();
for (IResourceProvider nextProvider : resourceProvider) {
Class<? extends IResource> resourceType = nextProvider.getResourceType();
if (resourceType == null) {
throw new NullPointerException("getResourceType() on class '" + nextProvider.getClass().getCanonicalName() + "' returned null");
}
if (typeToProvider.containsKey(resourceType)) {
throw new ServletException("Multiple providers for type: " + resourceType.getCanonicalName());
}
typeToProvider.put(resourceType, nextProvider);
}
ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) {
assertProviderIsValid(provider);
findResourceMethods(provider);
}
}
Collection<Object> providers = getPlainProviders();
if (providers != null) {
for (Object next : providers) {
assertProviderIsValid(next);
findResourceMethods(next);
}
}
findResourceMethods(getServerProfilesProvider());
findSystemMethods(getServerConformanceProvider());
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
}
/**
* This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
* server being used.
@ -899,6 +731,184 @@ public class RestfulServer extends HttpServlet {
// nothing by default
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}
public void registerInterceptor(IServerInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.add(theInterceptor);
}
private boolean requestIsBrowser(HttpServletRequest theRequest) {
String userAgent = theRequest.getHeader("User-Agent");
return userAgent != null && userAgent.contains("Mozilla");
}
/**
* Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
*
* @param theAddProfileTag
* The behaviour enum (must not be null)
*/
public void setAddProfileTag(AddProfileTagEnum theAddProfileTag) {
Validate.notNull(theAddProfileTag, "theAddProfileTag must not be null");
myAddProfileTag = theAddProfileTag;
}
public void setFhirContext(FhirContext theFhirContext) {
Validate.notNull(theFhirContext, "FhirContext must not be null");
myFhirContext = theFhirContext;
}
public void setImplementationDescription(String theImplementationDescription) {
myImplementationDescription = theImplementationDescription;
}
/**
* Sets (or clears) the list of interceptors
*
* @param theList
* The list of interceptors (may be null)
*/
public void setInterceptors(List<IServerInterceptor> theList) {
myInterceptors.clear();
if (theList != null) {
myInterceptors.addAll(theList);
}
}
/**
* Sets the paging provider to use, or <code>null</code> to use no paging (which is the default)
*/
public void setPagingProvider(IPagingProvider thePagingProvider) {
myPagingProvider = thePagingProvider;
}
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Collection<Object> theProviders) {
myPlainProviders = theProviders;
}
/**
* Sets the non-resource specific providers which implement method calls on this server.
*
* @see #setResourceProviders(Collection)
*/
public void setPlainProviders(Object... theProv) {
setPlainProviders(Arrays.asList(theProv));
}
/**
* Sets the non-resource specific providers which implement method calls on this server
*
* @see #setResourceProviders(Collection)
*/
public void setProviders(Object... theProviders) {
myPlainProviders = Arrays.asList(theProviders);
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(Collection<IResourceProvider> theResourceProviders) {
myResourceProviders = theResourceProviders;
}
/**
* Sets the resource providers for this server
*/
public void setResourceProviders(IResourceProvider... theResourceProviders) {
myResourceProviders = Arrays.asList(theResourceProviders);
}
/**
* Sets the security manager, or <code>null</code> if none
*/
public void setSecurityManager(ISecurityManager theSecurityManager) {
mySecurityManager = theSecurityManager;
}
/**
* Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
* server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
myServerAddressStrategy = theServerAddressStrategy;
}
/**
* Returns the server conformance provider, which is the provider that is used to generate the server's conformance
* (metadata) statement.
* <p>
* By default, the {@link ServerConformanceProvider} is used, but this can be changed, or set to <code>null</code>
* if you do not wish to export a conformance statement.
* </p>
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
* Note that this method can only be called prior to {@link #init() initialization} and will throw an
* {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
throw new IllegalStateException("Server is already started");
}
myServerConformanceProvider = theServerConformanceProvider;
}
/**
* Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
* but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
* Gets the server's version, as exported in conformance profiles exported by the server. This is informational
* only, but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
* If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
}
public void unregisterInterceptor(IServerInterceptor theInterceptor) {
Validate.notNull(theInterceptor, "Interceptor can not be null");
myInterceptors.remove(theInterceptor);
}
private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile();
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
public static Bundle createBundleFromBundleProvider(RestfulServer theServer, HttpServletResponse theHttpResponse, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
NarrativeModeEnum theNarrativeMode, int theOffset, Integer theLimit, String theSearchId) {
theHttpResponse.setStatus(200);
@ -1181,6 +1191,17 @@ public class RestfulServer extends HttpServlet {
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
}
private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
Writer writer;
if (theRespondGzip) {
theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8");
} else {
writer = theHttpResponse.getWriter();
}
return writer;
}
public static boolean prettyPrintResponse(Request theRequest) {
Map<String, String[]> requestParams = theRequest.getParameters();
String[] pretty = requestParams.remove(Constants.PARAM_PRETTY);
@ -1231,32 +1252,6 @@ public class RestfulServer extends HttpServlet {
streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
}
private static void addProfileToBundleEntry(FhirContext theContext, IResource theResource) {
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl);
}
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile();
if (isNotBlank(profile)) {
tl.add(new Tag(Tag.HL7_ORG_PROFILE_TAG, profile, null));
}
}
private static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException {
Writer writer;
if (theRespondGzip) {
theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8");
} else {
writer = theHttpResponse.getWriter();
}
return writer;
}
private static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode, int stausCode,
boolean theRespondGzip, String theServerBase) throws IOException {
theHttpResponse.setStatus(stausCode);

View File

@ -1,11 +1,18 @@
package ca.uhn.fhir.rest.server.interceptor;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.text.StrLookup;
import org.apache.commons.lang3.text.StrSubstitutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
@ -17,24 +24,41 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
* </p>
* <table>
* <tr>
* <td>${id}</td>
* <td>The resource ID associated with this request (or "" if none)</td>
* </tr>
* <tr>
* <td>${idOrResourceName}</td>
* <td>The resource ID associated with this request, or the resource name if the request applies to a type but not an instance, or "" otherwise</td>
* </tr>
* <tr>
* <td>${operationType}</td>
* <td>A code indicating the operation type for this request, e.g. "read", "history-instance", etc.)</td>
* </tr>
* <tr>
* <td>${id}</td>
* <td>The resource ID associated with this request (or "" if none)</td>
* <td>${remoteAddr}</td>
* <td>The originaring IP of the request</td>
* </tr>
* <tr>
* <td>${requestHeader.XXXX}</td>
* <td>The value of the HTTP request header named XXXX. For example, a substitution variable named
* "${requestHeader.x-forwarded-for} will yield the value of the first header named "x-forwarded-for", or "" if none.</td>
* </tr>
* <tr>
* <td>${requestParameters}</td>
* <td>The HTTP request parameters (or "")</td>
* </tr>
* </table>
*
*/
public class LoggingInterceptor extends InterceptorAdapter {
private String myMessageFormat = "${operationType} - ${id}";
private Logger myLogger = ourLog;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(LoggingInterceptor.class);
private Logger myLogger = ourLog;
private String myMessageFormat = "${operationType} - ${idOrResourceName}";
@Override
public boolean incomingRequest(final RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
public boolean incomingRequest(final RequestDetails theRequestDetails, final HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
StrLookup<?> lookup = new StrLookup<String>() {
@Override
public String lookup(String theKey) {
@ -45,6 +69,9 @@ public class LoggingInterceptor extends InterceptorAdapter {
if (theRequestDetails.getSystemOperationType() != null) {
return theRequestDetails.getSystemOperationType().getCode();
}
if (theRequestDetails.getOtherOperationType() != null) {
return theRequestDetails.getOtherOperationType().getCode();
}
return "";
}
if ("id".equals(theKey)) {
@ -53,6 +80,42 @@ public class LoggingInterceptor extends InterceptorAdapter {
}
return "";
}
if ("idOrResourceName".equals(theKey)) {
if (theRequestDetails.getId() != null) {
return theRequestDetails.getId().getValue();
}
if (theRequestDetails.getResourceName() != null) {
return theRequestDetails.getResourceName();
}
return "";
}
if (theKey.equals("requestParameters")) {
StringBuilder b = new StringBuilder();
for (Entry<String, String[]> next : theRequestDetails.getParameters().entrySet()) {
for (String nextValue : next.getValue()) {
if (b.length() == 0) {
b.append('?');
} else {
b.append('&');
}
try {
b.append(URLEncoder.encode(next.getKey(), "UTF-8"));
b.append('=');
b.append(URLEncoder.encode(nextValue, "UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new ca.uhn.fhir.context.ConfigurationException("UTF-8 not supported", e);
}
}
}
return b.toString();
}
if (theKey.startsWith("requestHeader.")) {
String val = theRequest.getHeader(theKey.substring("requestHeader.".length()));
return StringUtils.defaultString(val);
}
if (theKey.startsWith("remoteAddr")) {
return StringUtils.defaultString(theRequest.getRemoteAddr());
}
return "!VAL!";
}
};
@ -64,4 +127,23 @@ public class LoggingInterceptor extends InterceptorAdapter {
return true;
}
public void setLogger(Logger theLogger) {
Validate.notNull(theLogger, "Logger can not be null");
myLogger = theLogger;
}
public void setLoggerName(String theLoggerName) {
Validate.notBlank(theLoggerName, "Logger name can not be null/empty");
myLogger = LoggerFactory.getLogger(theLoggerName);
}
/**
* Sets the message format itself. See the {@link LoggingInterceptor class documentation} for information on the format
*/
public void setMessageFormat(String theMessageFormat) {
Validate.notBlank(theMessageFormat, "Message format can not be null/empty");
myMessageFormat = theMessageFormat;
}
}

View File

@ -70,17 +70,6 @@ public class InterceptorTest {
}
@Test
public void testLoggingInterceptor() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();

View File

@ -0,0 +1,238 @@
package ca.uhn.fhir.rest.server.interceptor;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hamcrest.core.StringContains;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.slf4j.Logger;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class LoggingInterceptorTest {
private static CloseableHttpClient ourClient;
private static int ourPort;
private static Server ourServer;
private static RestfulServer servlet;
private IServerInterceptor myInterceptor;
@Test
public void testRead() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertThat(captor.getValue(), StringContains.containsString("read - Patient/1"));
}
@Test
public void testSearch() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
interceptor.setMessageFormat( "${operationType} - ${idOrResourceName} - ${requestParameters}");
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=1");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertThat(captor.getValue(), StringContains.containsString("search-type - Patient - ?_id=1"));
}
@Test
public void testMetadata() throws Exception {
LoggingInterceptor interceptor = new LoggingInterceptor();
servlet.setInterceptors(Collections.singletonList((IServerInterceptor)interceptor));
Logger logger = mock(Logger.class);
interceptor.setLogger(logger);
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/metadata");
HttpResponse status = ourClient.execute(httpGet);
IOUtils.closeQuietly(status.getEntity().getContent());
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger, times(1)).info(captor.capture());
assertThat(captor.getValue(), StringContains.containsString("metadata - "));
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
}
@Before
public void before() {
myInterceptor = mock(IServerInterceptor.class);
servlet.setInterceptors(Collections.singletonList(myInterceptor));
}
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
servlet = new RestfulServer();
servlet.setResourceProviders(patientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyPatientResourceProvider implements IResourceProvider {
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
Patient patient = createPatient1();
idToPatient.put("1", patient);
}
{
Patient patient = new Patient();
patient.getIdentifier().add(new IdentifierDt());
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00002");
patient.getName().add(new HumanNameDt());
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientTwo");
patient.getGender().setText("F");
patient.getId().setValue("2");
idToPatient.put("2", patient);
}
return idToPatient;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Read()
public Patient getResourceById(@IdParam IdDt theId) {
String key = theId.getIdPart();
Patient retVal = getIdToPatient().get(key);
return retVal;
}
/**
* Retrieve the resource by its identifier
*
* @param theId
* The resource identity
* @return The resource
*/
@Search()
public List<Patient> getResourceById(@RequiredParam(name = "_id") String theId) {
Patient patient = getIdToPatient().get(theId);
if (patient != null) {
return Collections.singletonList(patient);
} else {
return Collections.emptyList();
}
}
@Override
public Class<Patient> getResourceType() {
return Patient.class;
}
private Patient createPatient1() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
patient.getId().setValue("1");
return patient;
}
}
}

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.LoggingInterceptor;
public class TestRestfulServer extends RestfulServer {
@ -77,6 +78,11 @@ public class TestRestfulServer extends RestfulServer {
setServerAddressStrategy(new HardcodedServerAddressStrategy(baseUrl));
setPagingProvider(new FifoMemoryPagingProvider(10));
LoggingInterceptor loggingInterceptor = new LoggingInterceptor();
loggingInterceptor.setLoggerName("fhirtest.access");
loggingInterceptor.setMessageFormat("Source[${requestHeader.x-forwarded-for}] Operation[${operationType} ${idOrResourceName}] UA[${requestHeader.user-agent}] Params[${requestParameters}]");
registerInterceptor(loggingInterceptor);
}
@Override

View File

@ -18,13 +18,31 @@
<fileNamePattern>${fhir.logdir}/fhirtest.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<!-- [%file:%line] -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] [%X{req.remoteAddr}] [%X{req.userAgent}] %-5level %logger{36} %msg%n</pattern>
</encoder>
</appender>
<appender name="ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>INFO</level>
</filter>
<file>${fhir.logdir}/access.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${fhir.logdir}/access.%d{yyyy-MM-dd}.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
</rollingPolicy>
<encoder>
<!-- [%file:%line] -->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %msg%n</pattern>
</encoder>
</appender>
<logger name="fhirtest.access" level="INFO" additivity="false">
<appender-ref ref="ACCESS" />
</logger>
<root level="DEBUG">
<appender-ref ref="FILE" />
</root>

View File

@ -1100,6 +1100,15 @@ public class Controller {
char nextChar = retVal.charAt(i);
int nextCharI = nextChar;
if (nextCharI == 65533) {
b.append(' ');
continue;
}
if (nextCharI == 160) {
b.append(' ');
continue;
}
if (nextCharI == 194) {
b.append(' ');
continue;
}
b.append(nextChar);

View File

@ -23,7 +23,6 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -65,11 +64,12 @@ public class PatientResourceProvider implements IResourceProvider {
}
/**
* Stores a new version of the patient in memory so that it
* can be retrieved later.
* Stores a new version of the patient in memory so that it can be retrieved later.
*
* @param thePatient The patient resource to store
* @param theId The ID of the patient to retrieve
* @param thePatient
* The patient resource to store
* @param theId
* The ID of the patient to retrieve
*/
private void addNewVersion(Patient thePatient, Long theId) {
InstantDt publishedDate;
@ -83,21 +83,19 @@ public class PatientResourceProvider implements IResourceProvider {
}
/*
* PUBLISHED time will always be set to the time that the first
* version was stored. UPDATED time is set to the time that the new
* version was stored.
* PUBLISHED time will always be set to the time that the first version was stored. UPDATED time is set to the time that the new version was stored.
*/
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, publishedDate);
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, InstantDt.withCurrentTime());
Deque<Patient> existingVersions = myIdToPatientVersions.get(theId);
/*
* We just use the current number of versions as the next version number
*/
IdDt version = new IdDt(existingVersions.size());
thePatient.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, version);
existingVersions.add(thePatient);
}
@ -153,7 +151,7 @@ public class PatientResourceProvider implements IResourceProvider {
return retVal;
}
/**
* The getResourceType method comes from IResourceProvider, and must be overridden to indicate what type of resource this provider supplies.
*/
@ -163,18 +161,16 @@ public class PatientResourceProvider implements IResourceProvider {
}
/**
* This is the "read" operation.
* The "@Read" annotation indicates that this method supports the read and/or vread operation.
* <p>
* Read operations take a single parameter annotated with the {@link IdParam} paramater, and
* should return a single resource instance.
* This is the "read" operation. The "@Read" annotation indicates that this method supports the read and/or vread operation.
* <p>
* Read operations take a single parameter annotated with the {@link IdParam} paramater, and should return a single resource instance.
* </p>
*
* @param theId
* The read operation takes one parameter, which must be of type IdDt and must be annotated with the "@Read.IdParam" annotation.
* @return Returns a resource matching this identifier, or null if none exists.
*/
@Read()
@Read(version = true)
public Patient readPatient(@IdParam IdDt theId) {
Deque<Patient> retVal;
try {
@ -186,7 +182,19 @@ public class PatientResourceProvider implements IResourceProvider {
throw new ResourceNotFoundException(theId);
}
return retVal.getLast();
if (theId.hasVersionIdPart() == false) {
return retVal.getLast();
} else {
for (Patient nextVersion : retVal) {
String nextVersionId = nextVersion.getId().getVersionIdPart();
if (theId.getVersionIdPart().equals(nextVersionId)) {
return nextVersion;
}
}
// No matching version
throw new ResourceNotFoundException("Unknown version: " + theId.getValue());
}
}
/**
@ -238,40 +246,4 @@ public class PatientResourceProvider implements IResourceProvider {
}
}
/**
* This is the "vread" operation.
* The "@Read" annotation indicates that this method supports the read and/or vread operation.
* <p>
* VRead operations take a parameter annotated with the {@link IdParam} paramater,
* and a paramater annotated with the {@link VersionIdParam} parmeter,
* and should return a single resource instance.
* </p>
*
* @param theId
* The read operation takes one parameter, which must be of type IdDt and must be annotated with the "@Read.IdParam" annotation.
* @return Returns a resource matching this identifier, or null if none exists.
*/
@Read()
public Patient vreadPatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId) {
Deque<Patient> versions;
try {
versions = myIdToPatientVersions.get(theId.getIdPartAsLong());
} catch (NumberFormatException e) {
/*
* If we can't parse the ID as a long, it's not valid so this is an unknown resource
*/
throw new ResourceNotFoundException(theId);
}
for (Patient nextVersion : versions) {
IdDt nextVersionId = (IdDt) nextVersion.getResourceMetadata().get(ResourceMetadataKeyEnum.VERSION_ID);
if (theVersionId.equals(nextVersionId)) {
return nextVersion;
}
}
throw new ResourceNotFoundException("Unknown version " + theVersionId);
}
}