All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.couchbase.client.java.bucket.BucketFlusher Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 Couchbase, Inc.
 *
 * 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.couchbase.client.java.bucket;

import com.couchbase.client.core.ClusterFacade;
import com.couchbase.client.core.CouchbaseException;
import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.message.ResponseStatus;
import com.couchbase.client.core.message.config.FlushRequest;
import com.couchbase.client.core.message.config.FlushResponse;
import com.couchbase.client.core.message.kv.GetRequest;
import com.couchbase.client.core.message.kv.GetResponse;
import com.couchbase.client.core.message.kv.UpsertRequest;
import com.couchbase.client.core.message.kv.UpsertResponse;
import com.couchbase.client.deps.io.netty.buffer.Unpooled;
import com.couchbase.client.deps.io.netty.util.CharsetUtil;
import com.couchbase.client.java.error.FlushDisabledException;
import rx.Observable;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func2;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * Helper class to flush a bucket properly and wait for it to be completed.
 *
 * @author Michael Nitschinger
 * @since 2.1.1
 */
@InterfaceStability.Uncommitted
@InterfaceAudience.Private
public class BucketFlusher {

    /**
     * The number of marker documents to create, defaults to the number of partitions.
     *
     * This is important to make sure all the individual vbuckets are actually flushed.
     */
    static final int FLUSH_MARKER_SIZE = 1024;

    private static final List FLUSH_MARKERS = new ArrayList();

    static {
        for (int i = 0; i < FLUSH_MARKER_SIZE; i++) {
            FLUSH_MARKERS.add("__flush_marker_" + i);
        }
    }

    private BucketFlusher() {
    }

    /**
     * Flush the bucket and make sure flush is complete before completing the observable.
     *
     * @param core the core reference.
     * @param bucket the bucket to flush.
     * @param password the password of the bucket.
     * @return an observable which is completed once the flush process is done.
     */
    public static Observable flush(final ClusterFacade core, final String bucket, final String password) {
       return createMarkerDocuments(core, bucket)
           .flatMap(new Func1, Observable>() {
               @Override
               public Observable call(List strings) {
                   return initiateFlush(core, bucket, password);
               }
           })
           .flatMap(new Func1>() {
               @Override
               public Observable call(Boolean isDone) {
                   return isDone ? Observable.just(true) : pollMarkerDocuments(core, bucket);
               }
           });
    }

    /**
     * Helper method to create marker documents for each partition.
     *
     * @param core the core reference.
     * @param bucket the name of the bucket.
     * @return a list of created flush marker IDs once they are completely upserted.
     */
    private static Observable> createMarkerDocuments(final ClusterFacade core, final String bucket) {
        return Observable
            .from(FLUSH_MARKERS)
            .flatMap(new Func1>() {
                @Override
                public Observable call(String id) {
                    return core.send(new UpsertRequest(id, Unpooled.copiedBuffer(id, CharsetUtil.UTF_8), bucket));
                }
            })
            .doOnNext(new Action1() {
                @Override
                public void call(UpsertResponse response) {
                    if (response.content() != null && response.content().refCnt() > 0) {
                        response.content().release();
                    }
                }
            })
            .last()
            .map(new Func1>() {
                @Override
                public List call(UpsertResponse response) {
                    return FLUSH_MARKERS;
                }
            });
    }

    /**
     * Initiates a flush request against the server.
     *
     * The result indicates if polling needs to be done or the flush is already complete. It can also fail in case
     * flush is disabled or something else went wrong in the server response.
     *
     * @param core the core reference.
     * @param bucket the bucket to flush.
     * @param password the password of the bucket.
     * @return an observable indicating if done (true) or polling needs to happen (false).
     */
    private static Observable initiateFlush(final ClusterFacade core, final String bucket, final String password) {
        return core
            .send(new FlushRequest(bucket, password))
            .map(new Func1() {
                @Override
                public Boolean call(FlushResponse flushResponse) {
                    if (!flushResponse.status().isSuccess()) {
                        if (flushResponse.content().contains("disabled")) {
                            throw new FlushDisabledException("Flush is disabled for this bucket.");
                        } else {
                            throw new CouchbaseException("Flush failed because of: " + flushResponse.content());
                        }
                    }
                    return flushResponse.isDone();
                }
            });
    }

    /**
     * Helper method to poll the list of marker documents until all of them are gone.
     *
     * @param core the core reference.
     * @param bucket the name of the bucket.
     * @return an observable completing when all marker documents are gone.
     */
    private static Observable pollMarkerDocuments(final ClusterFacade core, final String bucket) {
        return Observable
            .from(FLUSH_MARKERS)
            .flatMap(new Func1>() {
                @Override
                public Observable call(String id) {
                    return core.send(new GetRequest(id, bucket));
                }
            })
            .reduce(0, new Func2() {
                @Override
                public Integer call(Integer foundDocs, GetResponse response) {
                    if (response.content() != null && response.content().refCnt() > 0) {
                        response.content().release();
                    }
                    if (response.status() == ResponseStatus.SUCCESS) {
                        foundDocs++;
                    }
                    return foundDocs;
                }
            })
            .filter(new Func1() {
                @Override
                public Boolean call(Integer foundDocs) {
                    return foundDocs == 0;
                }
            })
            .repeatWhen(new Func1, Observable>() {
                @Override
                public Observable call(Observable observable) {
                    return observable.flatMap(new Func1>() {
                        @Override
                        public Observable call(Void aVoid) {
                            return Observable.timer(500, TimeUnit.MILLISECONDS);
                        }
                    });
                }
            })
            .take(1)
            .map(new Func1() {
                @Override
                public Boolean call(Integer integer) {
                    return true;
                }
            });
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy