Don't re-activate already active identical subscriptions

This commit is contained in:
James Agnew 2018-08-17 11:56:33 -04:00
parent ddc4464552
commit 6511545d25
11 changed files with 296 additions and 172 deletions

View File

@ -19,9 +19,9 @@ import javax.servlet.http.HttpServletRequest;
* 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.

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao.dstu3;
* 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.

View File

@ -29,9 +29,9 @@ import org.springframework.http.ResponseEntity;
* 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.

View File

@ -31,9 +31,9 @@ import java.util.List;
* 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.

View File

@ -29,9 +29,9 @@ import java.util.List;
* 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.

View File

@ -339,54 +339,64 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
return new ArrayList<>(myIdToSubscription.values());
}
public boolean hasSubscription(IIdType theId) {
public CanonicalSubscription hasSubscription(IIdType theId) {
Validate.notNull(theId);
Validate.notBlank(theId.getIdPart());
return myIdToSubscription.containsKey(theId.getIdPart());
return myIdToSubscription.get(theId.getIdPart());
}
/**
* Read the existing subscriptions from the database
*/
@SuppressWarnings("unused")
@Scheduled(fixedDelay = 10000)
@Scheduled(fixedDelay = 60000)
public void initSubscriptions() {
if (!myInitSubscriptionsSemaphore.tryAcquire()) {
return;
}
try {
ourLog.debug("Starting init subscriptions");
SearchParameterMap map = new SearchParameterMap();
map.add(Subscription.SP_TYPE, new TokenParam(null, getChannelType().toCode()));
map.add(Subscription.SP_STATUS, new TokenOrListParam()
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
map.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IBundleProvider subscriptionBundleList = getSubscriptionDao().search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
Set<String> allIds = new HashSet<>();
for (IBaseResource resource : resourceList) {
String nextId = resource.getIdElement().getIdPart();
allIds.add(nextId);
mySubscriptionActivatingSubscriber.activateOrRegisterSubscriptionIfRequired(resource);
}
unregisterAllSubscriptionsNotInCollection(allIds);
ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
doInitSubscriptions();
} finally {
myInitSubscriptionsSemaphore.release();
}
}
public Integer doInitSubscriptions() {
ourLog.debug("Starting init subscriptions");
SearchParameterMap map = new SearchParameterMap();
map.add(Subscription.SP_TYPE, new TokenParam(null, getChannelType().toCode()));
map.add(Subscription.SP_STATUS, new TokenOrListParam()
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
map.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS);
RequestDetails req = new ServletSubRequestDetails();
req.setSubRequest(true);
IBundleProvider subscriptionBundleList = getSubscriptionDao().search(map, req);
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
}
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
Set<String> allIds = new HashSet<>();
int changesCount = 0;
for (IBaseResource resource : resourceList) {
String nextId = resource.getIdElement().getIdPart();
allIds.add(nextId);
boolean changed = mySubscriptionActivatingSubscriber.activateOrRegisterSubscriptionIfRequired(resource);
if (changed) {
changesCount++;
}
}
unregisterAllSubscriptionsNotInCollection(allIds);
ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
return changesCount;
}
@SuppressWarnings("unused")
@PreDestroy
public void preDestroy() {

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription;
* 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.
@ -72,18 +72,6 @@ public class CanonicalSubscription implements Serializable {
myTrigger = theTrigger;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
CanonicalSubscription that = (CanonicalSubscription) theO;
return new EqualsBuilder()
.append(getIdElementString(), that.getIdElementString())
.isEquals();
}
public Subscription.SubscriptionChannelType getChannelType() {
return myChannelType;
@ -120,6 +108,15 @@ public class CanonicalSubscription implements Serializable {
return myHeaders;
}
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
myHeaders = new ArrayList<>();
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
}
}
}
public void setHeaders(String theHeaders) {
myHeaders = new ArrayList<>();
if (isNotBlank(theHeaders)) {
@ -171,19 +168,41 @@ public class CanonicalSubscription implements Serializable {
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(getIdElementString())
.toHashCode();
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
CanonicalSubscription that = (CanonicalSubscription) theO;
EqualsBuilder b = new EqualsBuilder();
b.append(myIdElement, that.myIdElement);
b.append(myCriteriaString, that.myCriteriaString);
b.append(myEndpointUrl, that.myEndpointUrl);
b.append(myPayloadString, that.myPayloadString);
b.append(myHeaders, that.myHeaders);
b.append(myChannelType, that.myChannelType);
b.append(myStatus, that.myStatus);
b.append(myTrigger, that.myTrigger);
b.append(myEmailDetails, that.myEmailDetails);
b.append(myRestHookDetails, that.myRestHookDetails);
return b.isEquals();
}
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
myHeaders = new ArrayList<>();
for (IPrimitiveType<String> next : theHeader) {
if (isNotBlank(next.getValueAsString())) {
myHeaders.add(next.getValueAsString());
}
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myIdElement)
.append(myCriteriaString)
.append(myEndpointUrl)
.append(myPayloadString)
.append(myHeaders)
.append(myChannelType)
.append(myStatus)
.append(myTrigger)
.append(myEmailDetails)
.append(myRestHookDetails)
.toHashCode();
}
public void setIdElement(IIdType theIdElement) {
@ -234,6 +253,28 @@ public class CanonicalSubscription implements Serializable {
myDeliverLatestVersion = theDeliverLatestVersion;
}
@Override
public boolean equals(Object theO) {
if (this == theO) return true;
if (theO == null || getClass() != theO.getClass()) return false;
RestHookDetails that = (RestHookDetails) theO;
return new EqualsBuilder()
.append(myStripVersionId, that.myStripVersionId)
.append(myDeliverLatestVersion, that.myDeliverLatestVersion)
.isEquals();
}
@Override
public int hashCode() {
return new HashCodeBuilder(17, 37)
.append(myStripVersionId)
.append(myDeliverLatestVersion)
.toHashCode();
}
public boolean isStripVersionId() {
return myStripVersionId;
}

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription;
* 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.
@ -70,7 +70,7 @@ public class SubscriptionActivatingSubscriber {
Validate.notNull(theTaskExecutor);
}
public synchronized void activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
public synchronized boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
// Grab the value for "Subscription.channel.type" so we can see if this
// subscriber applies..
String subscriptionChannelType = myCtx
@ -79,7 +79,7 @@ public class SubscriptionActivatingSubscriber {
.getValueAsString();
boolean subscriptionTypeApplies = BaseSubscriptionSubscriber.subscriptionTypeApplies(subscriptionChannelType, myChannelType);
if (subscriptionTypeApplies == false) {
return;
return false;
}
final IPrimitiveType<?> status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
@ -121,25 +121,28 @@ public class SubscriptionActivatingSubscriber {
}
}
});
return true;
} else {
activateSubscription(activeStatus, theSubscription, requestedStatus);
return activateSubscription(activeStatus, theSubscription, requestedStatus);
}
} else if (activeStatus.equals(statusString)) {
registerSubscriptionUnlessAlreadyRegistered(theSubscription);
return registerSubscriptionUnlessAlreadyRegistered(theSubscription);
} else {
// Status isn't "active" or "requested"
unregisterSubscriptionIfRegistered(theSubscription, statusString);
return unregisterSubscriptionIfRegistered(theSubscription, statusString);
}
}
protected void unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) {
if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) {
protected boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) {
if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement()) != null) {
ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue());
mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
return true;
}
return false;
}
private void activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement());
ourLog.info("Activating subscription {} from status {} to {} for channel {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus, myChannelType);
@ -147,11 +150,13 @@ public class SubscriptionActivatingSubscriber {
SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus);
subscription = mySubscriptionDao.update(subscription).getResource();
mySubscriptionInterceptor.submitResourceModifiedForUpdate(subscription);
return true;
} catch (final UnprocessableEntityException e) {
ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
SubscriptionUtil.setStatus(myCtx, subscription, "error");
SubscriptionUtil.setReason(myCtx, subscription, e.getMessage());
mySubscriptionDao.update(subscription);
return false;
}
}
@ -186,14 +191,25 @@ public class SubscriptionActivatingSubscriber {
});
}
private void registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) {
if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) {
protected boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) {
CanonicalSubscription existingSubscription = mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement());
CanonicalSubscription newSubscription = mySubscriptionInterceptor.canonicalize(theSubscription);
if (existingSubscription != null) {
if (newSubscription.equals(existingSubscription)) {
// No changes
return false;
}
}
if (existingSubscription != null) {
ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
} else {
ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
}
mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription);
return true;
}
@VisibleForTesting

