com.ikanow.aleph2.management_db.services.DataBucketStatusCrudService.java Source code

Java tutorial

Introduction

Here is the source code for com.ikanow.aleph2.management_db.services.DataBucketStatusCrudService.java

Source

/*******************************************************************************
 * Copyright 2015, The IKANOW Open Source Project.
 *
 * 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.
 *******************************************************************************/
package com.ikanow.aleph2.management_db.services;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import scala.Tuple2;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.ikanow.aleph2.data_model.interfaces.data_services.IManagementDbService;
import com.ikanow.aleph2.data_model.interfaces.data_services.IStorageService;
import com.ikanow.aleph2.data_model.interfaces.shared_services.IBasicSearchService;
import com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService;
import com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService;
import com.ikanow.aleph2.data_model.interfaces.shared_services.IServiceContext;
import com.ikanow.aleph2.data_model.interfaces.shared_services.MockServiceContext;
import com.ikanow.aleph2.data_model.objects.data_import.DataBucketStatusBean;
import com.ikanow.aleph2.data_model.objects.data_import.DataBucketBean;
import com.ikanow.aleph2.data_model.objects.shared.AuthorizationBean;
import com.ikanow.aleph2.data_model.objects.shared.BasicMessageBean;
import com.ikanow.aleph2.data_model.objects.shared.ProjectBean;
import com.ikanow.aleph2.data_model.utils.BeanTemplateUtils;
import com.ikanow.aleph2.data_model.utils.BeanTemplateUtils.MethodNamingHelper;
import com.ikanow.aleph2.data_model.utils.CrudUtils;
import com.ikanow.aleph2.data_model.utils.ErrorUtils;
import com.ikanow.aleph2.data_model.utils.FutureUtils;
import com.ikanow.aleph2.data_model.utils.Lambdas;
import com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent;
import com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent;
import com.ikanow.aleph2.data_model.utils.FutureUtils.ManagementFuture;
import com.ikanow.aleph2.data_model.utils.ModuleUtils;
import com.ikanow.aleph2.data_model.utils.Patterns;
import com.ikanow.aleph2.data_model.utils.SetOnce;
import com.ikanow.aleph2.management_db.data_model.BucketActionMessage;
import com.ikanow.aleph2.management_db.data_model.BucketActionRetryMessage;
import com.ikanow.aleph2.management_db.utils.ManagementDbErrorUtils;
import com.ikanow.aleph2.management_db.utils.MgmtCrudUtils;

//TODO (ALEPH-19): No method currently to access the harvest/enrichment/storage logs

/** CRUD service for Data Bucket Status with management proxy
 * @author acp
 */
public class DataBucketStatusCrudService implements IManagementCrudService<DataBucketStatusBean> {
    @SuppressWarnings("unused")
    private static final Logger _logger = LogManager.getLogger();

    protected final Provider<IStorageService> _storage_service;
    protected final Provider<IManagementDbService> _underlying_management_db;

    protected final SetOnce<ICrudService<DataBucketBean>> _underlying_data_bucket_db = new SetOnce<>();
    protected final SetOnce<ICrudService<DataBucketStatusBean>> _underlying_data_bucket_status_db = new SetOnce<>();
    protected final SetOnce<ICrudService<BucketActionRetryMessage>> _bucket_action_retry_store = new SetOnce<>();

    protected final ManagementDbActorContext _actor_context;

    /** Guice invoked constructor
     * @param underlying_management_db
     */
    @Inject
    public DataBucketStatusCrudService(final IServiceContext service_context,
            ManagementDbActorContext actor_context) {
        _underlying_management_db = service_context.getServiceProvider(IManagementDbService.class, Optional.empty())
                .get();

        _storage_service = service_context.getServiceProvider(IStorageService.class, Optional.empty()).get();

        _actor_context = actor_context;

        ModuleUtils.getAppInjector().thenRun(() -> initialize());
    }

    /** Work around for Guice circular development issues
     */
    protected void initialize() {
        _underlying_data_bucket_db.set(_underlying_management_db.get().getDataBucketStore());
        _underlying_data_bucket_status_db.set(_underlying_management_db.get().getDataBucketStatusStore());
        _bucket_action_retry_store
                .set(_underlying_management_db.get().getRetryStore(BucketActionRetryMessage.class));
    }

