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.nio.ByteBuffer;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.NavigableMap;
28  import java.util.TreeMap;
29  import java.util.UUID;
30  
31  import org.apache.hadoop.hbase.classification.InterfaceAudience;
32  import org.apache.hadoop.hbase.classification.InterfaceStability;
33  import org.apache.hadoop.hbase.Cell;
34  import org.apache.hadoop.hbase.CellScannable;
35  import org.apache.hadoop.hbase.CellScanner;
36  import org.apache.hadoop.hbase.CellUtil;
37  import org.apache.hadoop.hbase.HConstants;
38  import org.apache.hadoop.hbase.KeyValue;
39  import org.apache.hadoop.hbase.KeyValueUtil;
40  import org.apache.hadoop.hbase.Tag;
41  import org.apache.hadoop.hbase.exceptions.DeserializationException;
42  import org.apache.hadoop.hbase.io.HeapSize;
43  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
44  import org.apache.hadoop.hbase.security.access.AccessControlConstants;
45  import org.apache.hadoop.hbase.security.access.Permission;
46  import org.apache.hadoop.hbase.security.visibility.CellVisibility;
47  import org.apache.hadoop.hbase.security.visibility.VisibilityConstants;
48  import org.apache.hadoop.hbase.util.Bytes;
49  import org.apache.hadoop.hbase.util.ClassSize;
50  
51  import com.google.common.collect.ArrayListMultimap;
52  import com.google.common.collect.ListMultimap;
53  import com.google.common.collect.Lists;
54  import com.google.common.io.ByteArrayDataInput;
55  import com.google.common.io.ByteArrayDataOutput;
56  import com.google.common.io.ByteStreams;
57  
58  @InterfaceAudience.Public
59  @InterfaceStability.Evolving
60  public abstract class Mutation extends OperationWithAttributes implements Row, CellScannable,
61      HeapSize {
62    public static final long MUTATION_OVERHEAD = ClassSize.align(
63        // This
64        ClassSize.OBJECT +
65        // row + OperationWithAttributes.attributes
66        2 * ClassSize.REFERENCE +
67        // Timestamp
68        1 * Bytes.SIZEOF_LONG +
69        // durability
70        ClassSize.REFERENCE +
71        // familyMap
72        ClassSize.REFERENCE +
73        // familyMap
74        ClassSize.TREEMAP +
75        // priority
76        ClassSize.INTEGER
77    );
78  
79    /**
80     * The attribute for storing the list of clusters that have consumed the change.
81     */
82    private static final String CONSUMED_CLUSTER_IDS = "_cs.id";
83  
84    /**
85     * The attribute for storing TTL for the result of the mutation.
86     */
87    private static final String OP_ATTRIBUTE_TTL = "_ttl";
88  
89    /**
90     * @deprecated this field is private as of HBase 2.0.
91     */
92    @Deprecated
93    protected static final String RETURN_RESULTS = "_rr_";
94  
95    protected byte [] row = null;
96    protected long ts = HConstants.LATEST_TIMESTAMP;
97    protected Durability durability = Durability.USE_DEFAULT;
98  
99    // A Map sorted by column family.
100   protected NavigableMap<byte [], List<Cell>> familyMap =
101     new TreeMap<byte [], List<Cell>>(Bytes.BYTES_COMPARATOR);
102 
103   @Override
104   public CellScanner cellScanner() {
105     return CellUtil.createCellScanner(getFamilyCellMap());
106   }
107 
108   /**
109    * Creates an empty list if one doesn't exist for the given column family
110    * or else it returns the associated list of Cell objects.
111    *
112    * @param family column family
113    * @return a list of Cell objects, returns an empty list if one doesn't exist.
114    */
115   List<Cell> getCellList(byte[] family) {
116     List<Cell> list = this.familyMap.get(family);
117     if (list == null) {
118       list = new ArrayList<Cell>();
119       this.familyMap.put(family, list);
120     }
121     return list;
122   }
123 
124   /*
125    * Create a KeyValue with this objects row key and the Put identifier.
126    *
127    * @return a KeyValue with this objects row key and the Put identifier.
128    */
129   KeyValue createPutKeyValue(byte[] family, byte[] qualifier, long ts, byte[] value) {
130     return new KeyValue(this.row, family, qualifier, ts, KeyValue.Type.Put, value);
131   }
132 
133   /**
134    * Create a KeyValue with this objects row key and the Put identifier.
135    * @param family
136    * @param qualifier
137    * @param ts
138    * @param value
139    * @param tags - Specify the Tags as an Array {@link KeyValue.Tag}
140    * @return a KeyValue with this objects row key and the Put identifier.
141    */
142   KeyValue createPutKeyValue(byte[] family, byte[] qualifier, long ts, byte[] value, Tag[] tags) {
143     KeyValue kvWithTag = new KeyValue(this.row, family, qualifier, ts, value, tags);
144     return kvWithTag;
145   }
146 
147   /*
148    * Create a KeyValue with this objects row key and the Put identifier.
149    *
150    * @return a KeyValue with this objects row key and the Put identifier.
151    */
152   KeyValue createPutKeyValue(byte[] family, ByteBuffer qualifier, long ts, ByteBuffer value,
153                              Tag[] tags) {
154     return new KeyValue(this.row, 0, this.row == null ? 0 : this.row.length,
155         family, 0, family == null ? 0 : family.length,
156         qualifier, ts, KeyValue.Type.Put, value, tags != null ? Arrays.asList(tags) : null);
157   }
158 
159   /**
160    * Compile the column family (i.e. schema) information
161    * into a Map. Useful for parsing and aggregation by debugging,
162    * logging, and administration tools.
163    * @return Map
164    */
165   @Override
166   public Map<String, Object> getFingerprint() {
167     Map<String, Object> map = new HashMap<String, Object>();
168     List<String> families = new ArrayList<String>();
169     // ideally, we would also include table information, but that information
170     // is not stored in each Operation instance.
171     map.put("families", families);
172     for (Map.Entry<byte [], List<Cell>> entry : this.familyMap.entrySet()) {
173       families.add(Bytes.toStringBinary(entry.getKey()));
174     }
175     return map;
176   }
177 
178   /**
179    * Compile the details beyond the scope of getFingerprint (row, columns,
180    * timestamps, etc.) into a Map along with the fingerprinted information.
181    * Useful for debugging, logging, and administration tools.
182    * @param maxCols a limit on the number of columns output prior to truncation
183    * @return Map
184    */
185   @Override
186   public Map<String, Object> toMap(int maxCols) {
187     // we start with the fingerprint map and build on top of it.
188     Map<String, Object> map = getFingerprint();
189     // replace the fingerprint's simple list of families with a
190     // map from column families to lists of qualifiers and kv details
191     Map<String, List<Map<String, Object>>> columns =
192       new HashMap<String, List<Map<String, Object>>>();
193     map.put("families", columns);
194     map.put("row", Bytes.toStringBinary(this.row));
195     int colCount = 0;
196     // iterate through all column families affected
197     for (Map.Entry<byte [], List<Cell>> entry : this.familyMap.entrySet()) {
198       // map from this family to details for each cell affected within the family
199       List<Map<String, Object>> qualifierDetails = new ArrayList<Map<String, Object>>();
200       columns.put(Bytes.toStringBinary(entry.getKey()), qualifierDetails);
201       colCount += entry.getValue().size();
202       if (maxCols <= 0) {
203         continue;
204       }
205       // add details for each cell
206       for (Cell cell: entry.getValue()) {
207         if (--maxCols <= 0 ) {
208           continue;
209         }
210         Map<String, Object> cellMap = cellToStringMap(cell);
211         qualifierDetails.add(cellMap);
212       }
213     }
214     map.put("totalColumns", colCount);
215     // add the id if set
216     if (getId() != null) {
217       map.put("id", getId());
218     }
219     // Add the TTL if set
220     // Long.MAX_VALUE is the default, and is interpreted to mean this attribute
221     // has not been set.
222     if (getTTL() != Long.MAX_VALUE) {
223       map.put("ttl", getTTL());
224     }
225     return map;
226   }
227 
228   private static Map<String, Object> cellToStringMap(Cell c) {
229     Map<String, Object> stringMap = new HashMap<String, Object>();
230     stringMap.put("qualifier", Bytes.toStringBinary(c.getQualifierArray(), c.getQualifierOffset(),
231                 c.getQualifierLength()));
232     stringMap.put("timestamp", c.getTimestamp());
233     stringMap.put("vlen", c.getValueLength());
234     List<Tag> tags = Tag.asList(c.getTagsArray(), c.getTagsOffset(), c.getTagsLength());
235     if (tags != null) {
236       List<String> tagsString = new ArrayList<String>();
237       for (Tag t : tags) {
238         tagsString.add((t.getType()) + ":" + Bytes.toStringBinary(t.getValue()));
239       }
240       stringMap.put("tag", tagsString);
241     }
242     return stringMap;
243   }
244 
245   /**
246    * @deprecated Use {@link #getDurability()} instead.
247    * @return true if edits should be applied to WAL, false if not
248    */
249   @Deprecated
250   public boolean getWriteToWAL() {
251     return this.durability != Durability.SKIP_WAL;
252   }
253 
254   /**
255    * Set whether this Delete should be written to the WAL or not.
256    * Not writing the WAL means you may lose edits on server crash.
257    * This method will reset any changes made via {@link #setDurability(Durability)}
258    * @param write true if edits should be written to WAL, false if not
259    * @deprecated Use {@link #setDurability(Durability)} instead.
260    */
261   @Deprecated
262   public Mutation setWriteToWAL(boolean write) {
263     setDurability(write ? Durability.USE_DEFAULT : Durability.SKIP_WAL);
264     return this;
265   }
266 
267   /**
268    * Set the durability for this mutation
269    * @param d
270    */
271   public Mutation setDurability(Durability d) {
272     this.durability = d;
273     return this;
274   }
275 
276   /** Get the current durability */
277   public Durability getDurability() {
278     return this.durability;
279   }
280 
281   /**
282    * Method for retrieving the put's familyMap
283    * @return familyMap
284    */
285   public NavigableMap<byte [], List<Cell>> getFamilyCellMap() {
286     return this.familyMap;
287   }
288 
289   /**
290    * Method for setting the put's familyMap
291    */
292   public Mutation setFamilyCellMap(NavigableMap<byte [], List<Cell>> map) {
293     // TODO: Shut this down or move it up to be a Constructor.  Get new object rather than change
294     // this internal data member.
295     this.familyMap = map;
296     return this;
297   }
298 
299   /**
300    * Method for retrieving the put's familyMap that is deprecated and inefficient.
301    * @return the map
302    * @deprecated use {@link #getFamilyCellMap()} instead.
303    */
304   @Deprecated
305   public NavigableMap<byte [], List<KeyValue>> getFamilyMap() {
306     TreeMap<byte[], List<KeyValue>> fm =
307         new TreeMap<byte[], List<KeyValue>>(Bytes.BYTES_COMPARATOR);
308     for (Map.Entry<byte[], List<Cell>> e : familyMap.entrySet()) {
309       List<KeyValue> kvl = new ArrayList<KeyValue>(e.getValue().size());
310       for (Cell c : e.getValue()) {
311         kvl.add(KeyValueUtil.ensureKeyValue(c));
312       }
313       fm.put(e.getKey(), kvl);
314     }
315     return fm;
316   }
317 
318   /**
319    * Method for setting the put's familyMap that is deprecated and inefficient.
320    * @deprecated use {@link #setFamilyCellMap(NavigableMap)} instead.
321    */
322   @Deprecated
323   public Mutation setFamilyMap(NavigableMap<byte [], List<KeyValue>> map) {
324     TreeMap<byte[], List<Cell>> fm = new TreeMap<byte[], List<Cell>>(Bytes.BYTES_COMPARATOR);
325     for (Map.Entry<byte[], List<KeyValue>> e : map.entrySet()) {
326       fm.put(e.getKey(), Lists.<Cell>newArrayList(e.getValue()));
327     }
328     this.familyMap = fm;
329     return this;
330   }
331 
332   /**
333    * Method to check if the familyMap is empty
334    * @return true if empty, false otherwise
335    */
336   public boolean isEmpty() {
337     return familyMap.isEmpty();
338   }
339 
340   /**
341    * Method for retrieving the delete's row
342    * @return row
343    */
344   @Override
345   public byte [] getRow() {
346     return this.row;
347   }
348 
349   @Override
350   public int compareTo(final Row d) {
351     return Bytes.compareTo(this.getRow(), d.getRow());
352   }
353 
354   /**
355    * Method for retrieving the timestamp
356    * @return timestamp
357    */
358   public long getTimeStamp() {
359     return this.ts;
360   }
361 
362   /**
363    * Marks that the clusters with the given clusterIds have consumed the mutation
364    * @param clusterIds of the clusters that have consumed the mutation
365    */
366   public Mutation setClusterIds(List<UUID> clusterIds) {
367     ByteArrayDataOutput out = ByteStreams.newDataOutput();
368     out.writeInt(clusterIds.size());
369     for (UUID clusterId : clusterIds) {
370       out.writeLong(clusterId.getMostSignificantBits());
371       out.writeLong(clusterId.getLeastSignificantBits());
372     }
373     setAttribute(CONSUMED_CLUSTER_IDS, out.toByteArray());
374     return this;
375   }
376 
377   /**
378    * @return the set of clusterIds that have consumed the mutation
379    */
380   public List<UUID> getClusterIds() {
381     List<UUID> clusterIds = new ArrayList<UUID>();
382     byte[] bytes = getAttribute(CONSUMED_CLUSTER_IDS);
383     if(bytes != null) {
384       ByteArrayDataInput in = ByteStreams.newDataInput(bytes);
385       int numClusters = in.readInt();
386       for(int i=0; i<numClusters; i++){
387         clusterIds.add(new UUID(in.readLong(), in.readLong()));
388       }
389     }
390     return clusterIds;
391   }
392 
393   /**
394    * Sets the visibility expression associated with cells in this Mutation.
395    * @param expression
396    */
397   public Mutation setCellVisibility(CellVisibility expression) {
398     this.setAttribute(VisibilityConstants.VISIBILITY_LABELS_ATTR_KEY, ProtobufUtil
399         .toCellVisibility(expression).toByteArray());
400     return this;
401   }
402 
403   /**
404    * @return CellVisibility associated with cells in this Mutation.
405    * @throws DeserializationException
406    */
407   public CellVisibility getCellVisibility() throws DeserializationException {
408     byte[] cellVisibilityBytes = this.getAttribute(VisibilityConstants.VISIBILITY_LABELS_ATTR_KEY);
409     if (cellVisibilityBytes == null) return null;
410     return ProtobufUtil.toCellVisibility(cellVisibilityBytes);
411   }
412 
413   /**
414    * Number of KeyValues carried by this Mutation.
415    * @return the total number of KeyValues
416    */
417   public int size() {
418     int size = 0;
419     for (List<Cell> cells : this.familyMap.values()) {
420       size += cells.size();
421     }
422     return size;
423   }
424 
425   /**
426    * @return the number of different families
427    */
428   public int numFamilies() {
429     return familyMap.size();
430   }
431 
432   /**
433    * @return Calculate what Mutation adds to class heap size.
434    */
435   @Override
436   public long heapSize() {
437     long heapsize = MUTATION_OVERHEAD;
438     // Adding row
439     heapsize += ClassSize.align(ClassSize.ARRAY + this.row.length);
440 
441     // Adding map overhead
442     heapsize +=
443       ClassSize.align(this.familyMap.size() * ClassSize.MAP_ENTRY);
444     for(Map.Entry<byte [], List<Cell>> entry : this.familyMap.entrySet()) {
445       //Adding key overhead
446       heapsize +=
447         ClassSize.align(ClassSize.ARRAY + entry.getKey().length);
448 
449       //This part is kinds tricky since the JVM can reuse references if you
450       //store the same value, but have a good match with SizeOf at the moment
451       //Adding value overhead
452       heapsize += ClassSize.align(ClassSize.ARRAYLIST);
453       int size = entry.getValue().size();
454       heapsize += ClassSize.align(ClassSize.ARRAY +
455           size * ClassSize.REFERENCE);
456 
457       for(Cell cell : entry.getValue()) {
458         heapsize += CellUtil.estimatedHeapSizeOf(cell);
459       }
460     }
461     heapsize += getAttributeSize();
462     heapsize += extraHeapSize();
463     return ClassSize.align(heapsize);
464   }
465 
466   /**
467    * @return The serialized ACL for this operation, or null if none
468    */
469   public byte[] getACL() {
470     return getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL);
471   }
472 
473   /**
474    * @param user User short name
475    * @param perms Permissions for the user
476    */
477   public Mutation setACL(String user, Permission perms) {
478     setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL,
479       ProtobufUtil.toUsersAndPermissions(user, perms).toByteArray());
480     return this;
481   }
482 
483   /**
484    * @param perms A map of permissions for a user or users
485    */
486   public Mutation setACL(Map<String, Permission> perms) {
487     ListMultimap<String, Permission> permMap = ArrayListMultimap.create();
488     for (Map.Entry<String, Permission> entry : perms.entrySet()) {
489       permMap.put(entry.getKey(), entry.getValue());
490     }
491     setAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL,
492       ProtobufUtil.toUsersAndPermissions(permMap).toByteArray());
493     return this;
494   }
495 
496   /**
497    * Return the TTL requested for the result of the mutation, in milliseconds.
498    * @return the TTL requested for the result of the mutation, in milliseconds,
499    * or Long.MAX_VALUE if unset
500    */
501   public long getTTL() {
502     byte[] ttlBytes = getAttribute(OP_ATTRIBUTE_TTL);
503     if (ttlBytes != null) {
504       return Bytes.toLong(ttlBytes);
505     }
506     return Long.MAX_VALUE;
507   }
508 
509   /**
510    * Set the TTL desired for the result of the mutation, in milliseconds.
511    * @param ttl the TTL desired for the result of the mutation, in milliseconds
512    * @return this
513    */
514   public Mutation setTTL(long ttl) {
515     setAttribute(OP_ATTRIBUTE_TTL, Bytes.toBytes(ttl));
516     return this;
517   }
518 
519   /**
520    * @return current value for returnResults
521    */
522   // Used by Increment and Append only.
523   @InterfaceAudience.Private
524   protected boolean isReturnResults() {
525     byte[] v = getAttribute(RETURN_RESULTS);
526     return v == null ? true : Bytes.toBoolean(v);
527   }
528 
529   @InterfaceAudience.Private
530   // Used by Increment and Append only.
531   protected Mutation setReturnResults(boolean returnResults) {
532     setAttribute(RETURN_RESULTS, Bytes.toBytes(returnResults));
533     return this;
534   }
535 
536   /**
537    * Subclasses should override this method to add the heap size of their own fields.
538    * @return the heap size to add (will be aligned).
539    */
540   protected long extraHeapSize(){
541     return 0L;
542   }
543 
544 
545   /**
546    * @param row Row to check
547    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
548    * &gt; {@link HConstants#MAX_ROW_LENGTH}
549    * @return <code>row</code>
550    */
551   static byte [] checkRow(final byte [] row) {
552     return checkRow(row, 0, row == null? 0: row.length);
553   }
554 
555   /**
556    * @param row Row to check
557    * @param offset
558    * @param length
559    * @throws IllegalArgumentException Thrown if <code>row</code> is empty or null or
560    * &gt; {@link HConstants#MAX_ROW_LENGTH}
561    * @return <code>row</code>
562    */
563   static byte [] checkRow(final byte [] row, final int offset, final int length) {
564     if (row == null) {
565       throw new IllegalArgumentException("Row buffer is null");
566     }
567     if (length == 0) {
568       throw new IllegalArgumentException("Row length is 0");
569     }
570     if (length > HConstants.MAX_ROW_LENGTH) {
571       throw new IllegalArgumentException("Row length " + length + " is > " +
572         HConstants.MAX_ROW_LENGTH);
573     }
574     return row;
575   }
576 
577   static void checkRow(ByteBuffer row) {
578     if (row == null) {
579       throw new IllegalArgumentException("Row buffer is null");
580     }
581     if (row.remaining() == 0) {
582       throw new IllegalArgumentException("Row length is 0");
583     }
584     if (row.remaining() > HConstants.MAX_ROW_LENGTH) {
585       throw new IllegalArgumentException("Row length " + row.remaining() + " is > " +
586           HConstants.MAX_ROW_LENGTH);
587     }
588   }
589 }