package net.ameba.cassandra.web.controller;
import java.util.List;
import net.ameba.cassandra.web.util.ByteArray;
import org.apache.cassandra.thrift.Cassandra.Client;
import org.apache.cassandra.thrift.CfDef;
import org.apache.cassandra.thrift.ColumnOrSuperColumn;
import org.apache.cassandra.thrift.ColumnParent;
import org.apache.cassandra.thrift.ConsistencyLevel;
import org.apache.cassandra.thrift.KeyRange;
import org.apache.cassandra.thrift.KeySlice;
import org.apache.cassandra.thrift.KsDef;
import org.apache.cassandra.thrift.SlicePredicate;
import org.apache.cassandra.thrift.SliceRange;
import org.apache.cassandra.thrift.UnavailableException;
import org.apache.commons.codec.binary.Hex;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* The controller supports functions to manage column families.
*/
@Controller
public class ColumnFamilyController extends AbstractBaseController {
@RequestMapping(value="/keyspace/{name}/addcf", method=RequestMethod.GET)
public String addColumnFamily() {
return "/keyspace_addcf";
}
@RequestMapping(value="/keyspace/{name}/addcf", method=RequestMethod.POST)
public String addColumnFamilyExecute(
@PathVariable("name") String keyspaceName,
@RequestParam("name") String name,
@RequestParam("type") String type,
@RequestParam("comparator") String comparator,
@RequestParam("subComparator") String subComparator,
@RequestParam("comment") String comment,
@RequestParam("keyCache") double keyCache,
@RequestParam("rowCache") double rowCache,
ModelMap model) throws Exception {
Client client = clientProvider.getThriftClient();
client.set_keyspace(keyspaceName);
// trim name
name = name.trim();
if (name.length() == 0) {
throw new IllegalArgumentException("CFName must not be empty");
}
// clear sub comparator if the type is Standard.
if ("Standard".equals(type)) {
subComparator = "";
}
// creating definition.
CfDef cfdef = new CfDef(keyspaceName, name);
cfdef.setComment(comment);
cfdef.setColumn_type(type);
cfdef.setComparator_type(comparator);
if (subComparator != null && subComparator.length() > 0) {
cfdef.setSubcomparator_type(subComparator);
}
cfdef.setKey_cache_size(keyCache);
cfdef.setRow_cache_size(rowCache);
client.system_add_column_family(cfdef);
// redirecting to keyspace page.
model.clear();
return "redirect:/keyspace/" + keyspaceName + "/";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/", method=RequestMethod.GET)
public String describeColumnFamily(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
Client client = clientProvider.getThriftClient();
// Extracting list of column families
KsDef ksDef = client.describe_keyspace(keyspaceName);
model.put("columnFamilies", ksDef.getCf_defs());
// setting names
model.addAttribute("keyspaceName", keyspaceName);
model.addAttribute("columnFamilyName", columnFamilyName);
if (cassandraService.isSystemKeyspace(keyspaceName)) {
model.put("system", true);
}
return "/columnfamily";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/browse", method=RequestMethod.GET)
public String browseColumnFamily(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
@RequestParam(value="start", defaultValue="") String start,
@RequestParam(value="end", defaultValue="") String end,
@RequestParam(value="count", defaultValue="50") int count,
@RequestParam(value="columnCount", defaultValue="5") int columnCount,
@RequestParam(value="encode", defaultValue="hex") String encode,
ModelMap model) throws Exception {
model.addAttribute("count", count);
model.addAttribute("encode", encode);
Client client = clientProvider.getThriftClient();
// set target keyspace.
client.set_keyspace(keyspaceName);
// set target column family
ColumnParent parent = new ColumnParent(columnFamilyName);
// create target range
SliceRange sliceRange = new SliceRange();
sliceRange.setStart(new byte[0]);
sliceRange.setFinish(new byte[0]);
sliceRange.setCount(columnCount + 1);
SlicePredicate slicePredicate = new SlicePredicate();
slicePredicate.setSlice_range(sliceRange);
// Set row range from request
KeyRange range = new KeyRange(count + 1);
if (start.length() > 0) {
range.setStart_key(Hex.decodeHex(start.toCharArray()));
} else {
range.setStart_key(new byte[0]);
}
if (end.length() > 0) {
range.setEnd_key(Hex.decodeHex(end.toCharArray()));
} else {
range.setEnd_key(new byte[0]);
}
try {
// getting slice
List<KeySlice> slices = client.get_range_slices(
parent,
slicePredicate,
range,
ConsistencyLevel.ONE);
KeySliceType[] types = new KeySliceType[slices.size()];
for (int i = 0; i < types.length; i++) {
KeySlice keySlice = slices.get(i);
KeySliceType type = new KeySliceType();
type.key = ByteArray.toUTF(keySlice.getKey());
type.keyHex = new String(Hex.encodeHex(keySlice.getKey()));
int clen = Math.min(columnCount, keySlice.getColumnsSize());
type.columns = new String[clen];
for (int j = 0; j < clen; j++) {
ColumnOrSuperColumn cos = keySlice.columns.get(j);
if (cos.isSetColumn()) {
type.columns[j] = new String(Hex.encodeHex(cos.column.getName()));
} else if (cos.isSetSuper_column()) {
type.columns[j] = new String(Hex.encodeHex(cos.super_column.getName()));
} else {
type.columns[j] = "Unknown";
}
}
if (keySlice.getColumnsSize() > columnCount) {
type.hasMoreColumn = true;
}
types[i] = type;
}
model.addAttribute("slices", types);
} catch (UnavailableException ex) {
model.addAttribute("unavailable", true);
}
// setting names
model.addAttribute("keyspaceName", keyspaceName);
model.addAttribute("columnFamilyName", columnFamilyName);
return "/columnfamily_browse";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/rename", method=RequestMethod.GET)
public String renameColumnFamily(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
model.addAttribute("keyspaceName", keyspaceName);
model.addAttribute("columnFamilyName", columnFamilyName);
return "/columnfamily_rename";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/rename", method=RequestMethod.POST)
public String renameColumnFamilyExecute(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String originalName,
@RequestParam("name") String columnFamilyName,
ModelMap model) throws Exception {
throw new UnsupportedOperationException("rename column family no longer supported");
/*
Client client = clientProvider.getThriftClient();
client.set_keyspace(keyspaceName);
client.system_rename_column_family(
originalName,
columnFamilyName
);
model.clear();
return "redirect:/keyspace/" + keyspaceName + "/" + columnFamilyName + "/";
*/
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/drop", method=RequestMethod.GET)
public String dropColumnFamily(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
model.addAttribute("keyspaceName", keyspaceName);
model.addAttribute("columnFamilyName", columnFamilyName);
return "/columnfamily_drop";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/drop", method=RequestMethod.POST)
public String dropColumnFamilyExecute(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
Client client = clientProvider.getThriftClient();
client.set_keyspace(keyspaceName);
client.system_drop_column_family(
columnFamilyName
);
model.clear();
return "redirect:/keyspace/" + keyspaceName + "/" + columnFamilyName + "/";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/truncate", method=RequestMethod.GET)
public String truncateColyumnFamily(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
model.addAttribute("keyspaceName", keyspaceName);
model.addAttribute("columnFamilyName", columnFamilyName);
return "/columnfamily_truncate";
}
@RequestMapping(value="/keyspace/{keyspaceName}/{columnFamilyName}/truncate", method=RequestMethod.POST)
public String truncateColumnFamilyExecute(
@PathVariable("keyspaceName") String keyspaceName,
@PathVariable("columnFamilyName") String columnFamilyName,
ModelMap model) throws Exception {
Client client = clientProvider.getThriftClient();
client.set_keyspace(keyspaceName);
client.truncate(
columnFamilyName
);
model.clear();
return "redirect:/keyspace/" + keyspaceName + "/" + columnFamilyName + "/";
}
public static class KeySliceType {
private String key;
private String keyHex;
private String[] columns;
private boolean hasMoreColumn = false;
public String getKey() {
return key;
}
public String[] getColumns() {
return columns;
}
public String getKeyHex() {
return keyHex;
}
public boolean isHasMoreColumn() {
return hasMoreColumn;
}
}
}