View File

@ -62,6 +62,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
private static Server ourServer;
protected IGenericClient ourClient;
protected ResourceCountCache ourResourceCountsCache;
protected static SubscriptionRestHookInterceptor ourReskHookSubscriptionInterceptor;
private TerminologyUploaderProviderR4 myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
@ -70,72 +71,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
super();
}
@AfterClass
public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception {
ourServer.stop();
ourHttpClient.close();
ourServer = null;
ourHttpClient = null;
myValidationSupport.flush();
myValidationSupport = null;
ourWebApplicationContext.close();
ourWebApplicationContext = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
public static int getNumberOfParametersByName(Parameters theParameters, String theName) {
int retVal = 0;
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
retVal++;
}
}
return retVal;
}
public static ParametersParameterComponent getParameterByName(Parameters theParameters, String theName) {
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
return param;
}
}
return new ParametersParameterComponent();
}
public static List<ParametersParameterComponent> getParametersByName(Parameters theParameters, String theName) {
List<ParametersParameterComponent> params = new ArrayList<>();
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
params.add(param);
}
}
return params;
}
public static ParametersParameterComponent getPartByName(ParametersParameterComponent theParameter, String theName) {
for (ParametersParameterComponent part : theParameter.getPart()) {
if (part.getName().equals(theName)) {
return part;
}
}
return new ParametersParameterComponent();
}
public static boolean hasParameterByName(Parameters theParameters, String theName) {
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
return true;
}
}
return false;
}
@After
public void after() throws Exception {
myFhirCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.ONCE);
@ -219,6 +154,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
mySearchEntityDao = wac.getBean(ISearchDao.class);
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
ourReskHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
@ -274,4 +210,70 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
Thread.sleep(500);
}
@AfterClass
public static void afterClassClearContextBaseResourceProviderR4Test() throws Exception {
ourServer.stop();
ourHttpClient.close();
ourServer = null;
ourHttpClient = null;
myValidationSupport.flush();
myValidationSupport = null;
ourWebApplicationContext.close();
ourWebApplicationContext = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
public static int getNumberOfParametersByName(Parameters theParameters, String theName) {
int retVal = 0;
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
retVal++;
}
}
return retVal;
}
public static ParametersParameterComponent getParameterByName(Parameters theParameters, String theName) {
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
return param;
}
}
return new ParametersParameterComponent();
}
public static List<ParametersParameterComponent> getParametersByName(Parameters theParameters, String theName) {
List<ParametersParameterComponent> params = new ArrayList<>();
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
params.add(param);
}
}
return params;
}
public static ParametersParameterComponent getPartByName(ParametersParameterComponent theParameter, String theName) {
for (ParametersParameterComponent part : theParameter.getPart()) {
if (part.getName().equals(theName)) {
return part;
}
}
return new ParametersParameterComponent();
}
public static boolean hasParameterByName(Parameters theParameters, String theName) {
for (ParametersParameterComponent param : theParameters.getParameter()) {
if (param.getName().equals(theName)) {
return true;
}
}
return false;
}
}

View File

@ -52,33 +52,6 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
private List<IIdType> mySubscriptionIds = new ArrayList<>();
private CountingInterceptor myCountingInterceptor;
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();
ourListenerRestServer = new RestfulServer(FhirContext.forR4());
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(ourListenerPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourListenerServer.setHandler(proxyHandler);
ourListenerServer.start();
}
@AfterClass
public static void stopListenerServer() throws Exception {
ourListenerServer.stop();
}
@After
public void afterUnregisterRestHookListener() {
for (IIdType next : mySubscriptionIds) {
@ -180,6 +153,19 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
}
@Test
public void testActiveSubscriptionShouldntReActivate() throws Exception {
String criteria = "Observation?code=111111111&_format=xml";
String payload = "application/fhir+json";
createSubscription(criteria, payload, ourListenerServerBase);
waitForRegisteredSubscriptionCount(1);
for (int i = 0; i < 5; i++) {
Integer changes = ourReskHookSubscriptionInterceptor.doInitSubscriptions();
assertEquals(0, changes.intValue());
}
}
@Test
public void testRestHookSubscriptionNoopUpdateDoesntTriggerNewDelivery() throws Exception {
String payload = "application/fhir+json";
@ -233,7 +219,7 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
ourLog.info("** About to update subscription");
ourClient.update().resource(subscription1).execute();
waitForSize(modCount + 1, ()->myCountingInterceptor.getSentCount());
waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
ourLog.info("** About to send observation");
Observation observation1 = sendObservation(code, "SNOMED-CT");
@ -400,7 +386,6 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
Assert.assertFalse(observation2.getId().isEmpty());
}
@Test
public void testUpdateSubscriptionToMatchLater() throws Exception {
String payload = "application/xml";
@ -586,4 +571,31 @@ public class RestHookTestR4Test extends BaseResourceProviderR4Test {
}
@BeforeClass
public static void startListenerServer() throws Exception {
ourListenerPort = PortUtil.findFreePort();
ourListenerRestServer = new RestfulServer(FhirContext.forR4());
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(ourListenerPort);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourListenerServer.setHandler(proxyHandler);
ourListenerServer.start();
}
@AfterClass
public static void stopListenerServer() throws Exception {
ourListenerServer.stop();
}
}

View File

@ -230,3 +230,46 @@ DELETE FROM TRM_CODESYSTEM_VER;
DELETE FROM TRM_CODESYSTEM;
delete from hfj_resource;
# Delete All (Oracle)
DROP TABLE HFJ_FORCED_ID CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_ver CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_resource CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_history_tag CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_ver CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_forced_id CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_link CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_link CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_coords CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_date CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_number CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_quantity CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_string CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_token CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_spidx_uri CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_tag CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_search_result CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_res_param_present CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_idx_cmp_string_uniq CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_subscription_stats CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_MAP_GRP_ELM_TGT CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_MAP_GRP_ELEMENT CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_MAP_GROUP CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_MAP CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_DESIG CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_PC_LINK CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT_PROPERTY CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CONCEPT CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CODESYSTEM_VER CASCADE CONSTRAINTS PURGE;
DROP TABLE TRM_CODESYSTEM CASCADE CONSTRAINTS PURGE;
DROP TABLE hfj_resource CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SEARCH_RESULT CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SEARCH_PARM CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SEARCH_INCLUDE CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SEARCH CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SUBSCRIPTION CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_SUBSCRIPTION_FLAG_RES CASCADE CONSTRAINTS PURGE;
DROP TABLE HFJ_TAG_DEF CASCADE CONSTRAINTS PURGE;