/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.facebook.infrastructure.db;
import com.facebook.infrastructure.config.DatabaseDescriptor;
import com.facebook.infrastructure.utils.FBUtilities;
import org.apache.log4j.Logger;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
*/
public final class ColumnFamily
{
private static ColumnFamilySerializer serializer_;
public static final short utfPrefix_ = 2;
/* The column serializer for this Column Family. Create based on config. */
private static Logger logger_ = Logger.getLogger( ColumnFamily.class );
private static Map<String, String> columnTypes_ = new HashMap<String, String>();
private static Map<String, String> indexTypes_ = new HashMap<String, String>();
static
{
serializer_ = new ColumnFamilySerializer();
/* TODO: These are the various column types. Hard coded for now. */
columnTypes_.put("Standard", "Standard");
columnTypes_.put("Super", "Super");
indexTypes_.put("None", "None");
indexTypes_.put("Name", "Name");
indexTypes_.put("Time", "Time");
}
public static ColumnFamilySerializer serializer()
{
return serializer_;
}
/*
* This method returns the serializer whose methods are
* preprocessed by a dynamic proxy.
*/
public static ICompactSerializer2<ColumnFamily> serializerWithIndexes()
{
return (ICompactSerializer2<ColumnFamily>)Proxy.newProxyInstance( ColumnFamily.class.getClassLoader(), new Class[]{ICompactSerializer2.class}, new ColumnIndexInvocationHandler<ColumnFamily>(serializer_) );
}
public static String getColumnType(String key)
{
if ( key == null )
return columnTypes_.get("Standard");
return columnTypes_.get(key);
}
public static String getColumnIndexProperty(String columnIndexProperty)
{
if ( columnIndexProperty == null )
return indexTypes_.get("None");
return indexTypes_.get(columnIndexProperty);
}
private transient AbstractColumnFactory columnFactory_;
private String name_;
private transient ICompactSerializer2<IColumn> columnSerializer_;
private long markedForDeleteAt = Long.MIN_VALUE;
private AtomicInteger size_ = new AtomicInteger(0);
private EfficientBidiMap columns_;
private Comparator<IColumn> columnComparator_;
private Comparator<IColumn> getColumnComparator(String cfName, String columnType)
{
if(columnComparator_ == null)
{
/*
* if this columnfamily has supercolumns or there is an index on the column name,
* then sort by name
*/
if("Super".equals(columnType) || DatabaseDescriptor.isNameIndexEnabled(cfName))
{
columnComparator_ = ColumnComparatorFactory.getComparator(ColumnComparatorFactory.ComparatorType.NAME);
}
/* if this columnfamily has simple columns, and no index on name sort by timestamp */
else
{
columnComparator_ = ColumnComparatorFactory.getComparator(ColumnComparatorFactory.ComparatorType.TIMESTAMP);
}
}
return columnComparator_;
}
/* CTOR for JAXB */
private ColumnFamily()
{
}
public ColumnFamily(String cfName)
{
name_ = cfName;
createColumnFactoryAndColumnSerializer();
}
public ColumnFamily(String cfName, String columnType)
{
this(cfName);
createColumnFactoryAndColumnSerializer(columnType);
}
void createColumnFactoryAndColumnSerializer(String columnType)
{
if ( columnFactory_ == null )
{
columnFactory_ = AbstractColumnFactory.getColumnFactory(columnType);
columnSerializer_ = columnFactory_.createColumnSerializer();
if(columns_ == null)
columns_ = new EfficientBidiMap(getColumnComparator(name_, columnType));
}
}
void createColumnFactoryAndColumnSerializer()
{
String columnType = DatabaseDescriptor.getColumnFamilyType(name_);
if ( columnType == null )
{
List<String> tables = DatabaseDescriptor.getTables();
if ( tables.size() > 0 )
{
String table = tables.get(0);
columnType = Table.open(table).getColumnFamilyType(name_);
}
}
createColumnFactoryAndColumnSerializer(columnType);
}
ColumnFamily cloneMe()
{
ColumnFamily cf = new ColumnFamily(name_);
cf.markedForDeleteAt = markedForDeleteAt;
cf.columns_ = columns_.cloneMe();
return cf;
}
public String name()
{
return name_;
}
/*
* We need to go through each column
* in the column family and resolve it before adding
*/
void addColumns(ColumnFamily cf)
{
for (IColumn column : cf.getColumns().values())
{
addColumn(column);
}
}
public ICompactSerializer2<IColumn> getColumnSerializer()
{
createColumnFactoryAndColumnSerializer();
return columnSerializer_;
}
public IColumn addColumn(String name)
{
IColumn column = columnFactory_.createColumn(name);
addColumn(column);
return column;
}
// int getColumnCount()
// {
// int count = 0;
// Set<IColumn> columns = columns_.getSortedColumns();
// if( columns != null )
// {
// for(IColumn column: columns)
// {
// count = count + column.getObjectCount();
// }
// }
// return count;
// }
int getColumnCount()
{
int count = 0;
Map<String, IColumn> columns = columns_.getColumns();
if( columns != null )
{
if(!isSuper())
{
count = columns.size();
}
else
{
Collection<IColumn> values = columns.values();
for(IColumn column: values)
{
count += column.getObjectCount();
}
}
}
return count;
}
public boolean isSuper() {
return DatabaseDescriptor.getColumnType(name_).equals("Super");
}
public IColumn addColumn(String name, byte[] value)
{
return addColumn(name, value, 0);
}
public IColumn addColumn(String name, byte[] value, long timestamp)
{
return addColumn(name, value, timestamp, false);
}
public IColumn addColumn(String name, byte[] value, long timestamp, boolean deleted)
{
IColumn column = columnFactory_.createColumn(name, value, timestamp, deleted);
addColumn(column);
return column;
}
void clear()
{
columns_.clear();
}
/*
* If we find an old column that has the same name
* the ask it to resolve itself else add the new column .
*/
void addColumn(IColumn column)
{
String name = column.name();
IColumn oldColumn = columns_.get(name);
if ( oldColumn != null )
{
if (oldColumn instanceof SuperColumn) {
int oldSize = oldColumn.size();
((SuperColumn)oldColumn).integrate(column);
size_.addAndGet(oldColumn.size() - oldSize);
} else {
if (oldColumn.timestamp() <= column.timestamp()) {
columns_.put(name, column);
size_.addAndGet(column.size());
}
}
}
else
{
size_.addAndGet(column.size());
columns_.put(name, column);
}
}
public IColumn getColumn(String name)
{
return columns_.get( name );
}
public SortedSet<IColumn> getAllColumns()
{
return columns_.getSortedColumns();
}
Map<String, IColumn> getColumns()
{
return columns_.getColumns();
}
public void remove(String columnName)
{
columns_.remove(columnName);
}
void delete(long timestamp)
{
markedForDeleteAt = timestamp;
}
/* TODO this is tempting to misuse: the goal is to send a timestamp to the nodes w/ the data,
saying to delete all applicable columns. other than that it has no meaning!
Thus, this should really be part of the RowMutation message. */
public boolean isMarkedForDelete()
{
return markedForDeleteAt > Long.MIN_VALUE;
}
/*
* This is used as oldCf.merge(newCf). Basically we take the newCf
* and merge it into the oldCf.
*/
void merge(ColumnFamily columnFamily)
{
Map<String, IColumn> columns = columnFamily.getColumns();
Set<String> cNames = columns.keySet();
for ( String cName : cNames )
{
columns_.put(cName, columns.get(cName));
}
}
/*
* This function will repair a list of columns
* If there are any columns in the external list which are not present
* internally then they are added ( this might have to change depending on
* how we implement delete repairs).
* Also if there are any columns in teh internal and not in the external
* they are kept intact.
* Else the one with the greatest timestamp is considered latest.
*/
void repair(ColumnFamily columnFamily)
{
for (IColumn column : columnFamily.getAllColumns()) {
addColumn(column);
}
}
/*
* This function will calculate the differnce between 2 column families
* the external input is considered the superset of internal
* so there are no deletes in the diff.
*/
ColumnFamily diff(ColumnFamily columnFamily)
{
ColumnFamily cfDiff = new ColumnFamily(columnFamily.name());
Map<String, IColumn> columns = columnFamily.getColumns();
Set<String> cNames = columns.keySet();
for ( String cName : cNames )
{
IColumn columnInternal = columns_.get(cName);
IColumn columnExternal = columns.get(cName);
if( columnInternal == null )
{
cfDiff.addColumn(columnExternal);
}
else
{
IColumn columnDiff = columnInternal.diff(columnExternal);
if(columnDiff != null)
{
cfDiff.addColumn(columnDiff);
}
}
}
if(cfDiff.getColumns().size() != 0)
return cfDiff;
else
return null;
}
int size()
{
if ( size_.get() == 0 )
{
Set<String> cNames = columns_.getColumns().keySet();
for ( String cName : cNames )
{
size_.addAndGet(columns_.get(cName).size());
}
}
return size_.get();
}
public int hashCode()
{
return name().hashCode();
}
public boolean equals(Object o)
{
if ( !(o instanceof ColumnFamily) )
return false;
ColumnFamily cf = (ColumnFamily)o;
return name().equals(cf.name());
}
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("ColumnFamily(");
sb.append(name_);
if (isMarkedForDelete()) {
sb.append("-delete at " + getMarkedForDeleteAt() + "-");
}
sb.append(" [");
sb.append(StringUtils.join(getAllColumns(), ", "));
sb.append("])");
return sb.toString();
}
public byte[] digest()
{
Set<IColumn> columns = columns_.getSortedColumns();
byte[] xorHash = ArrayUtils.EMPTY_BYTE_ARRAY;
for(IColumn column : columns)
{
if(xorHash.length == 0)
{
xorHash = column.digest();
}
else
{
byte[] tmpHash = column.digest();
xorHash = FBUtilities.xor(xorHash, tmpHash);
}
}
return xorHash;
}
public long getMarkedForDeleteAt() {
return markedForDeleteAt;
}
}