Server logging interceptor enhancements
This commit is contained in:
parent
84c479927e
commit
f5823a8e2f
|
@ -36,4 +36,9 @@ class AddTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
|
|||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherOperationTypeEnum getOtherOperationType() {
|
||||
return OtherOperationTypeEnum.ADD_TAGS;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -93,4 +93,9 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherOperationTypeEnum getOtherOperationType() {
|
||||
return OtherOperationTypeEnum.METADATA;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -36,4 +36,9 @@ public class DeleteTagsMethodBinding extends BaseAddOrDeleteTagsMethodBinding {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherOperationTypeEnum getOtherOperationType() {
|
||||
return OtherOperationTypeEnum.DELETE_TAGS;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -207,4 +207,9 @@ public class GetTagsMethodBinding extends BaseMethodBinding<TagList> {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public OtherOperationTypeEnum getOtherOperationType() {
|
||||
return OtherOperationTypeEnum.GET_TAGS;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue