/**
* 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 org.apache.cassandra.db;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.apache.log4j.Logger;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.filter.QueryPath;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.io.ICompactSerializer2;
import org.apache.cassandra.utils.FBUtilities;
public class ColumnFamily implements IColumnContainer
{
/* The column serializer for this Column Family. Create based on config. */
private static ColumnFamilySerializer serializer_ = new ColumnFamilySerializer();
private static Logger logger_ = Logger.getLogger( ColumnFamily.class );
private static Map<String, String> columnTypes_ = new HashMap<String, String>();
String type_;
static
{
/* TODO: These are the various column types. Hard coded for now. */
columnTypes_.put("Standard", "Standard");
columnTypes_.put("Super", "Super");
}
public static ColumnFamilySerializer serializer()
{
return serializer_;
}
public static String getColumnType(String key)
{
if ( key == null )
return columnTypes_.get("Standard");
return columnTypes_.get(key);
}
public static ColumnFamily create(String tableName, String cfName)
{
String columnType = DatabaseDescriptor.getColumnFamilyType(tableName, cfName);
AbstractType comparator = DatabaseDescriptor.getComparator(tableName, cfName);
AbstractType subcolumnComparator = DatabaseDescriptor.getSubComparator(tableName, cfName);
return new ColumnFamily(cfName, columnType, comparator, subcolumnComparator);
}
private String name_;
private transient ICompactSerializer2<IColumn> columnSerializer_;
AtomicLong markedForDeleteAt = new AtomicLong(Long.MIN_VALUE);
AtomicInteger localDeletionTime = new AtomicInteger(Integer.MIN_VALUE);
private ConcurrentSkipListMap<byte[], IColumn> columns_;
public ColumnFamily(String cfName, String columnType, AbstractType comparator, AbstractType subcolumnComparator)
{
name_ = cfName;
type_ = columnType;
columnSerializer_ = columnType.equals("Standard") ? Column.serializer() : SuperColumn.serializer(subcolumnComparator);
columns_ = new ConcurrentSkipListMap<byte[], IColumn>(comparator);
}
public ColumnFamily cloneMeShallow()
{
ColumnFamily cf = new ColumnFamily(name_, type_, getComparator(), getSubComparator());
cf.markedForDeleteAt = markedForDeleteAt;
cf.localDeletionTime = localDeletionTime;
return cf;
}
private AbstractType getSubComparator()
{
return (columnSerializer_ instanceof SuperColumnSerializer) ? ((SuperColumnSerializer)columnSerializer_).getComparator() : null;
}
public ColumnFamily cloneMe()
{
ColumnFamily cf = cloneMeShallow();
cf.columns_ = columns_.clone();
return cf;
}
public String name()
{
return name_;
}
/*
* We need to go through each column
* in the column family and resolve it before adding
*/
public void addAll(ColumnFamily cf)
{
for (IColumn column : cf.getSortedColumns())
{
addColumn(column);
}
delete(cf);
}
public ICompactSerializer2<IColumn> getColumnSerializer()
{
return columnSerializer_;
}
int getColumnCount()
{
int count = 0;
if(!isSuper())
{
count = columns_.size();
}
else
{
for(IColumn column: columns_.values())
{
count += column.getObjectCount();
}
}
return count;
}
public boolean isSuper()
{
return type_.equals("Super");
}
public void addColumn(QueryPath path, byte[] value, long timestamp)
{
addColumn(path, value, timestamp, false);
}
/** In most places the CF must be part of a QueryPath but here it is ignored. */
public void addColumn(QueryPath path, byte[] value, long timestamp, boolean deleted)
{
assert path.columnName != null : path;
IColumn column;
if (path.superColumnName == null)
{
column = new Column(path.columnName, value, timestamp, deleted);
}
else
{
assert isSuper();
column = new SuperColumn(path.superColumnName, getSubComparator());
column.addColumn(new Column(path.columnName, value, timestamp, deleted)); // checks subcolumn name
}
addColumn(column);
}
public 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 .
*/
public void addColumn(IColumn column)
{
byte[] name = column.name();
IColumn oldColumn = columns_.putIfAbsent(name, column);
if (oldColumn != null)
{
if (oldColumn instanceof SuperColumn)
{
((SuperColumn) oldColumn).putColumn(column);
}
else
{
while (((Column) oldColumn).comparePriority((Column)column) <= 0)
{
if (columns_.replace(name, oldColumn, column))
break;
oldColumn = columns_.get(name);
}
}
}
}
public IColumn getColumn(byte[] name)
{
return columns_.get(name);
}
public SortedSet<byte[]> getColumnNames()
{
return columns_.keySet();
}
public Collection<IColumn> getSortedColumns()
{
return columns_.values();
}
public Map<byte[], IColumn> getColumnsMap()
{
return columns_;
}
public void remove(byte[] columnName)
{
columns_.remove(columnName);
}
@Deprecated // TODO this is a hack to set initial value outside constructor
public void delete(int localtime, long timestamp)
{
localDeletionTime.set(localtime);
markedForDeleteAt.set(timestamp);
}
public void delete(ColumnFamily cf2)
{
FBUtilities.atomicSetMax(localDeletionTime, cf2.getLocalDeletionTime()); // do this first so we won't have a column that's "deleted" but has no local deletion time
FBUtilities.atomicSetMax(markedForDeleteAt, cf2.getMarkedForDeleteAt());
}
public boolean isMarkedForDelete()
{
return markedForDeleteAt.get() > Long.MIN_VALUE;
}
/*
* This function will calculate the difference between 2 column families.
* The external input is assumed to be a superset of internal.
*/
public ColumnFamily diff(ColumnFamily cfComposite)
{
ColumnFamily cfDiff = new ColumnFamily(cfComposite.name(), cfComposite.type_, getComparator(), getSubComparator());
if (cfComposite.getMarkedForDeleteAt() > getMarkedForDeleteAt())
{
cfDiff.delete(cfComposite.getLocalDeletionTime(), cfComposite.getMarkedForDeleteAt());
}
// (don't need to worry about cfNew containing IColumns that are shadowed by
// the delete tombstone, since cfNew was generated by CF.resolve, which
// takes care of those for us.)
Map<byte[], IColumn> columns = cfComposite.getColumnsMap();
Set<byte[]> cNames = columns.keySet();
for (byte[] 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.getColumnsMap().isEmpty() || cfDiff.isMarkedForDelete())
return cfDiff;
else
return null;
}
public AbstractType getComparator()
{
return (AbstractType)columns_.comparator();
}
int size()
{
int size = 0;
for (IColumn column : columns_.values())
{
size += column.size();
}
return size;
}
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(getComparator().getColumnsString(getSortedColumns()));
sb.append("])");
return sb.toString();
}
public static byte[] digest(ColumnFamily cf)
{
MessageDigest digest;
try
{
digest = MessageDigest.getInstance("MD5");
}
catch (NoSuchAlgorithmException e)
{
throw new AssertionError(e);
}
if (cf != null)
cf.updateDigest(digest);
return digest.digest();
}
public void updateDigest(MessageDigest digest)
{
for (IColumn column : columns_.values())
{
column.updateDigest(digest);
}
}
public long getMarkedForDeleteAt()
{
return markedForDeleteAt.get();
}
public int getLocalDeletionTime()
{
return localDeletionTime.get();
}
public String type()
{
return type_;
}
String getComparatorName()
{
return getComparator().canonicalName;
}
String getSubComparatorName()
{
AbstractType subcolumnComparator = getSubComparator();
return subcolumnComparator == null ? "" : subcolumnComparator.canonicalName;
}
public static AbstractType getComparatorFor(String table, String columnFamilyName, byte[] superColumnName)
{
return superColumnName == null
? DatabaseDescriptor.getComparator(table, columnFamilyName)
: DatabaseDescriptor.getSubComparator(table, columnFamilyName);
}
public static ColumnFamily diff(ColumnFamily cf1, ColumnFamily cf2)
{
if (cf1 == null)
return cf2;
return cf1.diff(cf2);
}
public static ColumnFamily resolve(ColumnFamily cf1, ColumnFamily cf2)
{
if (cf1 == null)
return cf2;
cf1.resolve(cf2);
return cf1;
}
public void resolve(ColumnFamily cf)
{
// Row _does_ allow null CF objects :( seems a necessary evil for efficiency
if (cf == null)
return;
addAll(cf);
}
}