View Javadoc

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