View Javadoc

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<Cell>> familyMap =
69      new TreeMap<byte [], List<Cell>>(Bytes.BYTES_COMPARATOR);
70  
71    @Override
72    public CellScanner cellScanner() {
73      return CellUtil.createCellScanner(getFamilyCellMap());
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<Cell> getCellList(byte[] family) {
84      List<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<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<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    * Set the durability for this mutation. If this is set to true,
183    * the default durability of the table is set.
184    * @param writeToWal
185    */
186   @Deprecated
187   public void setWriteToWAL(boolean writeToWal) {
188     if(!writeToWal) {
189       setDurability(Durability.SKIP_WAL);
190     } else {
191     // This is required to handle the case where this method is
192     // called twice, first with writeToWal = false,
193     // and then with writeToWal = true
194       setDurability(Durability.USE_DEFAULT);
195     }
196   }
197 
198   /**
199    * Get the durability for this mutation.
200    * @return - true if this mutation is set to write to the WAL either
201    * synchronously, asynchronously or fsync to disk on the file system.
202    * - to get the exact durability, use the {#getDurability} method.
203    */
204   @Deprecated
205   public boolean getWriteToWAL() {
206     return Durability.SKIP_WAL != getDurability();
207   }
208 
209   /*
210    * Method for retrieving the put's familyMap  (family -> KeyValues)
211    * Application should use the getFamilyCellMap and the Cell interface instead of KeyValue.
212    *
213    * @return familyMap
214    */
215   @Deprecated
216   public Map<byte[], List<KeyValue>> getFamilyMap() {
217     Map<byte[], List<KeyValue>> fm = new TreeMap();
218     for (Map.Entry<byte[], List<Cell>> e : this.familyMap.entrySet()) {
219       byte[] family = e.getKey();
220       List<Cell> cells = e.getValue();
221       List<KeyValue> kvs = new ArrayList(cells.size());
222       for (Cell c : cells) {
223          KeyValue kv = KeyValueUtil.ensureKeyValue(c);
224          kvs.add(kv);
225        }
226        fm.put(family, kvs);
227      }
228      return fm;
229    }
230   
231   /**
232    * Method for retrieving the put's familyMap
233    * @return familyMap
234    */
235   public NavigableMap<byte [], List<Cell>> getFamilyCellMap() {
236     return this.familyMap;
237   }
238 
239   /**
240    * Method for setting the put's familyMap
241    */
242   public void setFamilyMap(NavigableMap<byte [], List<Cell>> map) {
243     // TODO: Shut this down or move it up to be a Constructor.  Get new object rather than change
244     // this internal data member.
245     this.familyMap = map;
246   }
247 
248   /**
249    * Method to check if the familyMap is empty
250    * @return true if empty, false otherwise
251    */
252   public boolean isEmpty() {
253     return familyMap.isEmpty();
254   }
255 
256   /**
257    * Method for retrieving the delete's row
258    * @return row
259    */
260   @Override
261   public byte [] getRow() {
262     return this.row;
263   }
264 
265   public int compareTo(final Row d) {
266     return Bytes.compareTo(this.getRow(), d.getRow());
267   }
268 
269   /**
270    * Method for retrieving the timestamp
271    * @return timestamp
272    */
273   public long getTimeStamp() {
274     return this.ts;
275   }
276 
277   /**
278    * Set the replication custer id.
279    * @param clusterId
280    */
281   public void setClusterId(UUID clusterId) {
282     if (clusterId == null) return;
283     byte[] val = new byte[2*Bytes.SIZEOF_LONG];
284     Bytes.putLong(val, 0, clusterId.getMostSignificantBits());
285     Bytes.putLong(val, Bytes.SIZEOF_LONG, clusterId.getLeastSignificantBits());
286     setAttribute(CLUSTER_ID_ATTR, val);
287   }
288 
289   /**
290    * @return The replication cluster id.
291    */
292   public UUID getClusterId() {
293     byte[] attr = getAttribute(CLUSTER_ID_ATTR);
294     if (attr == null) {
295       return HConstants.DEFAULT_CLUSTER_ID;
296     }
297     return new UUID(Bytes.toLong(attr,0), Bytes.toLong(attr, Bytes.SIZEOF_LONG));
298   }
299 
300   /**
301    * Number of KeyValues carried by this Mutation.
302    * @return the total number of KeyValues
303    */
304   public int size() {
305     int size = 0;
306     for (List<Cell> cells : this.familyMap.values()) {
307       size += cells.size();
308     }
309     return size;
310   }
311 
312   /**
313    * @return the number of different families
314    */
315   public int numFamilies() {
316     return familyMap.size();
317   }
318 
319   /**
320    * @return Calculate what Mutation adds to class heap size.
321    */
322   @Override
323   public long heapSize() {
324     long heapsize = MUTATION_OVERHEAD;
325     // Adding row
326     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
327 
328     // Adding map overhead
329     heapsize +=
330       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
331     for(Map.Entry<byte [], List<Cell>> entry : this.familyMap.entrySet()) {
332       //Adding key overhead
333       heapsize +=
334         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
335 
336       //This part is kinds tricky since the JVM can reuse references if you
337       //store the same value, but have a good match with SizeOf at the moment
338       //Adding value overhead
339       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
340       int size = entry.getValue().size();
341       heapsize += ClassSize.align(ClassSize.ARRAY +
342           size * ClassSize.REFERENCE);
343 
344       for(Cell cell : entry.getValue()) {
345         KeyValue kv = KeyValueUtil.ensureKeyValue(cell);
346         heapsize += kv.heapSize();
347       }
348     }
349     heapsize += getAttributeSize();
350     heapsize += extraHeapSize();
351     return ClassSize.align(heapsize);
352   }
353 
354   /**
355    * Subclasses should override this method to add the heap size of their own fields.
356    * @return the heap size to add (will be aligned).
357    */
358   protected long extraHeapSize(){
359     return 0L;
360   }
361 
362 
363   /**
364    * @param row Row to check
365    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
366    * &gt; {@link HConstants#MAX_ROW_LENGTH}
367    * @return <code>row</code>
368    */
369   static byte [] checkRow(final byte [] row) {
370     return checkRow(row, 0, row == null? 0: row.length);
371   }
372 
373   /**
374    * @param row Row to check
375    * @param offset
376    * @param length
377    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
378    * &gt; {@link HConstants#MAX_ROW_LENGTH}
379    * @return <code>row</code>
380    */
381   static byte [] checkRow(final byte [] row, final int offset, final int length) {
382     if (row == null) {
383       throw new IllegalArgumentException("Row buffer is null");
384     }
385     if (length == 0) {
386       throw new IllegalArgumentException("Row length is 0");
387     }
388     if (length > HConstants.MAX_ROW_LENGTH) {
389       throw new IllegalArgumentException("Row length " + length + " is > " +
390         HConstants.MAX_ROW_LENGTH);
391     }
392     return row;
393   }
394 }