    /** User constructor, for wrapping
     * @param underlying_management_db
     * @param underlying_data_bucket_db
     */
    public DataBucketStatusCrudService(final Provider<IManagementDbService> underlying_management_db,
            final Provider<IStorageService> storage_service,
            final ICrudService<DataBucketBean> underlying_data_bucket_db,
            final ICrudService<DataBucketStatusBean> underlying_data_bucket_status_db) {
        _underlying_management_db = underlying_management_db;
        _underlying_data_bucket_db.set(underlying_data_bucket_db);
        _underlying_data_bucket_status_db.set(underlying_data_bucket_status_db);
        _bucket_action_retry_store
                .set(_underlying_management_db.get().getRetryStore(BucketActionRetryMessage.class));
        _actor_context = ManagementDbActorContext.get();
        _storage_service = storage_service;
    }

    @Override
    public boolean deregisterOptimizedQuery(List<String> ordered_field_list) {
        return _underlying_data_bucket_status_db.get().deregisterOptimizedQuery(ordered_field_list);
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService#getSearchService()
     */
    @Override
    public Optional<IBasicSearchService<DataBucketStatusBean>> getSearchService() {
        return _underlying_data_bucket_status_db.get().getSearchService();
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.ICrudService#getUnderlyingPlatformDriver(java.lang.Class, java.util.Optional)
     */
    @SuppressWarnings("unchecked")
    @Override
    public <T> Optional<T> getUnderlyingPlatformDriver(Class<T> driver_class, Optional<String> driver_options) {
        if (driver_class == ICrudService.class) {
            return (Optional<T>) Optional.of(_underlying_data_bucket_status_db.get());
        } else {
            throw new RuntimeException("DataBucketCrudService.getUnderlyingPlatformDriver not supported");
        }
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getFilteredRepo(java.lang.String, java.util.Optional, java.util.Optional)
     */
    @Override
    public IManagementCrudService<DataBucketStatusBean> getFilteredRepo(String authorization_fieldname,
            Optional<AuthorizationBean> client_auth, Optional<ProjectBean> project_auth) {
        return new DataBucketStatusCrudService(
                new MockServiceContext.MockProvider<IManagementDbService>(
                        _underlying_management_db.get().getFilteredDb(client_auth, project_auth)),
                _storage_service,
                _underlying_data_bucket_db.get().getFilteredRepo(authorization_fieldname, client_auth,
                        project_auth),
                _underlying_data_bucket_status_db.get().getFilteredRepo(authorization_fieldname, client_auth,
                        project_auth));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObject(java.lang.Object, boolean)
     */
    @Override
    public ManagementFuture<Supplier<Object>> storeObject(DataBucketStatusBean new_object,
            boolean replace_if_present) {

        if (replace_if_present) {
            throw new RuntimeException("This method is not supported, call update instead");
        }

        // Check - has to have an _id and bucket_path

        if ((null == new_object._id()) || (null == new_object.bucket_path()) || (null == new_object.suspended())) {
            return FutureUtils.createManagementFuture(FutureUtils
                    .returnError(new RuntimeException(ErrorUtils.get(ManagementDbErrorUtils.ILLEGAL_STATUS_CREATION,
                            new_object._id() + "|" + new_object.bucket_path() + "|" + new_object.suspended()))));
        }

        // Assuming the store works, then check the bucket

        final CompletableFuture<Supplier<Object>> ret_val = _underlying_data_bucket_status_db.get()
                .storeObject(new_object);

        return FutureUtils.createManagementFuture(ret_val,
                CompletableFuture.completedFuture(Collections.<BasicMessageBean>emptyList()));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObject(java.lang.Object)
     */
    @Override
    public ManagementFuture<Supplier<Object>> storeObject(DataBucketStatusBean new_object) {
        return this.storeObject(new_object, false);
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObjects(java.util.List, boolean)
     */
    @Override
    public ManagementFuture<Tuple2<Supplier<List<Object>>, Supplier<Long>>> storeObjects(
            List<DataBucketStatusBean> new_objects, boolean continue_on_error) {
        throw new RuntimeException("This method is not supported, call storeObject on each object separately");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#storeObjects(java.util.List)
     */
    @Override
    public ManagementFuture<Tuple2<Supplier<List<Object>>, Supplier<Long>>> storeObjects(
            List<DataBucketStatusBean> new_objects) {
        return this.storeObjects(new_objects, false);
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#optimizeQuery(java.util.List)
     */
    @Override
    public ManagementFuture<Boolean> optimizeQuery(List<String> ordered_field_list) {
        return FutureUtils
                .createManagementFuture(_underlying_data_bucket_status_db.get().optimizeQuery(ordered_field_list));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent)
     */
    @Override
    public ManagementFuture<Optional<DataBucketStatusBean>> getObjectBySpec(
            QueryComponent<DataBucketStatusBean> unique_spec) {
        return FutureUtils
                .createManagementFuture(_underlying_data_bucket_status_db.get().getObjectBySpec(unique_spec));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, java.util.List, boolean)
     */
    @Override
    public ManagementFuture<Optional<DataBucketStatusBean>> getObjectBySpec(
            QueryComponent<DataBucketStatusBean> unique_spec, List<String> field_list, boolean include) {
        return FutureUtils.createManagementFuture(
                _underlying_data_bucket_status_db.get().getObjectBySpec(unique_spec, field_list, include));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectById(java.lang.Object)
     */
    @Override
    public ManagementFuture<Optional<DataBucketStatusBean>> getObjectById(Object id) {
        return FutureUtils.createManagementFuture(_underlying_data_bucket_status_db.get().getObjectById(id));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectById(java.lang.Object, java.util.List, boolean)
     */
    @Override
    public ManagementFuture<Optional<DataBucketStatusBean>> getObjectById(Object id, List<String> field_list,
            boolean include) {
        return FutureUtils.createManagementFuture(
                _underlying_data_bucket_status_db.get().getObjectById(id, field_list, include));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent)
     */
    @Override
    public ManagementFuture<Cursor<DataBucketStatusBean>> getObjectsBySpec(
            QueryComponent<DataBucketStatusBean> spec) {
        return FutureUtils.createManagementFuture(_underlying_data_bucket_status_db.get().getObjectsBySpec(spec));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, java.util.List, boolean)
     */
    @Override
    public ManagementFuture<Cursor<DataBucketStatusBean>> getObjectsBySpec(
            QueryComponent<DataBucketStatusBean> spec, List<String> field_list, boolean include) {
        return FutureUtils.createManagementFuture(
                _underlying_data_bucket_status_db.get().getObjectsBySpec(spec, field_list, include));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#countObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent)
     */
    @Override
    public ManagementFuture<Long> countObjectsBySpec(QueryComponent<DataBucketStatusBean> spec) {
        return FutureUtils.createManagementFuture(_underlying_data_bucket_status_db.get().countObjectsBySpec(spec));
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#countObjects()
     */
    @Override
    public ManagementFuture<Long> countObjects() {
        return FutureUtils.createManagementFuture(_underlying_data_bucket_status_db.get().countObjects());
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectById(java.lang.Object, com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent)
     */
    @Override
    public ManagementFuture<Boolean> updateObjectById(Object id, UpdateComponent<DataBucketStatusBean> update) {
        return this.updateObjectBySpec(
                CrudUtils.allOf(DataBucketStatusBean.class).when(DataBucketStatusBean::_id, id), Optional.of(false),
                update);
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, java.util.Optional, com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent)
     */
    @Override
    public ManagementFuture<Boolean> updateObjectBySpec(final QueryComponent<DataBucketStatusBean> unique_spec,
            final Optional<Boolean> upsert, final UpdateComponent<DataBucketStatusBean> update) {
        final MethodNamingHelper<DataBucketStatusBean> helper = BeanTemplateUtils.from(DataBucketStatusBean.class);

        if (upsert.orElse(false)) {
            throw new RuntimeException("This method is not supported with upsert set and true");
        }

        final Collection<BasicMessageBean> errors = validateUpdateCommand(update);
        if (!errors.isEmpty()) {
            return FutureUtils.createManagementFuture(CompletableFuture.completedFuture(false),
                    CompletableFuture.completedFuture(errors));
        }

        // Now perform the update and based on the results we may need to send out instructions
        // to any listening buckets

        final CompletableFuture<Optional<DataBucketStatusBean>> update_reply = _underlying_data_bucket_status_db
                .get().updateAndReturnObjectBySpec(unique_spec, Optional.of(false), update, Optional.of(false),
                        Arrays.asList(helper.field(DataBucketStatusBean::_id),
                                helper.field(DataBucketStatusBean::confirmed_suspended),
                                helper.field(DataBucketStatusBean::confirmed_multi_node_enabled),
                                helper.field(DataBucketStatusBean::confirmed_master_enrichment_type),
                                helper.field(DataBucketStatusBean::suspended),
                                helper.field(DataBucketStatusBean::quarantined_until),
                                helper.field(DataBucketStatusBean::node_affinity)),
                        true);

        try {
            // What happens now depends on the contents of the message         

            // Maybe the user wanted to suspend/resume the bucket:

            final CompletableFuture<Collection<BasicMessageBean>> suspend_future = Lambdas
                    .<CompletableFuture<Collection<BasicMessageBean>>>get(() -> {
                        if (update.getAll().containsKey(helper.field(DataBucketStatusBean::suspended))) {

                            // (note this handles suspending the bucket if no handlers are available)
                            return getOperationFuture(update_reply, sb -> sb.suspended(),
                                    _underlying_data_bucket_db.get(), _underlying_data_bucket_status_db.get(),
                                    _actor_context, _bucket_action_retry_store.get());
                        } else { // (this isn't an error, just nothing to do here)
                            return CompletableFuture.completedFuture(Collections.<BasicMessageBean>emptyList());
                        }
                    });

            // Maybe the user wanted to set quarantine on/off:

            final CompletableFuture<Collection<BasicMessageBean>> quarantine_future = Lambdas
                    .<CompletableFuture<Collection<BasicMessageBean>>>get(() -> {
                        if (update.getAll().containsKey(helper.field(DataBucketStatusBean::quarantined_until))) {

                            // (note this handles suspending the bucket if no handlers are available)
                            return getOperationFuture(update_reply, sb -> { // (this predicate is slightly more complex)
                                return (null != sb.quarantined_until())
                                        || (new Date().getTime() < sb.quarantined_until().getTime());
                            }, _underlying_data_bucket_db.get(), _underlying_data_bucket_status_db.get(),
                                    _actor_context, _bucket_action_retry_store.get());
                        } else { // (this isn't an error, just nothing to do here)
                            return CompletableFuture.completedFuture(Collections.<BasicMessageBean>emptyList());
                        }
                    });

            return FutureUtils.createManagementFuture(update_reply.thenApply(o -> o.isPresent()), // whether we updated
                    suspend_future.thenCombine(quarantine_future,
                            (f1, f2) -> Stream.concat(f1.stream(), f2.stream()).collect(Collectors.toList())));
            //(+combine error messages from suspend/quarantine operations)
        } catch (Exception e) {
            // This is a serious enough exception that we'll just leave here
            return FutureUtils.createManagementFuture(FutureUtils.returnError(e));
        }
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, java.util.Optional, com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent)
     */
    @Override
    public ManagementFuture<Long> updateObjectsBySpec(final QueryComponent<DataBucketStatusBean> spec,
            final Optional<Boolean> upsert, final UpdateComponent<DataBucketStatusBean> update) {
        if (upsert.orElse(false)) {
            throw new RuntimeException("This method is not supported with upsert set and true");
        }

        final CompletableFuture<Cursor<DataBucketStatusBean>> affected_ids = _underlying_data_bucket_status_db.get()
                .getObjectsBySpec(spec, Arrays.asList(
                        BeanTemplateUtils.from(DataBucketStatusBean.class).field(DataBucketStatusBean::_id)), true);

        try {
            return MgmtCrudUtils
                    .applyCrudPredicate(affected_ids, status -> this.updateObjectById(status._id(), update))._1();
        } catch (Exception e) {
            // This is a serious enough exception that we'll just leave here
            return FutureUtils.createManagementFuture(FutureUtils.returnError(e));
        }
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#updateAndReturnObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent, java.util.Optional, com.ikanow.aleph2.data_model.utils.CrudUtils.UpdateComponent, java.util.Optional, java.util.List, boolean)
     */
    @Override
    public ManagementFuture<Optional<DataBucketStatusBean>> updateAndReturnObjectBySpec(
            QueryComponent<DataBucketStatusBean> unique_spec, Optional<Boolean> upsert,
            UpdateComponent<DataBucketStatusBean> update, Optional<Boolean> before_updated, List<String> field_list,
            boolean include) {
        throw new RuntimeException("This method is not supported (use non-atomic update/get instead)");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectById(java.lang.Object)
     */
    @Override
    public ManagementFuture<Boolean> deleteObjectById(Object id) {
        throw new RuntimeException("This method is not supported (delete the bucket instead)");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent)
     */
    @Override
    public ManagementFuture<Boolean> deleteObjectBySpec(QueryComponent<DataBucketStatusBean> unique_spec) {
        throw new RuntimeException("This method is not supported (delete the bucket instead)");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteObjectsBySpec(com.ikanow.aleph2.data_model.utils.CrudUtils.QueryComponent)
     */
    @Override
    public ManagementFuture<Long> deleteObjectsBySpec(QueryComponent<DataBucketStatusBean> spec) {
        throw new RuntimeException("This method is not supported (delete the bucket instead)");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#deleteDatastore()
     */
    @Override
    public ManagementFuture<Boolean> deleteDatastore() {
        throw new RuntimeException("This method is not supported");
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IManagementCrudService#getRawService()
     */
    @Override
    public IManagementCrudService<JsonNode> getRawService() {
        throw new RuntimeException("DataBucketStatusCrudService.getRawService not supported");
    }

    //////////////////////////////////////////////////////////////////////

    // UTILITY CODE
    private static Collection<BasicMessageBean> validateUpdateCommand(
            UpdateComponent<DataBucketStatusBean> update) {
        final MethodNamingHelper<DataBucketStatusBean> helper = BeanTemplateUtils.from(DataBucketStatusBean.class);

        // There's a limited set of operations that are permitted:
        // - change the number of objects
        // - suspend or resume
        // - quarantine      
        // - add/remove log or status messages

        final ImmutableSet<String> statusFields = ImmutableSet.<String>builder()
                .add(helper.field(DataBucketStatusBean::last_harvest_status_messages))
                .add(helper.field(DataBucketStatusBean::last_enrichment_status_messages))
                .add(helper.field(DataBucketStatusBean::last_storage_status_messages)).build();

        final List<BasicMessageBean> errors = update.getAll().entries().stream()
                .map(kvtmp -> Patterns.match(kvtmp).<BasicMessageBean>andReturn()
                        .when(kv -> helper.field(DataBucketStatusBean::num_objects).equals(kv.getKey())
                                && (kv.getValue()._1().equals(CrudUtils.UpdateOperator.set)
                                        || kv.getValue()._1().equals(CrudUtils.UpdateOperator.increment))
                                && (kv.getValue()._2() instanceof Number), __ -> {
                                    return null;
                                })
                        .when(kv -> helper.field(DataBucketStatusBean::suspended).equals(kv.getKey())
                                && kv.getValue()._1().equals(CrudUtils.UpdateOperator.set)
                                && (kv.getValue()._2() instanceof Boolean), __ -> {
                                    return null;
                                })
                        .when(kv -> helper.field(DataBucketStatusBean::quarantined_until).equals(kv.getKey())
                                && ((kv.getValue()._1().equals(CrudUtils.UpdateOperator.set)
                                        && (kv.getValue()._2() instanceof Date))
                                        || kv.getValue()._1().equals(CrudUtils.UpdateOperator.unset)),
                                __ -> {
                                    return null;
                                })
                        .when(kv -> statusFields.contains(kv.getKey().split("[.]")), __ -> {
                            return null;
                        }).otherwise(kv -> {
                            return new BasicMessageBean(new Date(), // date
                                    false, // success
                                    IManagementDbService.CORE_MANAGEMENT_DB.get(),
                                    BucketActionMessage.UpdateBucketActionMessage.class.getSimpleName(), null, // message code
                                    ErrorUtils.get(ManagementDbErrorUtils.ILLEGAL_UPDATE_COMMAND, kv.getKey(),
                                            kv.getValue()._1()),
                                    null); // details                  
                        }))
                .filter(errmsg -> errmsg != null).collect(Collectors.toList());

        return errors;
    }

    /** Tries to distribute a request to listening data import managers to notify their harvesters that the bucket state has been updated
     * @param update_reply - the future reply to the find-and-update
     * @param suspended_predicate - takes the status bean (must exist at this point) and checks whether the bucket should be suspended
     * @param underlying_data_bucket_db - the data bucket bean db store
     * @param actor_context - actor context for distributing out requests
     * @param retry_store - the retry store for handling data import manager connectivity problems
     * @return a collection of success/error messages from either this function or the 
     */
    private static <T> CompletableFuture<Collection<BasicMessageBean>> getOperationFuture(
            final CompletableFuture<Optional<DataBucketStatusBean>> update_reply,
            final Predicate<DataBucketStatusBean> suspended_predicate,
            final ICrudService<DataBucketBean> underlying_data_bucket_db,
            final ICrudService<DataBucketStatusBean> underlying_data_bucket_status_db,
            final ManagementDbActorContext actor_context,
            final ICrudService<BucketActionRetryMessage> retry_store) {
        return update_reply.thenCompose(sb -> {
            return sb.isPresent() ? underlying_data_bucket_db.getObjectById(sb.get()._id())
                    : CompletableFuture.completedFuture(Optional.empty());
        }).thenCompose(bucket -> {
            if (!bucket.isPresent()) {
                return CompletableFuture.completedFuture(Arrays.asList(new BasicMessageBean(new Date(), // date
                        false, // success
                        IManagementDbService.CORE_MANAGEMENT_DB.get(),
                        BucketActionMessage.UpdateBucketActionMessage.class.getSimpleName(), null, // message code
                        ErrorUtils.get(ManagementDbErrorUtils.MISSING_STATUS_BEAN_OR_BUCKET,
                                update_reply.join().map(s -> s._id()).orElse("(unknown)")),
                        null) // details                  
                ));
            } else { // If we're here we've retrieved both the bucket and bucket status, so we're good to go
                final DataBucketStatusBean status_bean = update_reply.join().get();
                // (as above, if we're here then must exist)

                // Once we have the bucket, issue the update command
                final BucketActionMessage.UpdateBucketActionMessage update_message = new BucketActionMessage.UpdateBucketActionMessage(
                        bucket.get(), suspended_predicate.test(status_bean), // (ie user picks whether to suspend or unsuspend here)
                        bucket.get(), new HashSet<String>(
                                Optional.ofNullable(status_bean.node_affinity()).orElse(Collections.emptyList())));

                // Collect message and handle retries

                final CompletableFuture<Collection<BasicMessageBean>> management_results = MgmtCrudUtils
                        .applyRetriableManagementOperation(bucket.get(), actor_context, retry_store, update_message,
                                source -> {
                                    return new BucketActionMessage.UpdateBucketActionMessage(
                                            update_message.bucket(), status_bean.suspended(),
                                            update_message.bucket(), new HashSet<String>(Arrays.asList(source)));
                                });

                return MgmtCrudUtils.handleUpdatingStatus(bucket.get(), status_bean,
                        suspended_predicate.test(status_bean), management_results,
                        underlying_data_bucket_status_db);
            }
        });
    }

    /** Convenienence function returning whether a bucket is suspended (Based on its status bean)
     * @param status - the data bucket status bean
     * @return whether the bucket is suspended
     */
    public static boolean bucketIsSuspended(final DataBucketStatusBean status) {
        final Date now = new Date();

        final boolean is_suspended = Optional.ofNullable(status.suspended()).orElse(false)
                || (now.getTime() < Optional.ofNullable(status.quarantined_until()).orElse(now).getTime());

        return is_suspended;
    }

    /* (non-Javadoc)
     * @see com.ikanow.aleph2.data_model.interfaces.shared_services.IDataWriteService#getCrudService()
     */
    @Override
    public Optional<ICrudService<DataBucketStatusBean>> getCrudService() {
        return Optional.of(this);
    }

}