Added @Destroy annotation, which allows ResourceProviders to do cleanup when the server shuts down; renamed "ResfulServer" tests to "RestfulServer"

This commit is contained in:
b.debeaubien 2014-11-10 17:50:49 -05:00
parent 030ad3934c
commit 21d058f86b
7 changed files with 211 additions and 75 deletions

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
/**
* ResourceProvider methods tagged with @Destroy will be invoked when the RestfulServer is shut down.
* This is your chance to do any cleanup.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Destroy {
}

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import ca.uhn.fhir.rest.annotation.*;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
@ -44,18 +45,6 @@ import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.AddTags;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.DeleteTags;
import ca.uhn.fhir.rest.annotation.GetTags;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Transaction;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
@ -257,6 +246,7 @@ public abstract class BaseMethodBinding<T> implements IClientResponseHandler<T>
AddTags addTags = theMethod.getAnnotation(AddTags.class); AddTags addTags = theMethod.getAnnotation(AddTags.class);
DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class); DeleteTags deleteTags = theMethod.getAnnotation(DeleteTags.class);
Transaction transaction = theMethod.getAnnotation(Transaction.class); Transaction transaction = theMethod.getAnnotation(Transaction.class);
// ** if you add another annotation above, also add it to the next line: // ** if you add another annotation above, also add it to the next line:
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags, transaction)) { if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance, create, update, delete, history, validate, getTags, addTags, deleteTags, transaction)) {
return null; return null;

View File

@ -20,48 +20,10 @@ package ca.uhn.fhir.rest.server;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue; import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
@ -69,13 +31,9 @@ import ca.uhn.fhir.model.dstu.resource.Binary;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.*;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
import ca.uhn.fhir.rest.method.OtherOperationTypeEnum;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.RequestDetails;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
@ -83,6 +41,28 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.VersionUtil; import ca.uhn.fhir.util.VersionUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.exception.ExceptionUtils;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URLEncoder;
import java.util.*;
import java.util.zip.GZIPOutputStream;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class RestfulServer extends HttpServlet { public class RestfulServer extends HttpServlet {
@ -280,9 +260,35 @@ public class RestfulServer extends HttpServlet {
} }
} }
private void invokeDestroy(Object theProvider) {
Class<?> clazz = theProvider.getClass();
invokeDestroy(theProvider, clazz);
}
private void invokeDestroy(Object theProvider, Class<?> clazz) {
for (Method m : clazz.getDeclaredMethods()) {
Destroy destroy = m.getAnnotation(Destroy.class);
if (destroy != null) {
try {
m.invoke(theProvider);
} catch (IllegalAccessException e) {
ourLog.error("Exception occurred in destroy ", e);
} catch (InvocationTargetException e) {
ourLog.error("Exception occurred in destroy ", e);
}
return;
}
}
Class<?> supertype = clazz.getSuperclass();
if (!Object.class.equals(supertype)) {
invokeDestroy(theProvider, supertype);
}
}
/** /**
* Returns the setting for automatically adding profile tags * Returns the setting for automatically adding profile tags
* *
* @see #setAddProfileTag(AddProfileTagEnum) * @see #setAddProfileTag(AddProfileTagEnum)
*/ */
public AddProfileTagEnum getAddProfileTag() { public AddProfileTagEnum getAddProfileTag() {
@ -314,7 +320,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Provides the non-resource specific providers which implement method calls on this server * Provides the non-resource specific providers which implement method calls on this server
* *
* @see #getResourceProviders() * @see #getResourceProviders()
*/ */
public Collection<Object> getPlainProviders() { public Collection<Object> getPlainProviders() {
@ -351,7 +357,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* 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. * 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) * @see RestfulServer#setServerName(String)
*/ */
public String getServerName() { public String getServerName() {
@ -642,7 +648,7 @@ public class RestfulServer extends HttpServlet {
return; return;
} }
} }
BaseOperationOutcome oo = null; BaseOperationOutcome oo = null;
int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR; int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR;
@ -761,7 +767,16 @@ public class RestfulServer extends HttpServlet {
// nothing by default // nothing by default
} }
public boolean isUseBrowserFriendlyContentTypes() { @Override
public void destroy() {
if (getResourceProviders() != null) {
for (IResourceProvider iResourceProvider : getResourceProviders()) {
invokeDestroy(iResourceProvider);
}
}
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes; return myUseBrowserFriendlyContentTypes;
} }
@ -778,7 +793,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* 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 * 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. * on the class of the resource(s) being returned.
* *
* @param theAddProfileTag * @param theAddProfileTag
* The behaviour enum (must not be null) * The behaviour enum (must not be null)
*/ */
@ -798,7 +813,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Sets (or clears) the list of interceptors * Sets (or clears) the list of interceptors
* *
* @param theList * @param theList
* The list of interceptors (may be null) * The list of interceptors (may be null)
*/ */
@ -811,7 +826,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Sets (or clears) the list of interceptors * Sets (or clears) the list of interceptors
* *
* @param theList * @param theList
* The list of interceptors (may be null) * The list of interceptors (may be null)
*/ */
@ -831,7 +846,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Sets the non-resource specific providers which implement method calls on this server. * Sets the non-resource specific providers which implement method calls on this server.
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
public void setPlainProviders(Collection<Object> theProviders) { public void setPlainProviders(Collection<Object> theProviders) {
@ -840,7 +855,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Sets the non-resource specific providers which implement method calls on this server. * Sets the non-resource specific providers which implement method calls on this server.
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
public void setPlainProviders(Object... theProv) { public void setPlainProviders(Object... theProv) {
@ -849,7 +864,7 @@ public class RestfulServer extends HttpServlet {
/** /**
* Sets the non-resource specific providers which implement method calls on this server * Sets the non-resource specific providers which implement method calls on this server
* *
* @see #setResourceProviders(Collection) * @see #setResourceProviders(Collection)
*/ */
public void setProviders(Object... theProviders) { public void setProviders(Object... theProviders) {
@ -884,7 +899,7 @@ public class RestfulServer extends HttpServlet {
* 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. * 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> * </p>
* Note that this method can only be called before the server is initialized. * Note that this method can only be called before the server is initialized.
* *
* @throws IllegalStateException * @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. * Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
*/ */

View File

@ -29,7 +29,7 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.server.ResfulServerSelfReferenceTest.DummyPatientResourceProvider; import ca.uhn.fhir.rest.server.RestfulServerSelfReferenceTest.DummyPatientResourceProvider;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
/** /**

View File

@ -0,0 +1,95 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Created by Bill de Beaubien on 11/10/2014.
*/
@RunWith(MockitoJUnitRunner.class)
public class DestroyTest {
@Test
public void testDestroyCallsAnnotatedMethodsOnProviders() throws ServletException {
RestfulServer servlet = new RestfulServer();
DiagnosticReportProvider provider = spy(new DiagnosticReportProvider());
servlet.setResourceProviders(Arrays.asList((IResourceProvider) provider));
servlet.init();
servlet.destroy();
verify(provider).destroy();
}
@Test
public void testChainsUpThroughSuperclasses() throws ServletException {
RestfulServer servlet = new RestfulServer();
DerivedDiagnosticReportProvider provider = spy(new DerivedDiagnosticReportProvider());
servlet.setResourceProviders(Arrays.asList((IResourceProvider) provider));
servlet.init();
servlet.destroy();
verify(provider).destroy();
}
@Test
public void testNoDestroyDoesNotCauseInfiniteRecursion() throws ServletException {
RestfulServer servlet = new RestfulServer();
DiagnosticReportProviderSansDestroy provider = new DiagnosticReportProviderSansDestroy();
servlet.setResourceProviders(Arrays.asList((IResourceProvider) provider));
servlet.init();
servlet.destroy();
// nothing to verify other than the test didn't hang forever
}
public class DiagnosticReportProvider implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return DiagnosticReport.class;
}
@Create
public MethodOutcome createResource(@ResourceParam DiagnosticReport theDiagnosticReport) {
// do nothing
return new MethodOutcome();
}
@Destroy
public void destroy() {
// do nothing
}
}
public class DerivedDiagnosticReportProvider extends DiagnosticReportProvider {
// move along, nothing to see here
}
public class DiagnosticReportProviderSansDestroy implements IResourceProvider {
@Override
public Class<? extends IResource> getResourceType() {
return DiagnosticReport.class;
}
@Create
public MethodOutcome createResource(@ResourceParam DiagnosticReport theDiagnosticReport) {
// do nothing
return new MethodOutcome();
}
}
}

View File

@ -75,11 +75,11 @@ import ca.uhn.fhir.util.PortUtil;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
public class ResfulServerMethodTest { public class RestfulServerMethodTest {
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx; private static FhirContext ourCtx;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResfulServerMethodTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerMethodTest.class);
private static int ourPort; private static int ourPort;
private static DummyDiagnosticReportResourceProvider ourReportProvider; private static DummyDiagnosticReportResourceProvider ourReportProvider;
private static Server ourServer; private static Server ourServer;

View File

@ -41,9 +41,9 @@ import ca.uhn.fhir.util.PortUtil;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
public class ResfulServerSelfReferenceTest { public class RestfulServerSelfReferenceTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResfulServerSelfReferenceTest.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerSelfReferenceTest.class);
private static CloseableHttpClient ourClient; private static CloseableHttpClient ourClient;
private static FhirContext ourCtx; private static FhirContext ourCtx;