1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.client;
20  
21  import java.util.ArrayList;
22  import java.util.HashMap;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.NavigableMap;
26  import java.util.TreeMap;
27  import java.util.UUID;
28  
29  import org.apache.hadoop.classification.InterfaceAudience;
30  import org.apache.hadoop.classification.InterfaceStability;
31  import org.apache.hadoop.hbase.Cell;
32  import org.apache.hadoop.hbase.CellScannable;
33  import org.apache.hadoop.hbase.CellScanner;
34  import org.apache.hadoop.hbase.CellUtil;
35  import org.apache.hadoop.hbase.HConstants;
36  import org.apache.hadoop.hbase.KeyValue;
37  import org.apache.hadoop.hbase.KeyValueUtil;
38  import org.apache.hadoop.hbase.io.HeapSize;
39  import org.apache.hadoop.hbase.util.Bytes;
40  import org.apache.hadoop.hbase.util.ClassSize;
41  
42  @InterfaceAudience.Public
43  @InterfaceStability.Evolving
44  public abstract class Mutation extends OperationWithAttributes implements Row, CellScannable,
45      HeapSize {
46    public static final long MUTATION_OVERHEAD = ClassSize.align(
47        // This
48        ClassSize.OBJECT +
49        // row + OperationWithAttributes.attributes
50        2 * ClassSize.REFERENCE +
51        // Timestamp
52        1 * Bytes.SIZEOF_LONG +
53        // durability
54        ClassSize.REFERENCE +
55        // familyMap
56        ClassSize.REFERENCE +
57        // familyMap
58        ClassSize.TREEMAP);
59  
60    // Attribute used in Mutations to indicate the originating cluster.
61    private static final String CLUSTER_ID_ATTR = "_c.id_";
62  
63    protected byte [] row = null;
64    protected long ts = HConstants.LATEST_TIMESTAMP;
65    protected Durability durability = Durability.USE_DEFAULT;
66    
67    // A Map sorted by column family.
68    protected NavigableMap<byte [], List<? extends Cell>> familyMap =
69      new TreeMap<byte [], List<? extends Cell>>(Bytes.BYTES_COMPARATOR);
70  
71    @Override
72    public CellScanner cellScanner() {
73      return CellUtil.createCellScanner(getFamilyMap());
74    }
75  
76    /**
77     * Creates an empty list if one doesn't exist for the given column family
78     * or else it returns the associated list of Cell objects.
79     *
80     * @param family column family
81     * @return a list of Cell objects, returns an empty list if one doesn't exist.
82     */
83    List<? extends Cell> getCellList(byte[] family) {
84      List<? extends Cell> list = this.familyMap.get(family);
85      if (list == null) {
86        list = new ArrayList<Cell>();
87      }
88      return list;
89    }
90  
91    /*
92     * Create a nnnnnnnn with this objects row key and the Put identifier.
93     *
94     * @return a KeyValue with this objects row key and the Put identifier.
95     */
96    KeyValue createPutKeyValue(byte[] family, byte[] qualifier, long ts, byte[] value) {
97      return new KeyValue(this.row, family, qualifier, ts, KeyValue.Type.Put, value);
98    }
99  
100   /**
101    * Compile the column family (i.e. schema) information
102    * into a Map. Useful for parsing and aggregation by debugging,
103    * logging, and administration tools.
104    * @return Map
105    */
106   @Override
107   public Map<String, Object> getFingerprint() {
108     Map<String, Object> map = new HashMap<String, Object>();
109     List<String> families = new ArrayList<String>();
110     // ideally, we would also include table information, but that information
111     // is not stored in each Operation instance.
112     map.put("families", families);
113     for (Map.Entry<byte [], List<? extends Cell>> entry : this.familyMap.entrySet()) {
114       families.add(Bytes.toStringBinary(entry.getKey()));
115     }
116     return map;
117   }
118 
119   /**
120    * Compile the details beyond the scope of getFingerprint (row, columns,
121    * timestamps, etc.) into a Map along with the fingerprinted information.
122    * Useful for debugging, logging, and administration tools.
123    * @param maxCols a limit on the number of columns output prior to truncation
124    * @return Map
125    */
126   @Override
127   public Map<String, Object> toMap(int maxCols) {
128     // we start with the fingerprint map and build on top of it.
129     Map<String, Object> map = getFingerprint();
130     // replace the fingerprint's simple list of families with a
131     // map from column families to lists of qualifiers and kv details
132     Map<String, List<Map<String, Object>>> columns =
133       new HashMap<String, List<Map<String, Object>>>();
134     map.put("families", columns);
135     map.put("row", Bytes.toStringBinary(this.row));
136     int colCount = 0;
137     // iterate through all column families affected
138     for (Map.Entry<byte [], List<? extends Cell>> entry : this.familyMap.entrySet()) {
139       // map from this family to details for each cell affected within the family
140       List<Map<String, Object>> qualifierDetails = new ArrayList<Map<String, Object>>();
141       columns.put(Bytes.toStringBinary(entry.getKey()), qualifierDetails);
142       colCount += entry.getValue().size();
143       if (maxCols <= 0) {
144         continue;
145       }
146       // add details for each cell
147       for (Cell cell: entry.getValue()) {
148         if (--maxCols <= 0 ) {
149           continue;
150         }
151         // KeyValue v1 expectation.  Cast for now until we go all Cell all the time.
152         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
153         Map<String, Object> kvMap = kv.toStringMap();
154         // row and family information are already available in the bigger map
155         kvMap.remove("row");
156         kvMap.remove("family");
157         qualifierDetails.add(kvMap);
158       }
159     }
160     map.put("totalColumns", colCount);
161     // add the id if set
162     if (getId() != null) {
163       map.put("id", getId());
164     }
165     return map;
166   }
167 
168   /**
169    * Set the durability for this mutation
170    * @param d
171    */
172   public void setDurability(Durability d) {
173     this.durability = d;
174   }
175 
176   /** Get the current durability */
177   public Durability getDurability() {
178     return this.durability;
179   }
180 
181   /**
182    * Method for retrieving the put's familyMap
183    * @return familyMap
184    */
185   public NavigableMap<byte [], List<? extends Cell>> getFamilyMap() {
186     return this.familyMap;
187   }
188 
189   /**
190    * Method for setting the put's familyMap
191    */
192   public void setFamilyMap(NavigableMap<byte [], List<? extends Cell>> map) {
193     // TODO: Shut this down or move it up to be a Constructor.  Get new object rather than change
194     // this internal data member.
195     this.familyMap = map;
196   }
197 
198   /**
199    * Method to check if the familyMap is empty
200    * @return true if empty, false otherwise
201    */
202   public boolean isEmpty() {
203     return familyMap.isEmpty();
204   }
205 
206   /**
207    * Method for retrieving the delete's row
208    * @return row
209    */
210   @Override
211   public byte [] getRow() {
212     return this.row;
213   }
214 
215   public int compareTo(final Row d) {
216     return Bytes.compareTo(this.getRow(), d.getRow());
217   }
218 
219   /**
220    * Method for retrieving the timestamp
221    * @return timestamp
222    */
223   public long getTimeStamp() {
224     return this.ts;
225   }
226 
227   /**
228    * Set the replication custer id.
229    * @param clusterId
230    */
231   public void setClusterId(UUID clusterId) {
232     if (clusterId == null) return;
233     byte[] val = new byte[2*Bytes.SIZEOF_LONG];
234     Bytes.putLong(val, 0, clusterId.getMostSignificantBits());
235     Bytes.putLong(val, Bytes.SIZEOF_LONG, clusterId.getLeastSignificantBits());
236     setAttribute(CLUSTER_ID_ATTR, val);
237   }
238 
239   /**
240    * @return The replication cluster id.
241    */
242   public UUID getClusterId() {
243     byte[] attr = getAttribute(CLUSTER_ID_ATTR);
244     if (attr == null) {
245       return HConstants.DEFAULT_CLUSTER_ID;
246     }
247     return new UUID(Bytes.toLong(attr,0), Bytes.toLong(attr, Bytes.SIZEOF_LONG));
248   }
249 
250   /**
251    * Number of KeyValues carried by this Mutation.
252    * @return the total number of KeyValues
253    */
254   public int size() {
255     int size = 0;
256     for (List<? extends Cell> cells : this.familyMap.values()) {
257       size += cells.size();
258     }
259     return size;
260   }
261 
262   /**
263    * @return the number of different families
264    */
265   public int numFamilies() {
266     return familyMap.size();
267   }
268 
269   /**
270    * @return Calculate what Mutation adds to class heap size.
271    */
272   @Override
273   public long heapSize() {
274     long heapsize = MUTATION_OVERHEAD;
275     // Adding row
276     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
277 
278     // Adding map overhead
279     heapsize +=
280       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
281     for(Map.Entry<byte [], List<? extends Cell>> entry : this.familyMap.entrySet()) {
282       //Adding key overhead
283       heapsize +=
284         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
285 
286       //This part is kinds tricky since the JVM can reuse references if you
287       //store the same value, but have a good match with SizeOf at the moment
288       //Adding value overhead
289       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
290       int size = entry.getValue().size();
291       heapsize += ClassSize.align(ClassSize.ARRAY +
292           size * ClassSize.REFERENCE);
293 
294       for(Cell cell : entry.getValue()) {
295         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
296         heapsize += kv.heapSize();
297       }
298     }
299     heapsize += getAttributeSize();
300     heapsize += extraHeapSize();
301     return ClassSize.align(heapsize);
302   }
303 
304   /**
305    * Subclasses should override this method to add the heap size of their own fields.
306    * @return the heap size to add (will be aligned).
307    */
308   protected long extraHeapSize(){
309     return 0L;
310   }
311 
312 
313   /**
314    * @param row Row to check
315    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
316    * &gt; {@link HConstants#MAX_ROW_LENGTH}
317    * @return <code>row</code>
318    */
319   static byte [] checkRow(final byte [] row) {
320     return checkRow(row, 0, row == null? 0: row.length);
321   }
322 
323   /**
324    * @param row Row to check
325    * @param offset
326    * @param length
327    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
328    * &gt; {@link HConstants#MAX_ROW_LENGTH}
329    * @return <code>row</code>
330    */
331   static byte [] checkRow(final byte [] row, final int offset, final int length) {
332     if (row == null) {
333       throw new IllegalArgumentException("Row buffer is null");
334     }
335     if (length == 0) {
336       throw new IllegalArgumentException("Row length is 0");
337     }
338     if (length > HConstants.MAX_ROW_LENGTH) {
339       throw new IllegalArgumentException("Row length " + length + " is > " +
340         HConstants.MAX_ROW_LENGTH);
341     }
342     return row;
343   }
344 }