/*
* Copyright 2010 Outerthought bvba
*
* 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.util.repo;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.lilyproject.repository.api.FieldType;
import org.lilyproject.repository.api.FieldTypeNotFoundException;
import org.lilyproject.repository.api.IdRecord;
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.RecordId;
import org.lilyproject.repository.api.RepositoryException;
import org.lilyproject.repository.api.SchemaId;
import org.lilyproject.repository.api.Scope;
import org.lilyproject.repository.api.TypeManager;
/**
* Version tag related utilities.
*/
public class VersionTag {
/**
* Namespace for field types that serve as version tags.
*/
public static final String NAMESPACE = "org.lilyproject.vtag";
/**
* Name for the field type that serves as last version tag.
*/
public static final QName LAST = new QName(NAMESPACE, "last");
private VersionTag() {
}
public static QName qname(String vtag) {
return new QName(NAMESPACE, vtag);
}
/**
* Returns true if the given FieldType is a version tag.
*/
public static boolean isVersionTag(FieldType fieldType) {
String namespace = fieldType.getName().getNamespace();
return (fieldType.getScope() == Scope.NON_VERSIONED
&& fieldType.getValueType().getBaseName().equals("LONG")
&& namespace != null && namespace.equals(NAMESPACE) /* namespace is typically the longest string,
therefore compare it last */
&& !fieldType.getName().getName().equals("last")); /* filter out 'last' vtag, it should not be
custom assigned */
}
/**
* Filters the given set of fields to only those that are vtag fields.
*/
public static Set<SchemaId> filterVTagFields(Set<SchemaId> fieldIds, TypeManager typeManager)
throws RepositoryException, InterruptedException {
Set<SchemaId> result = new HashSet<SchemaId>();
for (SchemaId field : fieldIds) {
try {
if (isVersionTag(typeManager.getFieldTypeById(field))) {
result.add(field);
}
} catch (FieldTypeNotFoundException e) {
// ignore, if it does not exist, it can't be a version tag
}
}
return result;
}
/**
* Get an IdRecord of the given vtag version, based on a recordId.
*/
public static IdRecord getIdRecord(RecordId recordId, SchemaId vtagId, LTable table, LRepository repository)
throws RepositoryException, InterruptedException {
VTaggedRecord vtRecord = new VTaggedRecord(recordId, null, table, repository);
return vtRecord.getIdRecord(vtagId);
}
/**
* Get an IdRecord of the given vtag version, based on a existing IdRecord. The existing IdRecord should be the
* last (when it was read) and should have been read with all fields!
*/
public static IdRecord getIdRecord(IdRecord idRecord, SchemaId vtagId, LTable table, LRepository repository)
throws RepositoryException, InterruptedException {
VTaggedRecord vtRecord = new VTaggedRecord(idRecord, null, table, repository);
return vtRecord.getIdRecord(vtagId);
}
/**
* Returns null if the vtag does not exist or is not defined for the record.
*/
public static Record getRecord(RecordId recordId, String vtag, List<QName> fields,
LTable table, LRepository repository) throws RepositoryException, InterruptedException {
QName vtagName = new QName(NAMESPACE, vtag);
Record record = table.read(recordId);
long version;
if (vtag.equals("last")) {
// we loaded the last version
if (fields != null) {
filterFields(record, new HashSet<QName>(fields));
}
return record;
} else if (!record.hasField(vtagName)) {
return null;
} else {
version = (Long) record.getField(vtagName);
if (version == 0) {
reduceToNonVersioned(record, fields != null ? new HashSet<QName>(fields) : null,
repository.getTypeManager());
} else {
record = table.read(recordId, version, fields);
}
return record;
}
}
/**
* Removes any versioned information from the supplied record object.
*/
public static void reduceToNonVersioned(Record record, Set<QName> fields, TypeManager typeManager)
throws RepositoryException, InterruptedException {
if (record.getVersion() == null) {
// The record has no versions so there should be no versioned fields in it
return;
}
Iterator<Map.Entry<QName, Object>> fieldsIt = record.getFields().entrySet().iterator();
while (fieldsIt.hasNext()) {
Map.Entry<QName, Object> entry = fieldsIt.next();
if (fields != null && !fields.contains(entry.getKey())) {
fieldsIt.remove();
} else if (typeManager.getFieldTypeByName(entry.getKey()).getScope() != Scope.NON_VERSIONED) {
fieldsIt.remove();
}
}
// Remove versioned record type info
record.setRecordType(Scope.VERSIONED, (QName) null, null);
record.setRecordType(Scope.VERSIONED_MUTABLE, (QName) null, null);
}
private static void filterFields(Record record, Set<QName> fields) {
Iterator<Map.Entry<QName, Object>> fieldsIt = record.getFields().entrySet().iterator();
while (fieldsIt.hasNext()) {
Map.Entry<QName, Object> entry = fieldsIt.next();
if (!fields.contains(entry.getKey())) {
fieldsIt.remove();
}
}
}
}