com.cinchapi.concourse.server.storage.db.PrimaryRecord.java Source code

Java tutorial

Introduction

Here is the source code for com.cinchapi.concourse.server.storage.db.PrimaryRecord.java

Source

/*
 * Copyright (c) 2013-2016 Cinchapi 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.cinchapi.concourse.server.storage.db;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

import com.cinchapi.concourse.annotate.DoNotInvoke;
import com.cinchapi.concourse.annotate.PackagePrivate;
import com.cinchapi.concourse.server.model.PrimaryKey;
import com.cinchapi.concourse.server.model.Text;
import com.cinchapi.concourse.server.model.Value;
import com.cinchapi.concourse.server.storage.Action;
import com.cinchapi.concourse.server.storage.Versioned;
import com.cinchapi.concourse.time.Time;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * A logical grouping of data for a single entity.
 * <p>
 * This is the primary view of stored data within Concourse, similar to a Row in
 * a traditional database. PrimaryRecords are designed to efficiently handle
 * direct/non-query reads.
 * </p>
 * 
 * @author Jeff Nelson
 */
@ThreadSafe
@PackagePrivate
final class PrimaryRecord extends BrowsableRecord<PrimaryKey, Text, Value> {

    /**
     * DO NOT INVOKE. Use {@link Record#createPrimaryRecord(PrimaryKey)} or
     * {@link Record#createPrimaryRecordPartial(PrimaryKey, Text)} instead.
     * 
     * @param locator
     * @param key
     */
    @PackagePrivate
    @DoNotInvoke
    protected PrimaryRecord(PrimaryKey locator, @Nullable Text key) {
        super(locator, key);
    }

    /**
     * Return a log of revision to the entire Record.
     * 
     * @return the revision log
     */
    public Map<Long, String> audit() {
        read.lock();
        try {
            Map<Long, String> audit = Maps.newTreeMap();
            for (Entry<Text, List<CompactRevision<Value>>> entry : history.entrySet()) {
                String key = entry.getKey().toString();
                for (CompactRevision<Value> revision : entry.getValue()) {
                    audit.put(revision.getVersion(), revision.toString(locator, key));
                }
            }
            return audit;
        } finally {
            read.unlock();
        }
    }

    /**
     * Return a log of revisions to the field mapped from {@code key}.
     * 
     * @param key
     * @return the revision log
     */
    public Map<Long, String> audit(Text key) {
        read.lock();
        try {
            Map<Long, String> audit = Maps.newLinkedHashMap();
            List<CompactRevision<Value>> revisions = history.get(key); /* Authorized */
            if (revisions != null) {
                Iterator<CompactRevision<Value>> it = revisions.iterator();
                while (it.hasNext()) {
                    CompactRevision<Value> revision = it.next();
                    audit.put(revision.getVersion(), revision.toString(locator, key));
                }
            }
            return audit;
        } finally {
            read.unlock();
        }
    }

    /**
     * Return a time series of values that holds the data stored for {@code key}
     * after each modification.
     * 
     * @param key the field name
     * @param start the start timestamp (inclusive)
     * @param end the end timestamp (exclusive)
     * @return the time series of values held in the {@code key} field between
     *         {@code start} and {@code end}
     */
    public Map<Long, Set<Value>> chronologize(Text key, long start, long end) {
        read.lock();
        try {
            Map<Long, Set<Value>> context = Maps.newLinkedHashMap();
            List<CompactRevision<Value>> revisions = history.get(key);
            Set<Value> snapshot = Sets.newLinkedHashSet();
            if (revisions != null) {
                Iterator<CompactRevision<Value>> it = revisions.iterator();
                while (it.hasNext()) {
                    CompactRevision<Value> revision = it.next();
                    long timestamp = revision.getVersion();
                    if (timestamp >= end) {
                        break;
                    } else {
                        Action action = revision.getType();
                        snapshot = Sets.newLinkedHashSet(snapshot);
                        Value value = revision.getValue();
                        if (action == Action.ADD) {
                            snapshot.add(value);
                        } else if (action == Action.REMOVE) {
                            snapshot.remove(value);
                        }
                        if (timestamp >= start && !snapshot.isEmpty()) {
                            context.put(timestamp, snapshot);
                        }
                    }
                }
            }
            if (snapshot.isEmpty()) {
                // CON-474: If the last snapshot is empty, add it here so that
                // the Buffer has the proper context
                context.put(Time.NONE, snapshot);
            }
            return context;
        } finally {
            read.unlock();
        }
    }

    /**
     * Return the Set of values <em>currently</em> contained in the field mapped
     * from {@code key}.
     * 
     * @param key
     * @return the Set of contained values
     */
    public Set<Value> fetch(Text key) {
        return fetch(key, false, Versioned.NO_VERSION);
    }

    /**
     * Return the Set of values contained in the field mapped from {@code key}
     * at {@code timestamp}.
     * 
     * @param key
     * @param timestamp
     * @return the Set of contained values
     */
    public Set<Value> fetch(Text key, long timestamp) {
        return fetch(key, true, timestamp);
    }

    /**
     * Return {@code true} if the Record <em>currently</em> contains data.
     * 
     * @return {@code true} if {@link #describe()} is not an empty Set
     */
    public boolean ping() {
        return !describe().isEmpty();
    }

    /**
     * Return {@code true} if {@code value} <em>currently</em> exists in the
     * field mapped from {@code key}.
     * 
     * @param key
     * @param value
     * @return {@code true} if {@code key} as {@code value} is a valid mapping
     */
    public boolean verify(Text key, Value value) {
        return verify(key, value, false, Versioned.NO_VERSION);
    }

    /**
     * Return {@code true} if {@code value} existed in the field mapped from
     * {@code key} at {@code timestamp}
     * 
     * @param key
     * @param value
     * @param timestamp
     * @return {@code true} if {@code key} as {@code value} is a valid mapping
     */
    public boolean verify(Text key, Value value, long timestamp) {
        return verify(key, value, true, timestamp);
    }

    @Override
    protected Map<Text, Set<Value>> mapType() {
        return Maps.newHashMap();
    }

    /**
     * Return an unmodifiable view of the Set of values <em>currently</em>
     * contained in the field mapped from {@code key} or contained at
     * {@code timestamp} if {@code historical} is {@code true}.
     * 
     * @param key
     * @param historical - if {@code true}, read from the history, otherwise
     *            read from the present state
     * @param timestamp - this value is ignored if {@code historical} is set to
     *            false, otherwise this value is the historical timestamp at
     *            which to read
     * @return the Set of contained values
     */
    private Set<Value> fetch(Text key, boolean historical, long timestamp) {
        // NOTE: locking happens in super.get() methods
        return historical ? get(key, timestamp) : get(key);
    }

    /**
     * Return {@code true} if {@code value} <em>currently</em> exists in the
     * field mapped from {@code key} or existed in that field at
     * {@code timestamp} if {@code historical} is {@code true}.
     * 
     * @param key
     * @param value
     * @param historical - if {@code true}, read from the history, otherwise
     *            read from the present state
     * @param timestamp - this value is ignored if {@code historical} is set to
     *            false, otherwise this value is the historical timestamp at
     *            which to read
     * @return {@code true} if {@code key} as {@code value} is a valid mapping
     */
    private boolean verify(Text key, Value value, boolean historical, long timestamp) {
        // NOTE: locking happens in super.get() methods
        return historical ? get(key, timestamp).contains(value) : get(key).contains(value);
    }
}