Author: stack
Date: Sat Aug 27 04:01:18 2011
New Revision: 1162293

URL: http://svn.apache.org/viewvc?rev=1162293&view=rev
Log:
HBASE-4117  Slow Query Log and Client Operation Fingerprints

Added:
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Operation.java
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/client/TestOperation.java
Modified:
    hbase/trunk/CHANGES.txt
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/KeyValue.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Delete.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Get.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiAction.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiPut.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Put.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Scan.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/WritableRpcEngine.java

Modified: hbase/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Sat Aug 27 04:01:18 2011
@@ -468,6 +468,8 @@ Release 0.91.0 - Unreleased
                written version (Lars Hofhansl)
    HBASE-4242  Add documentation for HBASE-4071 (Lars Hofhansl)
    HBASE-4027  Enable direct byte buffers LruBlockCache (Li Pi)
+   HBASE-4117  Slow Query Log and Client Operation Fingerprints
+               (Riley Patterson)
 
 Release 0.90.5 - Unreleased
 

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/KeyValue.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/KeyValue.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/KeyValue.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/KeyValue.java Sat Aug 27 
04:01:18 2011
@@ -629,6 +629,7 @@ public class KeyValue implements Writabl
     stringMap.put("family", Bytes.toStringBinary(getFamily()));
     stringMap.put("qualifier", Bytes.toStringBinary(getQualifier()));
     stringMap.put("timestamp", getTimestamp());
+    stringMap.put("vlen", getValueLength());
     return stringMap;
   }
 

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Delete.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Delete.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Delete.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Delete.java Sat 
Aug 27 04:01:18 2011
@@ -34,7 +34,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * Used to perform Delete operations on a single row.
@@ -68,7 +70,8 @@ import java.util.TreeMap;
  * deleteFamily -- then you need to use the method overrides that take a
  * timestamp.  The constructor timestamp is not referenced.
  */
