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