/**
* 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.io.ICompactSerializer;
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.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Author : Avinash Lakshman ( alakshman@facebook.com) & Prashant Malik ( pmalik@facebook.com )
*/
public class Row
{
private static RowSerializer serializer_ = new RowSerializer();
private static Logger logger_ = Logger.getLogger(Row.class);
static RowSerializer serializer()
{
return serializer_;
}
private String key_;
private Map<String, ColumnFamily> columnFamilies_ = new Hashtable<String, ColumnFamily>();
protected Row()
{
}
public Row(String key)
{
key_ = key;
}
public String key()
{
return key_;
}
void key(String key)
{
key_ = key;
}
public Set<String> getColumnFamilyNames() {
return columnFamilies_.keySet();
}
public Collection<ColumnFamily> getColumnFamilies() {
return columnFamilies_.values();
}
@Deprecated // (use getColumnFamilies or getColumnFamilyNames)
public Map<String, ColumnFamily> getColumnFamilyMap()
{
return columnFamilies_;
}
public ColumnFamily getColumnFamily(String name) {
return columnFamilies_.get(name);
}
void addColumnFamily(ColumnFamily columnFamily)
{
columnFamilies_.put(columnFamily.name(), columnFamily);
}
void removeColumnFamily(ColumnFamily columnFamily)
{
columnFamilies_.remove(columnFamily.name());
int delta = (-1) * columnFamily.size();
}
public boolean isEmpty()
{
return ( columnFamilies_.size() == 0 );
}
/*
* This function will repair the current row with the input row
* what that means is that if there are any differences between the 2 rows then
* this fn will make the current row take the latest changes .
*/
public void repair(Row row)
{
Map<String, ColumnFamily> columnFamilies = row.getColumnFamilyMap();
Set<String> cfNames = columnFamilies.keySet();
for ( String cfName : cfNames )
{
ColumnFamily cf = columnFamilies_.get(cfName);
if ( cf == null )
{
cf = new ColumnFamily(cfName);
columnFamilies_.put(cfName, cf);
}
cf.repair(columnFamilies.get(cfName));
}
}
/*
* This function will calculate the difference between 2 rows
* and return the resultant row. This assumes that the row that
* is being submitted is a super set of the current row so
* it only calculates additional
* difference and does not take care of what needs to be delted from the current row to make
* it same as the input row.
*/
public Row diff(Row row)
{
Row rowDiff = new Row(key_);
Map<String, ColumnFamily> columnFamilies = row.getColumnFamilyMap();
Set<String> cfNames = columnFamilies.keySet();
for ( String cfName : cfNames )
{
ColumnFamily cf = columnFamilies_.get(cfName);
ColumnFamily cfDiff = null;
if ( cf == null )
rowDiff.getColumnFamilyMap().put(cfName, columnFamilies.get(cfName));
else
{
cfDiff = cf.diff(columnFamilies.get(cfName));
if(cfDiff != null)
rowDiff.getColumnFamilyMap().put(cfName, cfDiff);
}
}
if(rowDiff.getColumnFamilyMap().size() != 0)
return rowDiff;
else
return null;
}
public Row cloneMe()
{
Row row = new Row(key_);
row.columnFamilies_ = new HashMap<String, ColumnFamily>(columnFamilies_);
return row;
}
public byte[] digest()
{
long start = System.currentTimeMillis();
Set<String> cfamilies = columnFamilies_.keySet();
byte[] xorHash = ArrayUtils.EMPTY_BYTE_ARRAY;
for(String cFamily : cfamilies)
{
if(xorHash.length == 0)
{
xorHash = columnFamilies_.get(cFamily).digest();
}
else
{
byte[] tmpHash = columnFamilies_.get(cFamily).digest();
xorHash = FBUtilities.xor(xorHash, tmpHash);
}
}
logger_.info("DIGEST TIME: " + (System.currentTimeMillis() - start)
+ " ms.");
return xorHash;
}
void clear()
{
columnFamilies_.clear();
}
public String toString() {
return "Row(" + key_ + " [" + StringUtils.join(columnFamilies_.values(), ", ") + ")]";
}
}
class RowSerializer implements ICompactSerializer<Row>
{
public void serialize(Row row, DataOutputStream dos) throws IOException
{
dos.writeUTF(row.key());
Map<String, ColumnFamily> columnFamilies = row.getColumnFamilyMap();
int size = columnFamilies.size();
dos.writeInt(size);
if ( size > 0 )
{
Set<String> cNames = columnFamilies.keySet();
for ( String cName : cNames )
{
ColumnFamily.serializer().serialize(columnFamilies.get(cName), dos);
}
}
}
public Row deserialize(DataInputStream dis) throws IOException
{
String key = dis.readUTF();
Row row = new Row(key);
int size = dis.readInt();
if ( size > 0 )
{
for ( int i = 0; i < size; ++i )
{
ColumnFamily cf = ColumnFamily.serializer().deserialize(dis);
row.addColumnFamily(cf);
}
}
return row;
}
}