com.couchbase.client.dcp.state.SessionState.java Source code

Java tutorial

Introduction

Here is the source code for com.couchbase.client.dcp.state.SessionState.java

Source

/*
 * 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.dcp.state;

import com.couchbase.client.dcp.state.json.SessionStateDeserializer;
import com.couchbase.client.dcp.state.json.SessionStateSerializer;
import com.couchbase.client.deps.com.fasterxml.jackson.databind.ObjectMapper;
import com.couchbase.client.deps.com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.couchbase.client.deps.com.fasterxml.jackson.databind.annotation.JsonSerialize;
import rx.functions.Action1;

import java.util.Iterator;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReferenceArray;

/**
 * Holds the state information for the current session (all partitions involved).
 *
 * @author Michael Nitschinger
 * @since 1.0.0
 */
@JsonSerialize(using = SessionStateSerializer.class)
@JsonDeserialize(using = SessionStateDeserializer.class)
public class SessionState {

    /**
     * Private jackson mapper instance used for encoding and decoding into JSON for the session
     * and partition states.
     */
    private static final ObjectMapper JACKSON = new ObjectMapper();

    /**
     * Special Sequence number defined by DCP which says "no end".
     */
    public static final long NO_END_SEQNO = 0xffffffffffffffffL;

    /**
     * The current version format used on export, respected on import to aid backwards compatibility.
     */
    public static final int CURRENT_VERSION = 1;

    /**
     * The maximum number of partitions that can be stored.
     */
    private static final int MAX_PARTITIONS = 1024;

    /**
     * Contains states for each individual partition.
     */
    private final AtomicReferenceArray<PartitionState> partitionStates;

    /**
     * Initializes with an empty partition state for 1024 partitions.
     */
    public SessionState() {
        this.partitionStates = new AtomicReferenceArray<PartitionState>(MAX_PARTITIONS);
    }

    /**
     * Initializes all partition states to start at the beginning (0) with no end.
     *
     * @param numPartitions the actual number of partitions used.
     */
    public void setToBeginningWithNoEnd(final int numPartitions) {
        if (numPartitions > MAX_PARTITIONS) {
            throw new IllegalArgumentException("Can only hold " + MAX_PARTITIONS + " partitions, " + numPartitions
                    + "supplied as initializer.");
        }

        for (int i = 0; i < numPartitions; i++) {
            PartitionState partitionState = new PartitionState();
            partitionState.setEndSeqno(NO_END_SEQNO);
            partitionState.setStartSeqno(0);
            partitionState.setSnapshotStartSeqno(0);
            partitionState.setSnapshotEndSeqno(0);
            partitionStates.set(i, partitionState);
        }
    }

    /**
     * Recovers the session state from persisted JSON.
     *
     * @param persisted the persisted JSON format.
     */
    public void setFromJson(final byte[] persisted) {
        try {
            SessionState decoded = JACKSON.readValue(persisted, SessionState.class);
            decoded.foreachPartition(new Action1<PartitionState>() {
                int i = 0;

                @Override
                public void call(PartitionState dps) {
                    partitionStates.set(i++, dps);
                }
            });
        } catch (Exception ex) {
            throw new RuntimeException("Could not decode SessionState from JSON.", ex);
        }
    }

    /**
     * Accessor into the partition state, only use this if really needed.
     *
     * If you want to avoid going out of bounds, use the simpler iterator way on {@link #foreachPartition(Action1)}.
     *
     * @param partition the index of the partition.
     * @return the partition state for the given partition id.
     */
    public PartitionState get(final int partition) {
        return partitionStates.get(partition);
    }

    /**
     * Accessor to set/override the current partition state, only use this if really needed.
     *
     * @param partition the index of the partition.
     * @param partitionState the partition state to override.
     */
    public void set(int partition, PartitionState partitionState) {
        partitionStates.set(partition, partitionState);
    }

    /**
     * Check if the current sequence numbers for all partitions are equal to the ones set as end.
     *
     * @return true if all are at the end, false otherwise.
     */
    public boolean isAtEnd() {
        final AtomicBoolean atEnd = new AtomicBoolean(true);
        foreachPartition(new Action1<PartitionState>() {
            @Override
            public void call(PartitionState ps) {
                if (!ps.isAtEnd()) {
                    atEnd.set(false);
                }
            }
        });
        return atEnd.get();
    }

    /**
     * Helper method to rollback the given partition to the given sequence number.
     *
     * This will set the seqno AND REMOVE ALL ENTRIES from the failover log that are higher
     * than the given sequence number!
     *
     * @param partition the partition to rollback
     * @param seqno the sequence number where to roll it back to.
     */
    public void rollbackToPosition(short partition, long seqno) {
        PartitionState ps = partitionStates.get(partition);
        ps.setStartSeqno(seqno);
        ps.setSnapshotStartSeqno(seqno);
        ps.setSnapshotEndSeqno(seqno);
        Iterator<FailoverLogEntry> flog = ps.getFailoverLog().iterator();
        while (flog.hasNext()) {
            FailoverLogEntry entry = flog.next();
            // check if this entry is has a higher seqno than we need to roll back to
            if (entry.getSeqno() > seqno) {
                flog.remove();
            }
        }
        partitionStates.set(partition, ps);
    }

    /**
     * Provides an iterator over all partitions, calling the callback for each one.
     *
     * @param action the action to be called with the state for every partition.
     */
    public void foreachPartition(final Action1<PartitionState> action) {
        int len = partitionStates.length();
        for (int i = 0; i < len; i++) {
            PartitionState ps = partitionStates.get(i);
            if (ps == null) {
                continue;
            }
            action.call(ps);
        }
    }

    /**
     * Export the {@link PartitionState} into the desired format.
     *
     * @param format the format in which the state should be exposed, always uses the current version.
     * @return the exported format, depending on the type can be converted into a string by the user.
     */
    public byte[] export(final StateFormat format) {
        try {
            if (format == StateFormat.JSON) {
                return JACKSON.writeValueAsBytes(this);
            } else {
                throw new IllegalStateException("Unsupported Format " + format);
            }
        } catch (Exception ex) {
            throw new RuntimeException("Could not encode SessionState to Format " + format, ex);
        }
    }

    @Override
    public String toString() {
        return "SessionState[" + partitionStates + ']';
    }
}