Repository: gchq/Gaffer (java)

Issue:
Add config options to disable named operations and named views
**Describe the new feature you'd like**
To make configuration more obvious, it should be possible to explicitly disable named operations and named views, instead of having to do this by omitting cache configuration for them.

**Why do you want this feature?**
In #3147 it was noticed that the job tracker can be enabled and disabled (by default disabled), but the same is not true for named operations and named views (by default enabled). Although they are enabled by default, they cannot be used if no cache class has been configured.

This has led to situations where the cache has been disabled so that named operations are disabled. With the changes in #3147 it is possible to use distinct kinds of cache for named operations, named views, federated stores and jobs. This makes it possible to use just one of these services by configuring the cache for only this service.

**Describe alternatives you've considered**
Omitting cache configuration for named operations and named views to disable them, which is less clear.

**Additional context**
This also allows for removing some less readable logic blocks using `CacheServiceLoader.isEnabled` from `Store.java`.


Pull request:
Gh-3169: Add config options to disable named operations and named views
# Related issue

- Resolve #3169

Patch:
diff --git a/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java b/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
index 38d510f954..0165c3b77e 100644
--- a/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
+++ b/core/store/src/main/java/uk/gov/gchq/gaffer/store/Store.java
@@ -817,15 +817,14 @@ public abstract class Store {
 
     protected void populateCaches() {
         caches = new ArrayList<>();
-        if (properties.getJobTrackerEnabled()) {
+        if (properties.getJobTrackerEnabled() && isCacheServiceAvailable(JOB_TRACKER_CACHE_SERVICE_NAME)) {
             jobTracker = new JobTracker(getProperties().getCacheServiceJobTrackerSuffix(graphId));
             caches.add(jobTracker);
         }
-        if (CacheServiceLoader.isDefaultEnabled() ||
-                (CacheServiceLoader.isEnabled(NAMED_OPERATION_CACHE_SERVICE_NAME) &&
-                 CacheServiceLoader.isEnabled(NAMED_VIEW_CACHE_SERVICE_NAME))
-        ) {
+        if (properties.getNamedOperationEnabled() && isCacheServiceAvailable(NAMED_OPERATION_CACHE_SERVICE_NAME)) {
             caches.add(new NamedOperationCache(getProperties().getCacheServiceNamedOperationSuffix(graphId)));
+        }
+        if (properties.getNamedViewEnabled() && isCacheServiceAvailable(NAMED_VIEW_CACHE_SERVICE_NAME)) {
             caches.add(new NamedViewCache(getProperties().getCacheServiceNamedViewSuffix(graphId)));
         }
     }
@@ -1021,7 +1020,7 @@ public abstract class Store {
         addOperationHandler(GetExports.class, new GetExportsHandler());
 
         // Jobs
-        if (nonNull(getJobTracker())) {
+        if (properties.getJobTrackerEnabled() && isCacheServiceAvailable(JOB_TRACKER_CACHE_SERVICE_NAME)) {
             addOperationHandler(GetJobDetails.class, new GetJobDetailsHandler());
             addOperationHandler(GetAllJobDetails.class, new GetAllJobDetailsHandler());
             addOperationHandler(GetJobResults.class, new GetJobResultsHandler());
@@ -1038,18 +1037,17 @@ public abstract class Store {
         addOperationHandler(ToStream.class, new ToStreamHandler<>());
         addOperationHandler(ToVertices.class, new ToVerticesHandler());
 
-        if (CacheServiceLoader.isDefaultEnabled() ||
-                (CacheServiceLoader.isEnabled(NAMED_OPERATION_CACHE_SERVICE_NAME) &&
-                 CacheServiceLoader.isEnabled(NAMED_VIEW_CACHE_SERVICE_NAME))
-            ) {
-            // Named operation
+        // Named Operation
+        if (properties.getNamedOperationEnabled() && isCacheServiceAvailable(NAMED_OPERATION_CACHE_SERVICE_NAME)) {
             addOperationHandler(NamedOperation.class, new NamedOperationHandler());
             final String suffixNamedOperationCacheName = properties.getCacheServiceNamedOperationSuffix(graphId);
             addOperationHandler(AddNamedOperation.class, new AddNamedOperationHandler(suffixNamedOperationCacheName, properties.isNestedNamedOperationAllow()));
             addOperationHandler(GetAllNamedOperations.class, new GetAllNamedOperationsHandler(suffixNamedOperationCacheName));
             addOperationHandler(DeleteNamedOperation.class, new DeleteNamedOperationHandler(suffixNamedOperationCacheName));
+        }
 
-            // Named view
+        // Named View
+        if (properties.getNamedViewEnabled() && isCacheServiceAvailable(NAMED_VIEW_CACHE_SERVICE_NAME)) {
             final String suffixNamedViewCacheName = properties.getCacheServiceNamedViewSuffix(graphId);
             addOperationHandler(AddNamedView.class, new AddNamedViewHandler(suffixNamedViewCacheName));
             addOperationHandler(GetAllNamedViews.class, new GetAllNamedViewsHandler(suffixNamedViewCacheName));
@@ -1119,22 +1117,32 @@ public abstract class Store {
         }
     }
 
+    private boolean isCacheServiceAvailable(final String serviceName) {
+        return (CacheServiceLoader.isEnabled(serviceName) ||
+                CacheServiceLoader.isDefaultEnabled());
+    }
+
     protected void startCacheServiceLoader(final StoreProperties properties) {
         final String jobTrackerCacheClass = properties.getJobTrackerCacheServiceClass();
         final String namedViewCacheClass = properties.getNamedViewCacheServiceClass();
         final String namedOperationCacheClass = properties.getNamedOperationCacheServiceClass();
         final String defaultCacheClass = properties.getDefaultCacheServiceClass();
+
         final boolean jobTrackerEnabled = properties.getJobTrackerEnabled();
+        final boolean namedViewEnabled = properties.getNamedViewEnabled();
+        final boolean namedOperationEnabled = properties.getNamedOperationEnabled();
+
         final boolean defaultServiceRequired = (jobTrackerEnabled && jobTrackerCacheClass == null) |
-                                               (namedViewCacheClass == null) | (namedOperationCacheClass == null);
+                                               (namedViewEnabled && namedViewCacheClass == null) |
+                                               (namedOperationEnabled && namedOperationCacheClass == null);
 
         if (jobTrackerEnabled && jobTrackerCacheClass != null) {
             CacheServiceLoader.initialise(JOB_TRACKER_CACHE_SERVICE_NAME, jobTrackerCacheClass, properties.getProperties());
         }
-        if (namedViewCacheClass != null) {
+        if (namedViewEnabled && namedViewCacheClass != null) {
             CacheServiceLoader.initialise(NAMED_VIEW_CACHE_SERVICE_NAME, namedViewCacheClass, properties.getProperties());
         }
-        if (namedOperationCacheClass != null) {
+        if (namedOperationEnabled && namedOperationCacheClass != null) {
             CacheServiceLoader.initialise(NAMED_OPERATION_CACHE_SERVICE_NAME, namedOperationCacheClass, properties.getProperties());
         }
         if (defaultServiceRequired && defaultCacheClass != null) {
diff --git a/core/store/src/main/java/uk/gov/gchq/gaffer/store/StoreProperties.java b/core/store/src/main/java/uk/gov/gchq/gaffer/store/StoreProperties.java
index feff41c083..85b1f221e6 100644
--- a/core/store/src/main/java/uk/gov/gchq/gaffer/store/StoreProperties.java
+++ b/core/store/src/main/java/uk/gov/gchq/gaffer/store/StoreProperties.java
@@ -75,6 +75,8 @@ public class StoreProperties implements Cloneable {
     public static final String OPERATION_DECLARATIONS = "gaffer.store.operation.declarations";
     public static final String OPERATION_DECLARATIONS_JSON = "gaffer.store.operation.declarations.json";
 
+    public static final String NAMED_VIEW_ENABLED = "gaffer.store.namedview.enabled";
+    public static final String NAMED_OPERATION_ENABLED = "gaffer.store.namedoperation.enabled";
     public static final String JOB_TRACKER_ENABLED = "gaffer.store.job.tracker.enabled";
     public static final String RESCHEDULE_JOBS_ON_START = "gaffer.store.job.rescheduleOnStart";
 
@@ -315,6 +317,22 @@ public class StoreProperties implements Cloneable {
         set(JOB_TRACKER_ENABLED, Boolean.toString(jobTrackerEnabled));
     }
 
+    public boolean getNamedViewEnabled() {
+        return Boolean.valueOf(get(NAMED_VIEW_ENABLED, "true"));
+    }
+
+    public void setNamedViewEnabled(final boolean namedViewEnabled) {
+        set(NAMED_VIEW_ENABLED, Boolean.toString(namedViewEnabled));
+    }
+
+    public boolean getNamedOperationEnabled() {
+        return Boolean.valueOf(get(NAMED_OPERATION_ENABLED, "true"));
+    }
+
+    public void setNamedOperationEnabled(final boolean namedOperationEnabled) {
+        set(NAMED_OPERATION_ENABLED, Boolean.toString(namedOperationEnabled));
+    }
+
     public boolean getRescheduleJobsOnStart() {
         return Boolean.valueOf(get(RESCHEDULE_JOBS_ON_START, "false"));
     }


Test patch:
diff --git a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StorePropertiesTest.java b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StorePropertiesTest.java
index 0bf87b80db..6ddedb629a 100644
--- a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StorePropertiesTest.java
+++ b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StorePropertiesTest.java
@@ -235,6 +235,45 @@ public class StorePropertiesTest {
         assertThat(props.getJobTrackerEnabled()).isTrue();
     }
 
+    @Test
+    public void shouldGetAndSetNamedViewEnabled() {
+        // Given / When
+        final StoreProperties props = new StoreProperties();
+        // Then
+        assertThat(props.getNamedViewEnabled()).isTrue();
+
+        // Given / When
+        props.setNamedViewEnabled(false);
+        // Then
+        assertThat(props.getNamedViewEnabled()).isFalse();
+    }
+
+    @Test
+    public void shouldGetAndSetNamedOperationEnabled() {
+        // Given / When
+        final StoreProperties props = new StoreProperties();
+        // Then
+        assertThat(props.getNamedOperationEnabled()).isTrue();
+
+        // Given / When
+        props.setNamedOperationEnabled(false);
+        // Then
+        assertThat(props.getNamedOperationEnabled()).isFalse();
+    }
+
+    @Test
+    public void shouldGetAndSetRescheduleJobsOnStart() {
+        // Given / When
+        final StoreProperties props = new StoreProperties();
+        // Then
+        assertThat(props.getRescheduleJobsOnStart()).isFalse();
+
+        // Given / When
+        props.setRescheduleJobsOnStart(true);
+        // Then
+        assertThat(props.getRescheduleJobsOnStart()).isTrue();
+    }
+
     @Test
     public void shouldGetAndSetSchemaClass() {
         // Given / When
diff --git a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
index 17440a0b65..45d9d871af 100644
--- a/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
+++ b/core/store/src/test/java/uk/gov/gchq/gaffer/store/StoreTest.java
@@ -507,15 +507,16 @@ public class StoreTest {
         assertThat(result).isSameAs(getElementsResult);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
-    public void shouldReturnAllSupportedOperations(@Mock final StoreProperties properties) throws Exception {
+    public void shouldReturnAllSupportedOperationsWhenJobTrackerIsEnabled(@Mock final StoreProperties properties) throws Exception {
         // Given
         CacheServiceLoader.initialise(HashMapCacheService.class.getName());
 
         final Schema schema = createSchemaMock();
         given(properties.getJobExecutorThreadCount()).willReturn(1);
         given(properties.getJobTrackerEnabled()).willReturn(true);
+        given(properties.getNamedViewEnabled()).willReturn(true);
+        given(properties.getNamedOperationEnabled()).willReturn(true);
         store.initialise("graphId", schema, properties);
 
         // When
@@ -617,7 +618,6 @@ public class StoreTest {
         assertThat(supportedOperations).isEqualTo(expectedOperations);
     }
 
-    @SuppressWarnings("unchecked")
     @Test
     public void shouldReturnAllSupportedOperationsWhenJobTrackerIsDisabled(@Mock final StoreProperties properties) throws Exception {
         // Given
@@ -626,6 +626,8 @@ public class StoreTest {
         final Schema schema = createSchemaMock();
         given(properties.getJobExecutorThreadCount()).willReturn(1);
         given(properties.getJobTrackerEnabled()).willReturn(false);
+        given(properties.getNamedViewEnabled()).willReturn(true);
+        given(properties.getNamedOperationEnabled()).willReturn(true);
         store.initialise("graphId", schema, properties);
 
         // When
diff --git a/core/store/src/test/java/uk/gov/gchq/gaffer/store/integration/StoreIT.java b/core/store/src/test/java/uk/gov/gchq/gaffer/store/integration/StoreIT.java
index 07686293d4..7bd90e55a0 100644
--- a/core/store/src/test/java/uk/gov/gchq/gaffer/store/integration/StoreIT.java
+++ b/core/store/src/test/java/uk/gov/gchq/gaffer/store/integration/StoreIT.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2016-2023 Crown Copyright
+ * Copyright 2016-2024 Crown Copyright
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,8 +16,10 @@
 
 package uk.gov.gchq.gaffer.store.integration;
 
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
+import uk.gov.gchq.gaffer.cache.CacheServiceLoader;
 import uk.gov.gchq.gaffer.commonutil.StreamUtil;
 import uk.gov.gchq.gaffer.commonutil.TestGroups;
 import uk.gov.gchq.gaffer.data.element.Element;
@@ -49,6 +51,10 @@ import static uk.gov.gchq.gaffer.store.StoreTrait.PRE_AGGREGATION_FILTERING;
 import static uk.gov.gchq.gaffer.store.StoreTrait.TRANSFORMATION;
 
 public class StoreIT {
+    @BeforeEach
+    void before() {
+        CacheServiceLoader.shutdown();
+    }
 
     @Test
     public void shouldCreateStoreAndValidateSchemas() throws SchemaException, StoreException {
@@ -75,6 +81,40 @@ public class StoreIT {
         assertThat(testStore.getSchema().validate().isValid()).isTrue();
     }
 
+    @Test
+    public void shouldCreateStoreWithSpecifiedCaches() throws SchemaException, StoreException {
+        // Given
+        final Store testStore = new TestStore();
+
+        // When
+        testStore.initialise("testGraph", new Schema(), StoreProperties.loadStoreProperties("allCaches.properties"));
+
+        // Then
+        assertThat(CacheServiceLoader.isDefaultEnabled()).isFalse();
+        assertThat(CacheServiceLoader.isEnabled("JobTracker")).isTrue();
+        assertThat(CacheServiceLoader.isEnabled("NamedView")).isTrue();
+        assertThat(CacheServiceLoader.isEnabled("NamedOperation")).isTrue();
+    }
+
+    @Test
+    public void shouldCreateStoreWithoutAnyCaches() throws SchemaException, StoreException {
+        // Given
+        final Store testStore = new TestStore();
+        final StoreProperties properties = new StoreProperties();
+        properties.setJobTrackerEnabled(false);
+        properties.setNamedViewEnabled(false);
+        properties.setNamedOperationEnabled(false);
+
+        // When
+        testStore.initialise("testGraph", new Schema(), properties);
+
+        // Then
+        assertThat(CacheServiceLoader.isDefaultEnabled()).isFalse();
+        assertThat(CacheServiceLoader.isEnabled("JobTracker")).isFalse();
+        assertThat(CacheServiceLoader.isEnabled("NamedView")).isFalse();
+        assertThat(CacheServiceLoader.isEnabled("NamedOperation")).isFalse();
+    }
+
     private class TestStore extends Store {
         private final Set<StoreTrait> traits = new HashSet<>(Arrays.asList(INGEST_AGGREGATION, PRE_AGGREGATION_FILTERING, TRANSFORMATION));