/*
* Copyright 2013 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.fake;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.lilyproject.repository.api.Blob;
import org.lilyproject.repository.api.BlobAccess;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypeEntry;
import org.lilyproject.repository.api.IdGenerator;
import org.lilyproject.repository.api.IdRecord;
import org.lilyproject.repository.api.IdRecordScanner;
import org.lilyproject.repository.api.InvalidRecordException;
import org.lilyproject.repository.api.LRepository;
import org.lilyproject.repository.api.LTable;
import org.lilyproject.repository.api.MutationCondition;
import org.lilyproject.repository.api.QName;
import org.lilyproject.repository.api.Record;
import org.lilyproject.repository.api.RecordBuilder;
import org.lilyproject.repository.api.RecordException;
import org.lilyproject.repository.api.RecordExistsException;
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.RecordType;
import org.lilyproject.repository.api.Repository;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.ResponseStatus;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.Scope;
import org.lilyproject.repository.api.TableManager;
import org.lilyproject.repository.api.TypeException;
import org.lilyproject.repository.api.TypeManager;
import org.lilyproject.repository.impl.IdRecordImpl;
import org.lilyproject.repository.impl.RecordBuilderImpl;
import org.lilyproject.repository.impl.RecordImpl;
/**
* Fake Repository implementation that keeps a few records in a hashmap. No support for blobs, scanners, versions or
* scopes at the moment. We also don't do anything with mutation conditions.
*
* This is not thread safe either so thread carefully
*
*/
public class FakeLTable implements Repository {
private Map<RecordId, Record> records = new HashMap<RecordId, Record>();
private LRepository repository;
private String repositoryName;
private String tableName;
public FakeLTable(LRepository repository, String repositoryName, String tableName) {
this.repository = repository;
this.repositoryName = repositoryName;
this.tableName = tableName;
}
@Override
public Record newRecord() throws RecordException {
return new RecordImpl();
}
@Override
public Record newRecord(RecordId recordId) throws RecordException {
return new RecordImpl(recordId);
}
@Override
public Record create(Record record) throws RepositoryException, InterruptedException {
if (record.getId() == null) {
record.setId(repository.getIdGenerator().newRecordId());
}
if (records.containsKey(record.getId())) {
throw new RecordExistsException(record.getId());
}
record.setVersion(0l);
record = writeRecord(record);
return record;
}
@Override
public Record update(Record record, boolean updateVersion, boolean useLastRecordType) throws RepositoryException, InterruptedException {
if (updateVersion) {
Long version = record.getVersion();
if (version == null) {
version = 0L;
}
}
record = writeRecord(record);
return record;
}
private Record merge(Record record, Record original) {
Record result = original.clone();
if (record.getRecordTypeName() != null) {
result.setRecordType(record.getRecordTypeName());
}
// TODO merge meta map
for(Map.Entry<QName,Object> entry : record.getFields().entrySet()) {
result.setField(entry.getKey(), entry.getValue());
}
for (QName toDelete : record.getFieldsToDelete()) {
result.getFields().remove(toDelete);
}
return result;
}
private Record writeRecord(Record record) throws RepositoryException, InterruptedException{
record = record.cloneRecord();
Record originalRecord = record;
ResponseStatus status = ResponseStatus.UP_TO_DATE;
if (records.containsKey(record.getId())) {
originalRecord = records.get(record.getId());
record = merge(record, originalRecord);
if (!originalRecord.equals(record)) {
status = ResponseStatus.UPDATED;
}
} else {
status = ResponseStatus.CREATED;
}
QName recordTypeName = record.getRecordTypeName();
Long recordTypeVersion = getTypeManager().getRecordTypeByName(recordTypeName, null).getVersion();
record.setRecordType(recordTypeName, recordTypeVersion);
validateRecord(record, originalRecord, getTypeManager().getRecordTypeByName(recordTypeName, null));
Long version = record.getVersion() == null ? 0l : record.getVersion();
record.setVersion(version + 1);
records.put(record.getId(), record.cloneRecord());
record.setResponseStatus(status);
return record;
}
/* Grabbed from {@link org.lilyproject.repository.impl.HBaseRepository} */
private void validateRecord(Record record, Record originalRecord, RecordType recordType)
throws TypeException, InvalidRecordException, InterruptedException, RepositoryException {
// Check mandatory fields
Collection<FieldTypeEntry> fieldTypeEntries = recordType.getFieldTypeEntries();
List<QName> fieldsToDelete = record.getFieldsToDelete();
for (FieldTypeEntry fieldTypeEntry : fieldTypeEntries) {
if (fieldTypeEntry.isMandatory()) {
FieldType fieldType = getTypeManager().getFieldTypeById(fieldTypeEntry.getFieldTypeId());
QName fieldName = fieldType.getName();
if (fieldsToDelete.contains(fieldName)) {
throw new InvalidRecordException("Field: '" + fieldName + "' is mandatory.", record.getId());
}
if (!record.hasField(fieldName) && !originalRecord.hasField(fieldName)) {
throw new InvalidRecordException("Field: '" + fieldName + "' is mandatory.", record.getId());
}
}
}
}
@Override
public Record update(Record record) throws RepositoryException, InterruptedException {
return update(record, true, false);
}
@Override
public Record update(Record record, List<MutationCondition> mutationConditions) throws RepositoryException, InterruptedException {
return update(record, true, false);
}
@Override
public Record update(Record record, boolean updateVersion, boolean updateLastRecordType, List<MutationCondition> mutationConditions) throws RepositoryException, InterruptedException {
return update(record, updateVersion, updateLastRecordType);
}
@Override
public Record createOrUpdate(Record record) throws RepositoryException, InterruptedException {
return update(record);
}
@Override
public Record createOrUpdate(Record record, boolean b) throws RepositoryException, InterruptedException {
return update(record);
}
private Record getRecord(RecordId recordId) throws RecordNotFoundException {
Record record = records.get(recordId);
if (record == null) {
throw new RecordNotFoundException(recordId, this, repository);
}
return record.clone();
}
@Override
public Record read(RecordId recordId, List<QName> qNames) throws RepositoryException, InterruptedException {
return getRecord(recordId);
}
@Override
public Record read(RecordId recordId, QName... qNames) throws RepositoryException, InterruptedException {
return getRecord(recordId);
}
@Override
public List<Record> read(List<RecordId> recordIds, List<QName> qNames) throws RepositoryException, InterruptedException {
return read(recordIds);
}
@Override
public List<Record> read(List<RecordId> recordIds, QName... qNames) throws RepositoryException, InterruptedException {
List<Record> list = Lists.newArrayList();
for (RecordId id : recordIds) {
list.add(getRecord(id));
}
return list;
}
@Override
public Record read(RecordId recordId, Long aLong, List<QName> qNames) throws RepositoryException, InterruptedException {
return getRecord(recordId);
}
@Override
public Record read(RecordId recordId, Long aLong, QName... qNames) throws RepositoryException, InterruptedException {
return getRecord(recordId);
}
@Override
public List<Record> readVersions(RecordId recordId, Long aLong, Long aLong2, List<QName> qNames) throws RepositoryException, InterruptedException {
return Lists.newArrayList(read(recordId)); }
@Override
public List<Record> readVersions(RecordId recordId, Long aLong, Long aLong2, QName... qNames) throws RepositoryException, InterruptedException {
return Lists.newArrayList(read(recordId)); }
@Override
public List<Record> readVersions(RecordId recordId, List<Long> longs, List<QName> qNames) throws RepositoryException, InterruptedException {
return Lists.newArrayList(read(recordId)); }
@Override
public List<Record> readVersions(RecordId recordId, List<Long> longs, QName... qNames) throws RepositoryException, InterruptedException {
return Lists.newArrayList(read(recordId));
}
@Override
public IdRecord readWithIds(RecordId recordId, Long aLong, List<SchemaId> schemaIds) throws RepositoryException, InterruptedException {
Record record = getRecord(recordId);
TypeManager typeManager = this.getTypeManager();
Map<SchemaId, QName> map = Maps.newHashMap();
for (QName qname : record.getFields().keySet()) {
map.put(typeManager.getFieldTypeByName(qname).getId(), qname);
}
Map<Scope,SchemaId> recordTypeIds = Maps.newHashMap();
for (Scope scope : Scope.values()) {
RecordType recordType = typeManager.getRecordTypeByName(record.getRecordTypeName(scope), record.getVersion());
if (recordType != null) {
recordTypeIds.put(scope, recordType.getId());
}
}
IdRecord idRecord = new IdRecordImpl(record, map, recordTypeIds);
return idRecord;
}
@Override
public void delete(RecordId recordId) throws RepositoryException, InterruptedException {
records.remove(recordId);
}
@Override
public Record delete(RecordId recordId, List<MutationCondition> mutationConditions) throws RepositoryException, InterruptedException {
return records.remove(recordId);
}
@Override
public void delete(Record record) throws RepositoryException, InterruptedException {
records.remove(record.getId());
}
@Override
public OutputStream getOutputStream(Blob blob) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public BlobAccess getBlob(RecordId recordId, Long aLong, QName qName, int... ints) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public BlobAccess getBlob(RecordId recordId, Long aLong, QName qName, Integer integer, Integer integer2) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public BlobAccess getBlob(RecordId recordId, QName qName) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(RecordId recordId, Long aLong, QName qName, int... ints) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(RecordId recordId, Long aLong, QName qName, Integer integer, Integer integer2) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(RecordId recordId, QName qName) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(Record record, QName qName, int... ints) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public InputStream getInputStream(Record record, QName qName, Integer integer, Integer integer2) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public Set<RecordId> getVariants(RecordId recordId) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public RecordScanner getScanner(RecordScan recordScan) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public IdRecordScanner getScannerWithIds(RecordScan recordScan) throws RepositoryException, InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public RecordBuilder recordBuilder() throws RecordException, InterruptedException {
return new RecordBuilderImpl(this, repository.getIdGenerator());
}
@Override
public String getTableName() {
return tableName;
}
@Override
public LTable getTable(String s) throws InterruptedException, RepositoryException {
throw new UnsupportedOperationException();
}
@Override
public LTable getDefaultTable() throws InterruptedException, RepositoryException {
return repository.getDefaultTable();
}
@Override
public TableManager getTableManager() {
return repository.getTableManager();
}
@Override
public IdGenerator getIdGenerator() {
return repository.getIdGenerator();
}
@Override
public TypeManager getTypeManager() {
return repository.getTypeManager();
}
@Override
public RecordFactory getRecordFactory() {
return repository.getRecordFactory();
}
@Override
public String getRepositoryName() {
return repository.getRepositoryName();
}
@Override
public void close() throws IOException {
// nop
}
}