-public class Delete implements Writable, Row, Comparable<Row> {
+public class Delete extends Operation
+  implements Writable, Row, Comparable<Row> {
   private static final byte DELETE_VERSION = (byte)3;
 
   private byte [] row = null;
@@ -340,39 +343,67 @@ public class Delete implements Writable,
   }
 
   /**
-   * @return string
+   * Compile the column family (i.e. schema) information
+   * into a Map. Useful for parsing and aggregation by debugging,
+   * logging, and administration tools.
+   * @return Map
    */
   @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("row=");
-    sb.append(Bytes.toStringBinary(this.row));
-    sb.append(", ts=");
-    sb.append(this.ts);
-    sb.append(", families={");
-    boolean moreThanOne = false;
-    for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
-      if(moreThanOne) {
-        sb.append(", ");
-      } else {
-        moreThanOne = true;
+  public Map<String, Object> getFingerprint() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    List<String> families = new ArrayList<String>();
+    // ideally, we would also include table information, but that information
+    // is not stored in each Operation instance.
+    map.put("families", families);
+    for (Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) 
{
+      families.add(Bytes.toStringBinary(entry.getKey()));
+    }
+    return map;
+  }
+
+  /**
+   * Compile the details beyond the scope of getFingerprint (row, columns,
+   * timestamps, etc.) into a Map along with the fingerprinted information.
+   * Useful for debugging, logging, and administration tools.
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return Map
+   */
+  @Override
+  public Map<String, Object> toMap(int maxCols) {
+    // we start with the fingerprint map and build on top of it.
+    Map<String, Object> map = getFingerprint();
+    // replace the fingerprint's simple list of families with a 
+    // map from column families to lists of qualifiers and kv details
+    Map<String, List<Map<String, Object>>> columns =
+      new HashMap<String, List<Map<String, Object>>>();
+    map.put("families", columns);
+    map.put("row", Bytes.toStringBinary(this.row));
+    map.put("ts", this.ts);
+    int colCount = 0;
+    // iterate through all column families affected by this Delete
+    for (Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) 
{
+      // map from this family to details for each kv affected within the family
+      List<Map<String, Object>> qualifierDetails =
+        new ArrayList<Map<String, Object>>();
+      columns.put(Bytes.toStringBinary(entry.getKey()), qualifierDetails);
+      colCount += entry.getValue().size();
+      if (maxCols <= 0) {
+        continue;
       }
-      sb.append("(family=");
-      sb.append(Bytes.toString(entry.getKey()));
-      sb.append(", keyvalues=(");
-      boolean moreThanOneB = false;
-      for(KeyValue kv : entry.getValue()) {
-        if(moreThanOneB) {
-          sb.append(", ");
-        } else {
-          moreThanOneB = true;
+      // add details for each kv
+      for (KeyValue kv : entry.getValue()) {
+        if (--maxCols <= 0 ) {
+          continue;
         }
-        sb.append(kv.toString());
+        Map<String, Object> kvMap = kv.toStringMap();
+        // row and family information are already available in the bigger map
+        kvMap.remove("row");
+        kvMap.remove("family");
+        qualifierDetails.add(kvMap);
       }
-      sb.append(")");
     }
-    sb.append("}");
-    return sb.toString();
+    map.put("totalColumns", colCount);
+    return map;
   }
 
   //Writable

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Get.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Get.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Get.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Get.java Sat Aug 
27 04:01:18 2011
@@ -31,8 +31,10 @@ import org.apache.hadoop.io.WritableUtil
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.Set;
@@ -63,7 +65,8 @@ import java.util.TreeSet;
  * <p>
  * To add a filter, execute {@link #setFilter(Filter) setFilter}.
  */
-public class Get implements Writable, Row, Comparable<Row> {
+public class Get extends Operation
+  implements Writable, Row, Comparable<Row> {
   private static final byte GET_VERSION = (byte)2;
 
   private byte [] row = null;
@@ -352,55 +355,71 @@ public class Get implements Writable, Ro
   }
 
   /**
-   * @return String
+   * Compile the table and column family (i.e. schema) information
+   * into a String. Useful for parsing and aggregation by debugging,
+   * logging, and administration tools.
+   * @return Map
    */
   @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("row=");
-    sb.append(Bytes.toStringBinary(this.row));
-    sb.append(", maxVersions=");
-    sb.append("").append(this.maxVersions);
-    sb.append(", cacheBlocks=");
-    sb.append(this.cacheBlocks);
-    sb.append(", timeRange=");
-    sb.append("[").append(this.tr.getMin()).append(",");
-    sb.append(this.tr.getMax()).append(")");
-    sb.append(", families=");
-    if(this.familyMap.size() == 0) {
-      sb.append("ALL");
-      return sb.toString();
+  public Map<String, Object> getFingerprint() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    List<String> families = new ArrayList<String>();
+    map.put("families", families);
+    for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
+      this.familyMap.entrySet()) {
+      families.add(Bytes.toStringBinary(entry.getKey()));
     }
-    boolean moreThanOne = false;
-    for(Map.Entry<byte [], NavigableSet<byte[]>> entry :
+    return map;
+  }
+
+  /**
+   * Compile the details beyond the scope of getFingerprint (row, columns,
+   * timestamps, etc.) into a Map along with the fingerprinted information.
+   * Useful for debugging, logging, and administration tools.
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return Map
+   */
+  @Override
+  public Map<String, Object> toMap(int maxCols) {
+    // we start with the fingerprint map and build on top of it.
+    Map<String, Object> map = getFingerprint();
+    // replace the fingerprint's simple list of families with a 
+    // map from column families to lists of qualifiers and kv details
+    Map<String, List<String>> columns = new HashMap<String, List<String>>();
+    map.put("families", columns);
+    // add scalar information first
+    map.put("row", Bytes.toStringBinary(this.row));
+    map.put("maxVersions", this.maxVersions);
+    map.put("cacheBlocks", this.cacheBlocks);
+    List<Long> timeRange = new ArrayList<Long>();
+    timeRange.add(this.tr.getMin());
+    timeRange.add(this.tr.getMax());
+    map.put("timeRange", timeRange);
+    int colCount = 0;
+    // iterate through affected families and add details
+    for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
       this.familyMap.entrySet()) {
-      if(moreThanOne) {
-        sb.append("), ");
-      } else {
-        moreThanOne = true;
-        sb.append("{");
-      }
-      sb.append("(family=");
-      sb.append(Bytes.toString(entry.getKey()));
-      sb.append(", columns=");
+      List<String> familyList = new ArrayList<String>();
+      columns.put(Bytes.toStringBinary(entry.getKey()), familyList);
       if(entry.getValue() == null) {
-        sb.append("ALL");
+        colCount++;
+        --maxCols;
+        familyList.add("ALL");
       } else {
-        sb.append("{");
-        boolean moreThanOneB = false;
-        for(byte [] column : entry.getValue()) {
-          if(moreThanOneB) {
-            sb.append(", ");
-          } else {
-            moreThanOneB = true;
+        colCount += entry.getValue().size();
+        if (maxCols <= 0) {
+          continue;
+        }
+        for (byte [] column : entry.getValue()) {
+          if (--maxCols <= 0) {
+            continue;
           }
-          sb.append(Bytes.toStringBinary(column));
+          familyList.add(Bytes.toStringBinary(column));
         }
-        sb.append("}");
-      }
-    }
-    sb.append("}");
-    return sb.toString();
+      }   
+    }   
+    map.put("totalColumns", colCount);
+    return map;
   }
 
   //Row

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiAction.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiAction.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiAction.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiAction.java 
Sat Aug 27 04:01:18 2011
@@ -22,14 +22,13 @@ package org.apache.hadoop.hbase.client;
 import org.apache.hadoop.io.Writable;
 import org.apache.hadoop.hbase.io.HbaseObjectWritable;
 import org.apache.hadoop.hbase.util.Bytes;
-import org.apache.hadoop.hbase.HServerAddress;
 
 import java.io.DataOutput;
 import java.io.IOException;
 import java.io.DataInput;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.ArrayList;
 import java.util.Set;
 import java.util.TreeMap;
 

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiPut.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiPut.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiPut.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/MultiPut.java Sat 
Aug 27 04:01:18 2011
@@ -20,6 +20,7 @@
 
 package org.apache.hadoop.hbase.client;
 
+import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HServerAddress;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.io.Writable;
@@ -29,17 +30,24 @@ import java.io.DataOutput;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
 /**
  * @deprecated Use MultiAction instead
  * Data type class for putting multiple regions worth of puts in one RPC.
  */
-public class MultiPut implements Writable {
+public class MultiPut extends Operation implements Writable {
   public HServerAddress address; // client code ONLY
 
+  // TODO make this configurable
+  public static final int DEFAULT_MAX_PUT_OUTPUT = 10;
+
   // map of regions to lists of puts for that region.
   public Map<byte[], List<Put> > puts = new TreeMap<byte[], 
List<Put>>(Bytes.BYTES_COMPARATOR);
 
@@ -81,6 +89,116 @@ public class MultiPut implements Writabl
     return res;
   }
 
+  /**
+   * Compile the table and column family (i.e. schema) information 
+   * into a String. Useful for parsing and aggregation by debugging,
+   * logging, and administration tools.
+   * @return Map
+   */
+  @Override
+  public Map<String, Object> getFingerprint() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    // for extensibility, we have a map of table information that we will
+    // populate with only family information for each table
+    Map<String, Map> tableInfo = 
+      new HashMap<String, Map>();
+    map.put("tables", tableInfo);
+    for (Map.Entry<byte[], List<Put>> entry : puts.entrySet()) {
+      // our fingerprint only concerns itself with which families are touched,
+      // not how many Puts touch them, so we use this Set to do just that.
+      Set<String> familySet;
+      try {
+        // since the puts are stored by region, we may have already
+        // recorded families for this region. if that is the case,
+        // we want to add to the existing Set. if not, we make a new Set.
+        String tableName = Bytes.toStringBinary(
+            HRegionInfo.parseRegionName(entry.getKey())[0]);
+        if (tableInfo.get(tableName) == null) {
+          Map<String, Object> table = new HashMap<String, Object>();
+          familySet = new TreeSet<String>();
+          table.put("families", familySet);
+          tableInfo.put(tableName, table);
+        } else {
+          familySet = (Set<String>) tableInfo.get(tableName).get("families");
+        }
+      } catch (IOException ioe) {
+        // in the case of parse error, default to labeling by region
+        Map<String, Object> table = new HashMap<String, Object>();
+        familySet = new TreeSet<String>();
+        table.put("families", familySet);
+        tableInfo.put(Bytes.toStringBinary(entry.getKey()), table);
+      }   
+      // we now iterate through each Put and keep track of which families 
+      // are affected in this table.
+      for (Put p : entry.getValue()) {
+        for (byte[] fam : p.getFamilyMap().keySet()) {
+          familySet.add(Bytes.toStringBinary(fam));
+        }
+      }
+    }
+    return map;
+  }
+
+  /**
+   * Compile the details beyond the scope of getFingerprint (mostly 
+   * toMap from the Puts) into a Map along with the fingerprinted 
+   * information. Useful for debugging, logging, and administration tools.
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return Map
+   */
+  @Override
+  public Map<String, Object> toMap(int maxCols) {
+    Map<String, Object> map = getFingerprint();
+    Map<String, Object> tableInfo = (Map<String, Object>) map.get("tables");
+    int putCount = 0;
+    for (Map.Entry<byte[], List<Put>> entry : puts.entrySet()) {
+      // If the limit has been hit for put output, just adjust our counter
+      if (putCount >= DEFAULT_MAX_PUT_OUTPUT) {
+        putCount += entry.getValue().size();
+        continue;
+      }
+      List<Put> regionPuts = entry.getValue();
+      List<Map<String, Object>> putSummaries =
+        new ArrayList<Map<String, Object>>();
+      // find out how many of this region's puts we can add without busting
+      // the maximum
+      int regionPutsToAdd = regionPuts.size();
+      putCount += regionPutsToAdd;
+      if (putCount > DEFAULT_MAX_PUT_OUTPUT) {
+        regionPutsToAdd -= putCount - DEFAULT_MAX_PUT_OUTPUT;
+      }
+      for (Iterator<Put> iter = regionPuts.iterator(); regionPutsToAdd-- > 0;) 
{
+        putSummaries.add(iter.next().toMap(maxCols));
+      }
+      // attempt to extract the table name from the region name
+      String tableName = "";
+      try {
+        tableName = Bytes.toStringBinary(
+            HRegionInfo.parseRegionName(entry.getKey())[0]);
+      } catch (IOException ioe) {
+        // in the case of parse error, default to labeling by region
+        tableName = Bytes.toStringBinary(entry.getKey());
+      }
+      // since the puts are stored by region, we may have already 
+      // recorded puts for this table. if that is the case, 
+      // we want to add to the existing List. if not, we place a new list 
+      // in the map
+      Map<String, Object> table =
+        (Map<String, Object>) tableInfo.get(tableName);
+      if (table == null) {
+        // in case the Put has changed since getFingerprint's map was built
+        table = new HashMap<String, Object>();
+        tableInfo.put(tableName, table);
+        table.put("puts", putSummaries);
+      } else if (table.get("puts") == null) {
+        table.put("puts", putSummaries);
+      } else {
+        ((List<Map<String, Object>>) table.get("puts")).addAll(putSummaries);
+      }
+    }
+    map.put("totalPuts", putCount);
+    return map;
+  }
 
   @Override
   public void write(DataOutput out) throws IOException {

Added: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Operation.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Operation.java?rev=1162293&view=auto
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Operation.java 
(added)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Operation.java Sat 
Aug 27 04:01:18 2011
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2011 The Apache Software Foundation
+ *
+ * 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.hadoop.hbase.client;
+
+import java.io.IOException;
+import java.util.Map;
+
+import org.codehaus.jackson.map.ObjectMapper;
+
+/**
+ * Superclass for any type that maps to a potentially application-level query.
+ * (e.g. Put, Get, Delete, Scan, Next, etc.)
+ * Contains methods for exposure to logging and debugging tools.
+ */
+public abstract class Operation {
+  // TODO make this configurable
+  private static final int DEFAULT_MAX_COLS = 5;
+
+  /**
+   * Produces a Map containing a fingerprint which identifies the type and 
+   * the static schema components of a query (i.e. column families)
+   * @return a map containing fingerprint information (i.e. column families)
+   */
+  public abstract Map<String, Object> getFingerprint();
+
+  /**
+   * Produces a Map containing a summary of the details of a query 
+   * beyond the scope of the fingerprint (i.e. columns, rows...)
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return a map containing parameters of a query (i.e. rows, columns...)
+   */
+  public abstract Map<String, Object> toMap(int maxCols);
+
+  /**
+   * Produces a Map containing a full summary of a query.
+   * @return a map containing parameters of a query (i.e. rows, columns...)
+   */
+  public Map<String, Object> toMap() {
+    return toMap(DEFAULT_MAX_COLS);
+  }
+
+  /**
+   * Produces a JSON object for fingerprint and details exposure in a
+   * parseable format.
+   * @param maxCols a limit on the number of columns to include in the JSON
+   * @return a JSONObject containing this Operation's information, as a string
+   */
+  public String toJSON(int maxCols) throws IOException {
+    ObjectMapper mapper = new ObjectMapper();
+    return mapper.writeValueAsString(toMap(maxCols));
+  }
+
+  /**
+   * Produces a JSON object sufficient for description of a query
+   * in a debugging or logging context.
+   * @return the produced JSON object, as a string
+   */
+  public String toJSON() throws IOException {
+    return toJSON(DEFAULT_MAX_COLS);
+  }
+
+  /**
+   * Produces a string representation of this Operation. It defaults to a JSON
+   * representation, but falls back to a string representation of the 
+   * fingerprint and details in the case of a JSON encoding failure.
+   * @param maxCols a limit on the number of columns output in the summary
+   * prior to truncation
+   * @return a JSON-parseable String
+   */
+  public String toString(int maxCols) {
+    /* for now this is merely a wrapper from producing a JSON string, but 
+     * toJSON is kept separate in case this is changed to be a less parsable
+     * pretty printed representation.
+     */
+    try {
+      return toJSON(maxCols);
+    } catch (IOException ioe) {
+      return toMap(maxCols).toString();
+    }
+  }
+
+  /**
+   * Produces a string representation of this Operation. It defaults to a JSON
+   * representation, but falls back to a string representation of the
+   * fingerprint and details in the case of a JSON encoding failure.
+   * @return String
+   */
+  @Override
+  public String toString() {
+    return toString(DEFAULT_MAX_COLS);
+  }
+}
+

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Put.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Put.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Put.java (original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Put.java Sat Aug 
27 04:01:18 2011
@@ -37,7 +37,9 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.TreeMap;
+import java.util.TreeSet;
 
 
 /**
@@ -47,7 +49,8 @@ import java.util.TreeMap;
  * for each column to be inserted, execute {@link #add(byte[], byte[], byte[]) 
add} or
  * {@link #add(byte[], byte[], long, byte[]) add} if setting the timestamp.
  */
-public class Put implements HeapSize, Writable, Row, Comparable<Row> {
+public class Put extends Operation
+  implements HeapSize, Writable, Row, Comparable<Row> {
   private static final byte PUT_VERSION = (byte)2;
 
   private byte [] row = null;
@@ -462,39 +465,67 @@ public class Put implements HeapSize, Wr
     return Collections.unmodifiableMap(attributes);
   }
 
+  /**
+   * Compile the column family (i.e. schema) information
+   * into a Map. Useful for parsing and aggregation by debugging,
+   * logging, and administration tools.
+   * @return Map
+   */
+  @Override
+  public Map<String, Object> getFingerprint() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    List<String> families = new ArrayList<String>();
+    // ideally, we would also include table information, but that information
+    // is not stored in each Operation instance.
+    map.put("families", families);
+    for (Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) 
{
+      families.add(Bytes.toStringBinary(entry.getKey()));
+    } 
+    return map;
+  }
 
   /**
-   * @return String
+   * Compile the details beyond the scope of getFingerprint (row, columns,
+   * timestamps, etc.) into a Map along with the fingerprinted information.
+   * Useful for debugging, logging, and administration tools.
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return Map
    */
   @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("row=");
-    sb.append(Bytes.toStringBinary(this.row));
-    sb.append(", families={");
-    boolean moreThanOne = false;
-    for(Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) {
-      if(moreThanOne) {
-        sb.append(", ");
-      } else {
-        moreThanOne = true;
-      }
-      sb.append("(family=");
-      sb.append(Bytes.toString(entry.getKey()));
-      sb.append(", keyvalues=(");
-      boolean moreThanOneB = false;
-      for(KeyValue kv : entry.getValue()) {
-        if(moreThanOneB) {
-          sb.append(", ");
-        } else {
-          moreThanOneB = true;
+  public Map<String, Object> toMap(int maxCols) {
+    // we start with the fingerprint map and build on top of it.
+    Map<String, Object> map = getFingerprint();
+    // replace the fingerprint's simple list of families with a 
+    // map from column families to lists of qualifiers and kv details
+    Map<String, List<Map<String, Object>>> columns =
+      new HashMap<String, List<Map<String, Object>>>();
+    map.put("families", columns);
+    map.put("row", Bytes.toStringBinary(this.row));
+    int colCount = 0;
+    // iterate through all column families affected by this Put
+    for (Map.Entry<byte [], List<KeyValue>> entry : this.familyMap.entrySet()) 
{
+      // map from this family to details for each kv affected within the family
+      List<Map<String, Object>> qualifierDetails =
+        new ArrayList<Map<String, Object>>();
+      columns.put(Bytes.toStringBinary(entry.getKey()), qualifierDetails);
+      colCount += entry.getValue().size();
+      if (maxCols <= 0) {
+        continue;
+      }
+      // add details for each kv
+      for (KeyValue kv : entry.getValue()) {
+        if (--maxCols <= 0 ) {
+          continue;
         }
-        sb.append(kv.toString());
+        Map<String, Object> kvMap = kv.toStringMap();
+        // row and family information are already available in the bigger map
+        kvMap.remove("row");
+        kvMap.remove("family");
+        qualifierDetails.add(kvMap);
       }
-      sb.append(")");
     }
-    sb.append("}");
-    return sb.toString();
+    map.put("totalColumns", colCount);
+    return map;
   }
 
   public int compareTo(Row p) {

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Scan.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Scan.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Scan.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/client/Scan.java Sat Aug 
27 04:01:18 2011
@@ -34,8 +34,10 @@ import org.apache.hadoop.io.WritableUtil
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.NavigableSet;
 import java.util.TreeMap;
@@ -81,7 +83,7 @@ import java.util.TreeSet;
  * Expert: To explicitly disable server-side block caching for this scan,
  * execute {@link #setCacheBlocks(boolean)}.
  */
-public class Scan implements Writable {
+public class Scan extends Operation implements Writable {
   private static final byte SCAN_VERSION = (byte)2;
   private byte [] startRow = HConstants.EMPTY_START_ROW;
   private byte [] stopRow  = HConstants.EMPTY_END_ROW;
@@ -494,60 +496,79 @@ public class Scan implements Writable {
   }
 
   /**
-   * @return String
+   * Compile the table and column family (i.e. schema) information
+   * into a String. Useful for parsing and aggregation by debugging,
+   * logging, and administration tools.
+   * @return Map
    */
   @Override
-  public String toString() {
-    StringBuilder sb = new StringBuilder();
-    sb.append("startRow=");
-    sb.append(Bytes.toStringBinary(this.startRow));
-    sb.append(", stopRow=");
-    sb.append(Bytes.toStringBinary(this.stopRow));
-    sb.append(", maxVersions=");
-    sb.append(this.maxVersions);
-    sb.append(", batch=");
-    sb.append(this.batch);
-    sb.append(", caching=");
-    sb.append(this.caching);
-    sb.append(", cacheBlocks=");
-    sb.append(this.cacheBlocks);
-    sb.append(", timeRange=");
-    sb.append("[").append(this.tr.getMin()).append(",");
-    sb.append(this.tr.getMax()).append(")");
-    sb.append(", families=");
+  public Map<String, Object> getFingerprint() {
+    Map<String, Object> map = new HashMap<String, Object>();
+    List<String> families = new ArrayList<String>();
     if(this.familyMap.size() == 0) {
-      sb.append("ALL");
-      return sb.toString();
+      map.put("families", "ALL");
+      return map;
+    } else {
+      map.put("families", families);
     }
-    boolean moreThanOne = false;
-    for(Map.Entry<byte [], NavigableSet<byte[]>> entry : 
this.familyMap.entrySet()) {
-      if(moreThanOne) {
-        sb.append("), ");
-      } else {
-        moreThanOne = true;
-        sb.append("{");
-      }
-      sb.append("(family=");
-      sb.append(Bytes.toStringBinary(entry.getKey()));
-      sb.append(", columns=");
+    for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
+        this.familyMap.entrySet()) {
+      families.add(Bytes.toStringBinary(entry.getKey()));
+    }
+    return map;
+  }
+
+  /**
+   * Compile the details beyond the scope of getFingerprint (row, columns,
+   * timestamps, etc.) into a Map along with the fingerprinted information.
+   * Useful for debugging, logging, and administration tools.
+   * @param maxCols a limit on the number of columns output prior to truncation
+   * @return Map
+   */ 
+  @Override
+  public Map<String, Object> toMap(int maxCols) {
+    // start with the fingerpring map and build on top of it
+    Map<String, Object> map = getFingerprint();
+    // map from families to column list replaces fingerprint's list of families
+    Map<String, List<String>> familyColumns =
+      new HashMap<String, List<String>>();
+    map.put("families", familyColumns);
+    // add scalar information first
+    map.put("startRow", Bytes.toStringBinary(this.startRow));
+    map.put("stopRow", Bytes.toStringBinary(this.stopRow));
+    map.put("maxVersions", this.maxVersions);
+    map.put("batch", this.batch);
+    map.put("caching", this.caching);
+    map.put("cacheBlocks", this.cacheBlocks);
+    List<Long> timeRange = new ArrayList<Long>();
+    timeRange.add(this.tr.getMin());
+    timeRange.add(this.tr.getMax());
+    map.put("timeRange", timeRange);
+    int colCount = 0;
+    // iterate through affected families and list out up to maxCols columns
+    for (Map.Entry<byte [], NavigableSet<byte[]>> entry :
+      this.familyMap.entrySet()) {
+      List<String> columns = new ArrayList<String>();
+      familyColumns.put(Bytes.toStringBinary(entry.getKey()), columns);
       if(entry.getValue() == null) {
-        sb.append("ALL");
+        colCount++;
+        --maxCols;
+        columns.add("ALL");
       } else {
-        sb.append("{");
-        boolean moreThanOneB = false;
-        for(byte [] column : entry.getValue()) {
-          if(moreThanOneB) {
-            sb.append(", ");
-          } else {
-            moreThanOneB = true;
+        colCount += entry.getValue().size();
+        if (maxCols <= 0) {
+          continue;
+        } 
+        for (byte [] column : entry.getValue()) {
+          if (--maxCols <= 0) {
+            continue;
           }
-          sb.append(Bytes.toStringBinary(column));
+          columns.add(Bytes.toStringBinary(column));
         }
-        sb.append("}");
-      }
-    }
-    sb.append("}");
-    return sb.toString();
+      } 
+    }       
+    map.put("totalColumns", colCount);
+    return map;
   }
 
   @SuppressWarnings("unchecked")

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/HBaseServer.java Sat 
Aug 27 04:01:18 2011
@@ -93,16 +93,8 @@ public abstract class HBaseServer implem
    */
   private static final int DEFAULT_MAX_QUEUE_SIZE_PER_HANDLER = 10;
 
-  private static final String WARN_RESPONSE_SIZE =
-      "hbase.ipc.warn.response.size";
-
-  /** Default value for above param */
-  private static final int DEFAULT_WARN_RESPONSE_SIZE = 100 * 1024 * 1024;
-
   static final int BUFFER_INITIAL_SIZE = 1024;
 
-  private final int warnResponseSize;
-
   private static final String WARN_DELAYED_CALLS =
       "hbase.ipc.warn.delayedrpc.number";
 
@@ -337,11 +329,6 @@ public abstract class HBaseServer implem
         LOG.warn("Error sending response to call: ", e);
       }
 
-      if (buf.size() > warnResponseSize) {
-        LOG.warn("responseTooLarge for: "+this+": Size: "
-            + StringUtils.humanReadableInt(buf.size()));
-      }
-
       this.response = buf.getByteBuffer();
     }
 
@@ -1328,8 +1315,6 @@ public abstract class HBaseServer implem
     this.tcpNoDelay = conf.getBoolean("ipc.server.tcpnodelay", false);
     this.tcpKeepAlive = conf.getBoolean("ipc.server.tcpkeepalive", true);
 
-    this.warnResponseSize = conf.getInt(WARN_RESPONSE_SIZE,
-                                        DEFAULT_WARN_RESPONSE_SIZE);
     this.warnDelayedCalls = conf.getInt(WARN_DELAYED_CALLS,
                                         DEFAULT_WARN_DELAYED_CALLS);
     this.delayedCalls = new AtomicInteger(0);

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/WritableRpcEngine.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/WritableRpcEngine.java?rev=1162293&r1=1162292&r2=1162293&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/WritableRpcEngine.java 
(original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/ipc/WritableRpcEngine.java 
Sat Aug 27 04:01:18 2011
@@ -35,7 +35,11 @@ import javax.net.SocketFactory;
 
 import org.apache.commons.logging.*;
 
+import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.client.Operation;
 import org.apache.hadoop.hbase.io.HbaseObjectWritable;
+import org.apache.hadoop.hbase.regionserver.HRegionServer;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Objects;
 import org.apache.hadoop.io.*;
 import org.apache.hadoop.hbase.ipc.VersionedProtocol;
@@ -43,6 +47,8 @@ import org.apache.hadoop.security.UserGr
 import org.apache.hadoop.security.authorize.ServiceAuthorizationManager;
 import org.apache.hadoop.conf.*;
 
+import org.codehaus.jackson.map.ObjectMapper;
+
 /** An RpcEngine implementation for Writable data. */
 class WritableRpcEngine implements RpcEngine {
   // LOG is NOT in hbase subpackage intentionally so that the default HBase
@@ -246,6 +252,21 @@ class WritableRpcEngine implements RpcEn
     private boolean verbose;
     private boolean authorize = false;
 
+    // for JSON encoding
+    private static ObjectMapper mapper = new ObjectMapper();
+
+    private static final String WARN_RESPONSE_TIME =
+      "hbase.ipc.warn.response.time";
+    private static final String WARN_RESPONSE_SIZE =
+      "hbase.ipc.warn.response.size";
+
+    /** Default value for above params */
+    private static final int DEFAULT_WARN_RESPONSE_TIME = 10000; // 
milliseconds
+    private static final int DEFAULT_WARN_RESPONSE_SIZE = 100 * 1024 * 1024;
+
+    private final int warnResponseTime;
+    private final int warnResponseSize;
+
     private static String classNameBase(String className) {
       String[] names = className.split("\\.", -1);
       if (names == null || names.length == 0) {
@@ -282,6 +303,11 @@ class WritableRpcEngine implements RpcEn
       this.authorize =
         conf.getBoolean(
             ServiceAuthorizationManager.SERVICE_AUTHORIZATION_CONFIG, false);
+
+      this.warnResponseTime = conf.getInt(WARN_RESPONSE_TIME,
+          DEFAULT_WARN_RESPONSE_TIME);
+      this.warnResponseSize = conf.getInt(WARN_RESPONSE_SIZE,
+          DEFAULT_WARN_RESPONSE_SIZE);
     }
 
     @Override
@@ -326,8 +352,35 @@ class WritableRpcEngine implements RpcEn
         rpcMetrics.inc(call.getMethodName(), processingTime);
         if (verbose) log("Return: "+value);
 
-        return new HbaseObjectWritable(method.getReturnType(), value);
+        HbaseObjectWritable retVal =
+          new HbaseObjectWritable(method.getReturnType(), value);
+        long responseSize = retVal.getWritableSize();
+        // log any RPC responses that are slower than the configured warn
+        // response time or larger than configured warning size
+        boolean tooSlow = (processingTime > warnResponseTime
+            && warnResponseTime > -1);
+        boolean tooLarge = (responseSize > warnResponseSize
+            && warnResponseSize > -1);
+        if (tooSlow || tooLarge) {
+          // when tagging, we let TooLarge trump TooSmall to keep output simple
+          // note that large responses will often also be slow.
+          logResponse(call, (tooLarge ? "TooLarge" : "TooSlow"),
+              startTime, processingTime, qTime, responseSize);
+          // provides a count of log-reported slow responses
+          if (tooSlow) {
+            rpcMetrics.inc(call.getMethodName() + ".slowResponse.",
+                processingTime);
+          }
+        }
+        if (processingTime > 1000) {
+          // we use a hard-coded one second period so that we can clearly
+          // indicate the time period we're warning about in the name of the 
+          // metric itself
+          rpcMetrics.inc(call.getMethodName() + ".aboveOneSec.",
+              processingTime);
+        }
 
+        return retVal;
       } catch (InvocationTargetException e) {
         Throwable target = e.getTargetException();
         if (target instanceof IOException) {
@@ -345,6 +398,60 @@ class WritableRpcEngine implements RpcEn
         throw ioe;
       }
     }
+
+    /**
+     * Logs an RPC response to the LOG file, producing valid JSON objects for
+     * client Operations.
+     * @param call The call to log.
+     * @param tag  The tag that will be used to indicate this event in the log.
+     * @param startTime       The time that the call was initiated, in ms.
+     * @param processingTime  The duration that the call took to run, in ms.
+     * @param qTime           The duration that the call spent on the queue 
+     *                        prior to being initiated, in ms.
+     * @param responseSize    The size in bytes of the response buffer.
+     */
+    private void logResponse(Invocation call, String tag,
+        long startTime, int processingTime, int qTime, long responseSize)
+      throws IOException {
+      Object params[] = call.getParameters();
+      // for JSON encoding
+      ObjectMapper mapper = new ObjectMapper();
+      // base information that is reported regardless of type of call
+      Map<String, Object> responseInfo = new HashMap<String, Object>();
+      responseInfo.put("starttimems", startTime);
+      responseInfo.put("processingtimems", processingTime);
+      responseInfo.put("queuetimems", qTime);
+      responseInfo.put("responsesize", responseSize);
+      responseInfo.put("class", instance.getClass().getSimpleName());
+      responseInfo.put("method", call.getMethodName());
+      if (params.length == 2 && instance instanceof HRegionServer &&
+          params[0] instanceof byte[] &&
+          params[1] instanceof Operation) {
+        // if the slow process is a query, we want to log its table as well 
+        // as its own fingerprint
+        byte [] tableName =
+          HRegionInfo.parseRegionName((byte[]) params[0])[0];
+        responseInfo.put("table", Bytes.toStringBinary(tableName));
+        // annotate the response map with operation details
+        responseInfo.putAll(((Operation) params[1]).toMap());
+        // report to the log file
+        LOG.warn("(operation" + tag + "): " +
+            mapper.writeValueAsString(responseInfo));
+      } else if (params.length == 1 && instance instanceof HRegionServer &&
+          params[0] instanceof Operation) {
+        // annotate the response map with operation details
+        responseInfo.putAll(((Operation) params[1]).toMap());
+        // report to the log file
+        LOG.warn("(operation" + tag + "): " +
+            mapper.writeValueAsString(responseInfo));
+      } else {
+        // can't get JSON details, so just report call.toString() along with 
+        // a more generic tag.
+        responseInfo.put("call", call.toString());
+        LOG.warn("(response" + tag + "): " +
+            mapper.writeValueAsString(responseInfo));
+      }
+    }
   }
 
   protected static void log(String value) {

Added: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/client/TestOperation.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/client/TestOperation.java?rev=1162293&view=auto
==============================================================================
--- hbase/trunk/src/test/java/org/apache/hadoop/hbase/client/TestOperation.java 
(added)
+++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/client/TestOperation.java 
Sat Aug 27 04:01:18 2011
@@ -0,0 +1,130 @@
+/**
+ * Copyright 2011 The Apache Software Foundation
+ *
+ * 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.hadoop.hbase.client;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.hbase.util.Bytes;
+
+import org.codehaus.jackson.map.ObjectMapper;
+
+/**
+ * Run tests that use the funtionality of the Operation superclass for 
+ * Puts, Gets, Deletes, Scans, and MultiPuts.
+ */
+public class TestOperation {
+  private static byte [] ROW = Bytes.toBytes("testRow");
+  private static byte [] FAMILY = Bytes.toBytes("testFamily");
+  private static byte [] QUALIFIER = Bytes.toBytes("testQualifier");
+  private static byte [] VALUE = Bytes.toBytes("testValue");
+
+  private static ObjectMapper mapper = new ObjectMapper();
+
+  /**
+   * Test the client Operations' JSON encoding to ensure that produced JSON is 
+   * parseable and that the details are present and not corrupted.
+   * @throws IOException
+   */
+  @Test
+  public void testOperationJSON()
+      throws IOException {
+    // produce a Scan Operation
+    Scan scan = new Scan(ROW);
+    scan.addColumn(FAMILY, QUALIFIER);
+    // get its JSON representation, and parse it
+    String json = scan.toJSON();
+    Map<String, Object> parsedJSON = mapper.readValue(json, HashMap.class);
+    // check for the row
+    assertEquals("startRow incorrect in Scan.toJSON()",
+        Bytes.toStringBinary(ROW), parsedJSON.get("startRow"));
+    // check for the family and the qualifier.
+    List familyInfo = (List) ((Map) parsedJSON.get("families")).get(
+        Bytes.toStringBinary(FAMILY));
+    assertNotNull("Family absent in Scan.toJSON()", familyInfo);
+    assertEquals("Qualifier absent in Scan.toJSON()", 1, familyInfo.size());
+    assertEquals("Qualifier incorrect in Scan.toJSON()",
+        Bytes.toStringBinary(QUALIFIER),
+        familyInfo.get(0));
+
+    // produce a Get Operation
+    Get get = new Get(ROW);
+    get.addColumn(FAMILY, QUALIFIER);
+    // get its JSON representation, and parse it
+    json = get.toJSON();
+    parsedJSON = mapper.readValue(json, HashMap.class);
+    // check for the row
+    assertEquals("row incorrect in Get.toJSON()",
+        Bytes.toStringBinary(ROW), parsedJSON.get("row"));
+    // check for the family and the qualifier.
+    familyInfo = (List) ((Map) parsedJSON.get("families")).get(
+        Bytes.toStringBinary(FAMILY));
+    assertNotNull("Family absent in Get.toJSON()", familyInfo);
+    assertEquals("Qualifier absent in Get.toJSON()", 1, familyInfo.size());
+    assertEquals("Qualifier incorrect in Get.toJSON()",
+        Bytes.toStringBinary(QUALIFIER),
+        familyInfo.get(0));
+
+    // produce a Put operation
+    Put put = new Put(ROW);
+    put.add(FAMILY, QUALIFIER, VALUE);
+    // get its JSON representation, and parse it
+    json = put.toJSON();
+    parsedJSON = mapper.readValue(json, HashMap.class);
+    // check for the row
+    assertEquals("row absent in Put.toJSON()",
+        Bytes.toStringBinary(ROW), parsedJSON.get("row"));
+    // check for the family and the qualifier.
+    familyInfo = (List) ((Map) parsedJSON.get("families")).get(
+        Bytes.toStringBinary(FAMILY));
+    assertNotNull("Family absent in Put.toJSON()", familyInfo);
+    assertEquals("KeyValue absent in Put.toJSON()", 1, familyInfo.size());
+    Map kvMap = (Map) familyInfo.get(0);
+    assertEquals("Qualifier incorrect in Put.toJSON()",
+        Bytes.toStringBinary(QUALIFIER),
+        kvMap.get("qualifier"));
+    assertEquals("Value length incorrect in Put.toJSON()", 
+        VALUE.length, kvMap.get("vlen"));
+
+    // produce a Delete operation
+    Delete delete = new Delete(ROW);
+    delete.deleteColumn(FAMILY, QUALIFIER);
+    // get its JSON representation, and parse it
+    json = delete.toJSON();
+    parsedJSON = mapper.readValue(json, HashMap.class);
+    // check for the row
+    assertEquals("row absent in Delete.toJSON()",
+        Bytes.toStringBinary(ROW), parsedJSON.get("row"));
+    // check for the family and the qualifier.
+    familyInfo = (List) ((Map) parsedJSON.get("families")).get(
+        Bytes.toStringBinary(FAMILY));
+    assertNotNull("Family absent in Delete.toJSON()", familyInfo);
+    assertEquals("KeyValue absent in Delete.toJSON()", 1, familyInfo.size());
+    kvMap = (Map) familyInfo.get(0);
+    assertEquals("Qualifier incorrect in Delete.toJSON()", 
+        Bytes.toStringBinary(QUALIFIER), kvMap.get("qualifier"));
+  }
+}


Reply via email to