Package org.lilyproject.repository.impl

Source Code of org.lilyproject.repository.impl.BaseRepository

/*
* Copyright 2012 NGDATA nv
*
* 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 org.lilyproject.repository.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;

import com.google.common.base.Preconditions;
import org.apache.hadoop.hbase.client.Get;
import org.apache.hadoop.hbase.client.HTableInterface;
import org.apache.hadoop.hbase.client.Result;
import org.apache.hadoop.hbase.client.ResultScanner;
import org.apache.hadoop.hbase.client.Scan;
import org.apache.hadoop.hbase.filter.CompareFilter;
import org.apache.hadoop.hbase.filter.Filter;
import org.apache.hadoop.hbase.filter.FilterList;
import org.apache.hadoop.hbase.filter.SingleColumnValueFilter;
import org.apache.hadoop.hbase.util.Bytes;
import org.lilyproject.repository.api.Blob;
import org.lilyproject.repository.api.BlobAccess;
import org.lilyproject.repository.api.BlobException;
import org.lilyproject.repository.api.BlobManager;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypes;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.IdRecord;
import org.lilyproject.repository.api.IdRecordScanner;
import org.lilyproject.repository.api.LRepository;
import org.lilyproject.repository.api.LTable;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordException;
import org.lilyproject.repository.api.RecordFactory;
import org.lilyproject.repository.api.RecordId;
import org.lilyproject.repository.api.RecordNotFoundException;
import org.lilyproject.repository.api.RecordScan;
import org.lilyproject.repository.api.RecordScanner;
import org.lilyproject.repository.api.Repository;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.TableManager;
import org.lilyproject.repository.api.ReturnFields;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.TypeException;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.api.VersionNotFoundException;
import org.lilyproject.repository.api.filter.RecordFilter;
import org.lilyproject.repository.impl.RepositoryMetrics.Action;
import org.lilyproject.repository.spi.HBaseRecordFilterFactory;
import org.lilyproject.util.ArgumentValidator;
import org.lilyproject.util.Pair;
import org.lilyproject.util.hbase.LilyHBaseSchema;
import org.lilyproject.util.hbase.LilyHBaseSchema.RecordCf;
import org.lilyproject.util.hbase.LilyHBaseSchema.RecordColumn;
import org.lilyproject.util.hbase.RepoAndTableUtil;

public abstract class BaseRepository implements Repository {
    protected final AbstractRepositoryManager repositoryManager;
    protected final TypeManager typeManager;
    protected final IdGenerator idGenerator;
    protected final BlobManager blobManager;
    protected final RecordFactory recordFactory;
    protected final RecordDecoder recdec;
    protected final HTableInterface recordTable;
    protected final HTableInterface nonAuthRecordTable;
    protected final RepoTableKey repoTableKey;
    protected final TableManager tableManager;
    protected RepositoryMetrics metrics;

    /**
     * Not all rows in the HBase record table are real records, this filter excludes non-valid
     * record rows.
     */
    protected static final SingleColumnValueFilter REAL_RECORDS_FILTER;

    static {
        // A record is a real row iff the deleted flag exists and is not true.
        // It is possible for the delete flag not to exist on a row: this is
        // in case a lock was taken on a not-yet-existing row and the record
        // creation failed. Therefore, the filterIfMissing is important.
        REAL_RECORDS_FILTER = new SingleColumnValueFilter(RecordCf.DATA.bytes,
                RecordColumn.DELETED.bytes, CompareFilter.CompareOp.NOT_EQUAL, Bytes.toBytes(true));
        REAL_RECORDS_FILTER.setFilterIfMissing(true);
    }

    protected BaseRepository(RepoTableKey repoTableKey, AbstractRepositoryManager repositoryManager,
            BlobManager blobManager, HTableInterface recordTable, HTableInterface nonAuthRecordTable,
            RepositoryMetrics metrics, TableManager tableManager, RecordFactory recordFactory) {

        Preconditions.checkNotNull(repositoryManager, "repositoryManager cannot be null");
        Preconditions.checkNotNull(blobManager, "blobManager cannot be null");
        Preconditions.checkNotNull(recordTable, "recordTable cannot be null");

        this.repoTableKey = repoTableKey;
        this.repositoryManager = repositoryManager;
        this.typeManager = repositoryManager.getTypeManager();
        this.blobManager = blobManager;
        this.idGenerator = repositoryManager.getIdGenerator();
        this.recordTable = recordTable;
        this.nonAuthRecordTable = nonAuthRecordTable;
        this.recdec = new RecordDecoder(typeManager, idGenerator, new RecordFactoryImpl());
        this.metrics = metrics;
        this.tableManager = tableManager;
        this.recordFactory = recordFactory;
    }

    @Override
    public TableManager getTableManager() {
        return tableManager;
    }

    @Override
    public RecordFactory getRecordFactory() {
        return recordFactory;
    }

    @Override
    public LTable getTable(String tableName) throws InterruptedException, RepositoryException {
        if (! RepoAndTableUtil.isValidTableName(tableName))
            throw new IllegalArgumentException("Not a valid table name: " + tableName);
        return repositoryManager.getRepository(repoTableKey.getRepositoryName(), tableName);
    }

    @Override
    public LTable getDefaultTable() throws InterruptedException, RepositoryException {
        return getTable(LilyHBaseSchema.Table.RECORD.name);
    }

    @Override
    public TypeManager getTypeManager() {
        return typeManager;
    }

    @Override
    public OutputStream getOutputStream(Blob blob) throws BlobException {
        return blobManager.getOutputStream(blob);
    }

    @Override
    public InputStream getInputStream(RecordId recordId, Long version, QName fieldName, int... indexes)
            throws RepositoryException, InterruptedException {
        Record record = read(recordId, version, fieldName);
        return getInputStream(record, fieldName, indexes);
    }

    @Override
    public InputStream getInputStream(RecordId recordId, QName fieldName)
            throws RepositoryException, InterruptedException {
        return getInputStream(recordId, null, fieldName);
    }

    @Override
    public InputStream getInputStream(Record record, QName fieldName, int... indexes)
            throws RepositoryException, InterruptedException {
        FieldType fieldType = typeManager.getFieldTypeByName(fieldName);
        return blobManager.getBlobAccess(record, fieldName, fieldType, indexes).getInputStream();
    }

    @Override
    public BlobAccess getBlob(RecordId recordId, Long version, QName fieldName, int... indexes)
            throws RepositoryException, InterruptedException {
        Record record = read(recordId, version, fieldName);
        FieldType fieldType = typeManager.getFieldTypeByName(fieldName);
        return blobManager.getBlobAccess(record, fieldName, fieldType, indexes);
    }

    @Override
    public BlobAccess getBlob(RecordId recordId, Long version, QName fieldName, Integer mvIndex, Integer hIndex)
            throws RepositoryException, InterruptedException {
        return getBlob(recordId, version, fieldName, convertToIndexes(mvIndex, hIndex));
    }

    @Override
    public BlobAccess getBlob(RecordId recordId, QName fieldName) throws RepositoryException, InterruptedException {
        return getBlob(recordId, null, fieldName);
    }

    @Override
    public InputStream getInputStream(RecordId recordId, Long version, QName fieldName, Integer mvIndex, Integer hIndex)
            throws RepositoryException, InterruptedException {
        return getInputStream(recordId, version, fieldName, convertToIndexes(mvIndex, hIndex));
    }

    @Override
    public InputStream getInputStream(Record record, QName fieldName, Integer mvIndex, Integer hIndex)
            throws RepositoryException, InterruptedException {
        return getInputStream(record, fieldName, convertToIndexes(mvIndex, hIndex));
    }

    private int[] convertToIndexes(Integer mvIndex, Integer hIndex) {
        int[] indexes;
        if (mvIndex == null && hIndex == null) {
            indexes = new int[0];
        } else if (mvIndex == null) {
            indexes = new int[]{hIndex};
        } else if (hIndex == null) {
            indexes = new int[]{mvIndex};
        } else {
            indexes = new int[]{mvIndex, hIndex};
        }

        return indexes;
    }

    @Override
    public RecordScanner getScanner(RecordScan scan) throws RepositoryException, InterruptedException {
        return new HBaseRecordScannerImpl(createHBaseResultScanner(scan), recdec);
    }

    @Override
    public IdRecordScanner getScannerWithIds(RecordScan scan) throws RepositoryException, InterruptedException {
        return new HBaseIdRecordScannerImpl(createHBaseResultScanner(scan), recdec);
    }

    private ResultScanner createHBaseResultScanner(RecordScan scan) throws RepositoryException, InterruptedException {
        Scan hbaseScan = new Scan();

        hbaseScan.setMaxVersions(1);

        if (scan.getRawStartRecordId() != null) {
            hbaseScan.setStartRow(scan.getRawStartRecordId());
        } else if (scan.getStartRecordId() != null) {
            hbaseScan.setStartRow(scan.getStartRecordId().toBytes());
        }

        if (scan.getRawStopRecordId() != null) {
            hbaseScan.setStopRow(scan.getRawStopRecordId());
        } else if (scan.getStopRecordId() != null) {
            hbaseScan.setStopRow(scan.getStopRecordId().toBytes());
        }

        // Filters
        FilterList filterList = new FilterList(FilterList.Operator.MUST_PASS_ALL);

        // filter out deleted records
        filterList.addFilter(REAL_RECORDS_FILTER);

        // add user's filter
        if (scan.getRecordFilter() != null) {
            Filter filter = filterFactory.createHBaseFilter(scan.getRecordFilter(), this, filterFactory);
            filterList.addFilter(filter);
        }

        hbaseScan.setFilter(filterList);

        hbaseScan.setCaching(scan.getCaching());

        hbaseScan.setCacheBlocks(scan.getCacheBlocks());

        ReturnFields returnFields = scan.getReturnFields();
        if (returnFields != null && returnFields.getType() != ReturnFields.Type.ALL) {
            RecordDecoder.addSystemColumnsToScan(hbaseScan);
            switch (returnFields.getType()) {
                case ENUM:
                    for (QName field : returnFields.getFields()) {
                        FieldTypeImpl fieldType = (FieldTypeImpl) typeManager.getFieldTypeByName(field);
                        hbaseScan.addColumn(RecordCf.DATA.bytes, fieldType.getQualifier());
                    }
                    break;
                case NONE:
                    // nothing to add
                    break;
                default:
                    throw new RuntimeException("Unrecognized ReturnFields type: " + returnFields.getType());
            }
        } else {
            hbaseScan.addFamily(RecordCf.DATA.bytes);
        }

        ResultScanner hbaseScanner;
        try {
            hbaseScanner = recordTable.getScanner(hbaseScan);
        } catch (IOException e) {
            throw new RecordException("Error creating scanner", e);
        }
        return hbaseScanner;
    }

    private static final List<HBaseRecordFilterFactory> FILTER_FACTORIES;
    static {
        List<HBaseRecordFilterFactory> filterFactories = new ArrayList<HBaseRecordFilterFactory>();
        // Make our own copy of list of filter factories, because it is not thread-safe to iterate over filterLoader
        ServiceLoader<HBaseRecordFilterFactory> filterLoader =
                ServiceLoader.load(HBaseRecordFilterFactory.class, BaseRepository.class.getClassLoader());
        for (HBaseRecordFilterFactory filterFactory : filterLoader) {
            filterFactories.add(filterFactory);
        }
        FILTER_FACTORIES = Collections.unmodifiableList(filterFactories);
    }

    private HBaseRecordFilterFactory filterFactory = new HBaseRecordFilterFactory() {
        @Override
        public Filter createHBaseFilter(RecordFilter filter, LRepository repository, HBaseRecordFilterFactory factory)
                throws RepositoryException, InterruptedException {
            for (HBaseRecordFilterFactory filterFactory : FILTER_FACTORIES) {
                Filter hbaseFilter = filterFactory.createHBaseFilter(filter, repository, factory);
                if (hbaseFilter != null) {
                    return hbaseFilter;
                }
            }
            throw new RepositoryException("No implementation available for filter type " + filter.getClass().getName());
        }
    };

    /* READING */
    @Override
    public Record read(RecordId recordId, List<QName> fieldNames) throws RepositoryException, InterruptedException {
        return read(recordId, null, fieldNames == null ? null : fieldNames.toArray(new QName[fieldNames.size()]));
    }

    @Override
    public Record read(RecordId recordId, QName... fieldNames) throws RepositoryException, InterruptedException {
        return read(recordId, null, fieldNames);
    }

    @Override
    public List<Record> read(List<RecordId> recordIds, List<QName> fieldNames)
            throws RepositoryException, InterruptedException {
        return read(recordIds, fieldNames == null ? null : fieldNames.toArray(new QName[fieldNames.size()]));
    }

    @Override
    public List<Record> read(List<RecordId> recordIds, QName... fieldNames)
            throws RepositoryException, InterruptedException {
        FieldTypes fieldTypes = typeManager.getFieldTypesSnapshot();
        List<FieldType> fields = getFieldTypesFromNames(fieldTypes, fieldNames);

        return read(recordIds, fields, fieldTypes);
    }

    @Override
    public Record read(RecordId recordId, Long version, List<QName> fieldNames)
            throws RepositoryException, InterruptedException {
        return read(recordId, version, fieldNames == null ? null : fieldNames.toArray(new QName[fieldNames.size()]));
    }

    @Override
    public Record read(RecordId recordId, Long version, QName... fieldNames)
            throws RepositoryException, InterruptedException {
        FieldTypes fieldTypes = typeManager.getFieldTypesSnapshot();
        List<FieldType> fields = getFieldTypesFromNames(fieldTypes, fieldNames);

        return read(recordId, version, fields, fieldTypes);
    }

    @Override
    public IdRecord readWithIds(RecordId recordId, Long version, List<SchemaId> fieldIds)
            throws RepositoryException, InterruptedException {
        FieldTypes fieldTypes = typeManager.getFieldTypesSnapshot();
        List<FieldType> fields = getFieldTypesFromIds(fieldIds, fieldTypes);

        return readWithIds(recordId, version, fields, fieldTypes);
    }

    private IdRecord readWithIds(RecordId recordId, Long requestedVersion, List<FieldType> fields,
                                 FieldTypes fieldTypes) throws RepositoryException, InterruptedException {
        long before = System.currentTimeMillis();
        try {
            ArgumentValidator.notNull(recordId, "recordId");

            Result result = getRow(recordId, requestedVersion, 1, fields);

            Long latestVersion = recdec.getLatestVersion(result);
            if (requestedVersion == null) {
                // Latest version can still be null if there are only non-versioned fields in the record
                requestedVersion = latestVersion;
            } else {
                if (latestVersion == null || latestVersion < requestedVersion) {
                    // The requested version is higher than the highest existing version
                    throw new VersionNotFoundException(recordId, requestedVersion);
                }
            }
            return recdec.decodeRecordWithIds(recordId, requestedVersion, result, fieldTypes);
        } finally {
            if (metrics != null) {
                metrics.report(Action.READ, System.currentTimeMillis() - before);
            }
        }
    }

    private List<FieldType> getFieldTypesFromIds(List<SchemaId> fieldIds, FieldTypes fieldTypes)
            throws TypeException, InterruptedException {
        List<FieldType> fields = null;
        if (fieldIds != null) {
            fields = new ArrayList<FieldType>(fieldIds.size());
            for (SchemaId fieldId : fieldIds) {
                fields.add(fieldTypes.getFieldType(fieldId));
            }
        }
        return fields;
    }

    protected List<FieldType> getFieldTypesFromNames(FieldTypes fieldTypes, QName... fieldNames)
            throws TypeException, InterruptedException {
        List<FieldType> fields = null;
        if (fieldNames != null) {
            fields = new ArrayList<FieldType>();
            for (QName fieldName : fieldNames) {
                fields.add(fieldTypes.getFieldType(fieldName));
            }
        }
        return fields;
    }

    protected Record read(RecordId recordId, Long requestedVersion, List<FieldType> fields, FieldTypes fieldTypes)
            throws RepositoryException, InterruptedException {
        return readWithOcc(recordId, requestedVersion, fields, fieldTypes).getV1();
    }

    /**
     * Returns both the record and its occ (optimistic concurrency control) version bytes.
     * <p>
     * Note, the occ bytes can be null if the record being read is from a version of Lily before OCC was used.
     */
    protected Pair<Record, byte[]> readWithOcc(RecordId recordId, Long requestedVersion, List<FieldType> fields,
            FieldTypes fieldTypes) throws RepositoryException, InterruptedException {
        return readWithOcc(recordId, requestedVersion, fields, fieldTypes, false);
    }

    protected Pair<Record, byte[]> readWithOcc(RecordId recordId, Long requestedVersion, List<FieldType> fields,
            FieldTypes fieldTypes, boolean disableAuth) throws RepositoryException, InterruptedException {
        long before = System.currentTimeMillis();
        try {
            ArgumentValidator.notNull(recordId, "recordId");

            Result result = getRow(recordId, requestedVersion, 1, fields, disableAuth);

            Long latestVersion = recdec.getLatestVersion(result);
            if (requestedVersion == null) {
                // Latest version can still be null if there are only non-versioned fields in the record
                requestedVersion = latestVersion;
            } else {
                if (latestVersion == null || latestVersion < requestedVersion) {
                    // The requested version is higher than the highest existing version
                    throw new VersionNotFoundException(recordId, requestedVersion);
                }
            }

            byte[] occBytes = result.getValue(RecordCf.DATA.bytes, RecordColumn.OCC.bytes);
            return new Pair<Record, byte[]>(recdec.decodeRecord(recordId, requestedVersion, null, result, fieldTypes), occBytes);
        } finally {
            if (metrics != null) {
                metrics.report(Action.READ, System.currentTimeMillis() - before);
            }
        }
    }

    private List<Record> read(List<RecordId> recordIds, List<FieldType> fields, FieldTypes fieldTypes)
            throws RepositoryException, InterruptedException {
        long before = System.currentTimeMillis();
        try {
            ArgumentValidator.notNull(recordIds, "recordIds");
            List<Record> records = new ArrayList<Record>();
            if (recordIds.isEmpty()) {
                return records;
            }

            Map<RecordId, Result> results = getRows(recordIds, fields);

            for (RecordId recordId : recordIds) {
                Result result = results.get(recordId);
                if (result != null){
                    Long version = recdec.getLatestVersion(result);
                    records.add(recdec.decodeRecord(recordId, version, null, result, fieldTypes));
                }
            }
            return records;
        } finally {
            if (metrics != null) {
                metrics.report(Action.READ, System.currentTimeMillis() - before);
            }
        }
    }

    // Retrieves the row from the table and check if it exists and has not been flagged as deleted
    protected Result getRow(RecordId recordId, Long version, int numberOfVersions, List<FieldType> fields)
            throws RecordException {
        return getRow(recordId, version, numberOfVersions, fields, false);
    }

    protected Result getRow(RecordId recordId, Long version, int numberOfVersions, List<FieldType> fields,
            boolean disableAuth) throws RecordException {
        Result result;
        Get get = new Get(recordId.toBytes());
        get.setFilter(REAL_RECORDS_FILTER);

        try {
            // Add the columns for the fields to get
            addFieldsToGet(get, fields);

            if (version != null) {
                get.setTimeRange(0, version + 1); // Only retrieve data within this timerange
            }
            get.setMaxVersions(numberOfVersions);

            // Retrieve the data from the repository
            if (disableAuth) {
                result = nonAuthRecordTable.get(get);
            } else {
                result = recordTable.get(get);
            }

            if (result == null || result.isEmpty()) {
                throw new RecordNotFoundException(recordId, this, this);
            }

        } catch (IOException e) {
            throw new RecordException("Exception occurred while retrieving record '" + recordId
                    + "' from HBase table", e);
        }
        return result;
    }

    private void addFieldsToGet(Get get, List<FieldType> fields) {
        if (fields != null && (!fields.isEmpty())) {
            for (FieldType field : fields) {
                get.addColumn(RecordCf.DATA.bytes, ((FieldTypeImpl) field).getQualifier());
            }
            RecordDecoder.addSystemColumnsToGet(get);
        } else {
            // Retrieve everything
            get.addFamily(RecordCf.DATA.bytes);
        }
    }

    // Retrieves the row from the table and check if it exists and has not been flagged as deleted
    protected Map<RecordId, Result> getRows(List<RecordId> recordIds, List<FieldType> fields)
            throws RecordException {
        Map<RecordId, Result> results = new HashMap<RecordId, Result>();

        try {
            List<Get> gets = new ArrayList<Get>();
            for (RecordId recordId : recordIds) {
                Get get = new Get(recordId.toBytes());
                // Add the columns for the fields to get
                addFieldsToGet(get, fields);
                get.setMaxVersions(1); // Only retrieve the most recent version of each field
                gets.add(get);
            }

            // Retrieve the data from the repository
            int i = 0;
            for (Result result : recordTable.get(gets)) {
                if (result == null || result.isEmpty()) {
                    i++; // Skip this recordId (instead of throwing a RecordNotFoundException)
                    continue;
                }
                // Check if the record was deleted
                byte[] deleted = recdec.getLatest(result, RecordCf.DATA.bytes, RecordColumn.DELETED.bytes);
                if ((deleted == null) || (Bytes.toBoolean(deleted))) {
                    i++; // Skip this recordId (instead of throwing a RecordNotFoundException)
                    continue;
                }
                results.put(recordIds.get(i++), result);
            }
        } catch (IOException e) {
            throw new RecordException("Exception occurred while retrieving records '" + recordIds
                    + "' from HBase table", e);
        }
        return results;
    }

    @Override
    public List<Record> readVersions(RecordId recordId, Long fromVersion, Long toVersion, List<QName> fieldNames)
            throws RepositoryException, InterruptedException {
        return readVersions(recordId, fromVersion, toVersion,
                fieldNames == null ? null : fieldNames.toArray(new QName[fieldNames.size()]));
    }

    @Override
    public List<Record> readVersions(RecordId recordId, Long fromVersion, Long toVersion, QName... fieldNames)
            throws RepositoryException, InterruptedException {
        ArgumentValidator.notNull(recordId, "recordId");
        ArgumentValidator.notNull(fromVersion, "fromVersion");
        ArgumentValidator.notNull(toVersion, "toVersion");
        if (fromVersion > toVersion) {
            throw new IllegalArgumentException("fromVersion '" + fromVersion +
                    "' must be smaller or equal to toVersion '" + toVersion + "'");
        }

        FieldTypes fieldTypes = typeManager.getFieldTypesSnapshot();
        List<FieldType> fields = getFieldTypesFromNames(fieldTypes, fieldNames);

        int numberOfVersionsToRetrieve = (int) (toVersion - fromVersion + 1);
        Result result = getRow(recordId, toVersion, numberOfVersionsToRetrieve, fields);
        if (fromVersion < 1L) {
            fromVersion = 1L; // Put the fromVersion to a sensible value
        }
        List<Long> versionsToRead = new ArrayList<Long>();
        Long latestVersion = recdec.getLatestVersion(result);
        if (latestVersion != null) {
            if (latestVersion < toVersion) {
                toVersion = latestVersion; // Limit the toVersion to the highest possible version
            }

            for (long version = fromVersion; version <= toVersion; version++) {
                versionsToRead.add(version);
            }
        }
        return recdec.decodeRecords(recordId, versionsToRead, result, fieldTypes);
    }

    @Override
    public List<Record> readVersions(RecordId recordId, List<Long> versions, List<QName> fieldNames)
            throws RepositoryException, InterruptedException {
        return readVersions(recordId, versions,
                fieldNames == null ? null : fieldNames.toArray(new QName[fieldNames.size()]));
    }

    @Override
    public List<Record> readVersions(RecordId recordId, List<Long> versions, QName... fieldNames)
            throws RepositoryException, InterruptedException {
        ArgumentValidator.notNull(recordId, "recordId");
        ArgumentValidator.notNull(versions, "versions");

        if (versions.isEmpty()) {
            return new ArrayList<Record>();
        }

        Collections.sort(versions);

        FieldTypes fieldTypes = typeManager.getFieldTypesSnapshot();
        List<FieldType> fields = getFieldTypesFromNames(fieldTypes, fieldNames);

        Long lowestRequestedVersion = versions.get(0);
        Long highestRequestedVersion = versions.get(versions.size() - 1);
        int numberOfVersionsToRetrieve = (int) (highestRequestedVersion - lowestRequestedVersion + 1);
        Result result = getRow(recordId, highestRequestedVersion, numberOfVersionsToRetrieve, fields);
        Long latestVersion = recdec.getLatestVersion(result);

        // Drop the versions that are higher than the latestVersion
        List<Long> validVersions = new ArrayList<Long>();
        for (Long version : versions) {
            if (version > latestVersion) {
                break;
            }
            validVersions.add(version);
        }
        return recdec.decodeRecords(recordId, validVersions, result, fieldTypes);
    }

    @Override
    public Record newRecord() throws RecordException {
        return recordFactory.newRecord();
    }

    @Override
    public Record newRecord(RecordId recordId) throws RecordException {
        return recordFactory.newRecord(recordId);
    }

    @Override
    public String getTableName() {
        return repoTableKey.getTableName();
    }

    @Override
    public String getRepositoryName() {
        return repoTableKey.getRepositoryName();
    }
}
TOP

Related Classes of org.lilyproject.repository.impl.BaseRepository

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.