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  package org.apache.hadoop.hbase.security.visibility;
19  
20  import static org.apache.hadoop.hbase.TagType.VISIBILITY_TAG_TYPE;
21  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_FAMILY;
22  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABELS_TABLE_NAME;
23  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.LABEL_QUALIFIER;
24  import static org.apache.hadoop.hbase.security.visibility.VisibilityConstants.SORTED_ORDINAL_SERIALIZATION_FORMAT;
25  import static org.apache.hadoop.hbase.security.visibility.VisibilityUtils.SYSTEM_LABEL;
26  
27  import java.io.ByteArrayOutputStream;
28  import java.io.DataOutputStream;
29  import java.io.IOException;
30  import java.util.ArrayList;
31  import java.util.BitSet;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.Iterator;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Set;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.hadoop.classification.InterfaceAudience;
42  import org.apache.hadoop.conf.Configuration;
43  import org.apache.hadoop.hbase.Cell;
44  import org.apache.hadoop.hbase.CellUtil;
45  import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
46  import org.apache.hadoop.hbase.Tag;
47  import org.apache.hadoop.hbase.client.Delete;
48  import org.apache.hadoop.hbase.client.Mutation;
49  import org.apache.hadoop.hbase.client.Put;
50  import org.apache.hadoop.hbase.client.Scan;
51  import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment;
52  import org.apache.hadoop.hbase.filter.Filter;
53  import org.apache.hadoop.hbase.io.util.StreamUtils;
54  import org.apache.hadoop.hbase.regionserver.HRegion;
55  import org.apache.hadoop.hbase.regionserver.OperationStatus;
56  import org.apache.hadoop.hbase.regionserver.RegionScanner;
57  import org.apache.hadoop.hbase.security.User;
58  import org.apache.hadoop.hbase.security.access.AccessControlLists;
59  import org.apache.hadoop.hbase.util.Bytes;
60  import org.apache.hadoop.hbase.util.Pair;
61  import org.apache.hadoop.hbase.zookeeper.ZooKeeperWatcher;
62  
63  import com.google.common.collect.Lists;
64  
65  @InterfaceAudience.Private
66  public class DefaultVisibilityLabelServiceImpl implements VisibilityLabelService {
67  
68    private static final Log LOG = LogFactory.getLog(DefaultVisibilityLabelServiceImpl.class);
69  
70    // "system" label is having an ordinal value 1.
71    private static final int SYSTEM_LABEL_ORDINAL = 1;
72    private static final Tag[] LABELS_TABLE_TAGS = new Tag[1];
73    private static final byte[] DUMMY_VALUE = new byte[0];
74  
75    private volatile int ordinalCounter = -1;
76    private Configuration conf;
77    private HRegion labelsRegion;
78    private VisibilityLabelsCache labelsCache;
79    private List<ScanLabelGenerator> scanLabelGenerators;
80  
81    static {
82      ByteArrayOutputStream baos = new ByteArrayOutputStream();
83      DataOutputStream dos = new DataOutputStream(baos);
84      try {
85        StreamUtils.writeRawVInt32(dos, SYSTEM_LABEL_ORDINAL);
86      } catch (IOException e) {
87        // We write to a byte array. No Exception can happen.
88      }
89      LABELS_TABLE_TAGS[0] = new Tag(VISIBILITY_TAG_TYPE, baos.toByteArray());
90    }
91  
92    public DefaultVisibilityLabelServiceImpl() {
93  
94    }
95  
96    @Override
97    public void setConf(Configuration conf) {
98      this.conf = conf;
99    }
100 
101   @Override
102   public Configuration getConf() {
103     return this.conf;
104   }
105 
106   @Override
107   public void init(RegionCoprocessorEnvironment e) throws IOException {
108     ZooKeeperWatcher zk = e.getRegionServerServices().getZooKeeper();
109     try {
110       labelsCache = VisibilityLabelsCache.createAndGet(zk, this.conf);
111     } catch (IOException ioe) {
112       LOG.error("Error creating VisibilityLabelsCache", ioe);
113       throw ioe;
114     }
115     this.scanLabelGenerators = VisibilityUtils.getScanLabelGenerators(this.conf);
116     if (e.getRegion().getRegionInfo().getTable().equals(LABELS_TABLE_NAME)) {
117       this.labelsRegion = e.getRegion();
118       Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
119           extractLabelsAndAuths(getExistingLabelsWithAuths());
120       Map<String, Integer> labels = labelsAndUserAuths.getFirst();
121       Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
122       // Add the "system" label if it is not added into the system yet
123       addSystemLabel(this.labelsRegion, labels, userAuths);
124       int ordinal = SYSTEM_LABEL_ORDINAL; // Ordinal 1 is reserved for "system" label.
125       for (Integer i : labels.values()) {
126         if (i > ordinal) {
127           ordinal = i;
128         }
129       }
130       this.ordinalCounter = ordinal + 1;
131       if (labels.size() > 0) {
132         // If there is no data need not write to zk
133         byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(labels);
134         this.labelsCache.writeToZookeeper(serialized, true);
135         this.labelsCache.refreshLabelsCache(serialized);
136       }
137       if (userAuths.size() > 0) {
138         byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
139         this.labelsCache.writeToZookeeper(serialized, false);
140         this.labelsCache.refreshUserAuthsCache(serialized);
141       }
142     }
143   }
144 
145   protected List<List<Cell>> getExistingLabelsWithAuths() throws IOException {
146     Scan scan = new Scan();
147     RegionScanner scanner = labelsRegion.getScanner(scan);
148     List<List<Cell>> existingLabels = new ArrayList<List<Cell>>();
149     try {
150       while (true) {
151         List<Cell> cells = new ArrayList<Cell>();
152         scanner.next(cells);
153         if (cells.isEmpty()) {
154           break;
155         }
156         existingLabels.add(cells);
157       }
158     } finally {
159       scanner.close();
160     }
161     return existingLabels;
162   }
163 
164   protected Pair<Map<String, Integer>, Map<String, List<Integer>>> extractLabelsAndAuths(
165       List<List<Cell>> labelDetails) {
166     Map<String, Integer> labels = new HashMap<String, Integer>();
167     Map<String, List<Integer>> userAuths = new HashMap<String, List<Integer>>();
168     for (List<Cell> cells : labelDetails) {
169       for (Cell cell : cells) {
170         if (Bytes.equals(cell.getQualifierArray(), cell.getQualifierOffset(),
171             cell.getQualifierLength(), LABEL_QUALIFIER, 0, LABEL_QUALIFIER.length)) {
172           labels.put(
173               Bytes.toString(cell.getValueArray(), cell.getValueOffset(), cell.getValueLength()),
174               Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
175         } else {
176           // These are user cells who has authorization for this label
177           String user = Bytes.toString(cell.getQualifierArray(), cell.getQualifierOffset(),
178               cell.getQualifierLength());
179           List<Integer> auths = userAuths.get(user);
180           if (auths == null) {
181             auths = new ArrayList<Integer>();
182             userAuths.put(user, auths);
183           }
184           auths.add(Bytes.toInt(cell.getRowArray(), cell.getRowOffset()));
185         }
186       }
187     }
188     return new Pair<Map<String, Integer>, Map<String, List<Integer>>>(labels, userAuths);
189   }
190 
191   protected void addSystemLabel(HRegion region, Map<String, Integer> labels,
192       Map<String, List<Integer>> userAuths) throws IOException {
193     if (!labels.containsKey(SYSTEM_LABEL)) {
194       Put p = new Put(Bytes.toBytes(SYSTEM_LABEL_ORDINAL));
195       p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, Bytes.toBytes(SYSTEM_LABEL));
196       // Set auth for "system" label for all super users.
197       List<String> superUsers = getSystemAndSuperUsers();
198       for (String superUser : superUsers) {
199         p.addImmutable(LABELS_TABLE_FAMILY, Bytes.toBytes(superUser), DUMMY_VALUE,
200             LABELS_TABLE_TAGS);
201       }
202       region.put(p);
203       labels.put(SYSTEM_LABEL, SYSTEM_LABEL_ORDINAL);
204       for (String superUser : superUsers) {
205         List<Integer> auths = userAuths.get(superUser);
206         if (auths == null) {
207           auths = new ArrayList<Integer>(1);
208           userAuths.put(superUser, auths);
209         }
210         auths.add(SYSTEM_LABEL_ORDINAL);
211       }
212     }
213   }
214 
215   protected List<String> getSystemAndSuperUsers() throws IOException {
216     User user = User.getCurrent();
217     if (user == null) {
218       throw new IOException("Unable to obtain the current user, "
219           + "authorization checks for internal operations will not work correctly!");
220     }
221     if (LOG.isTraceEnabled()) {
222       LOG.trace("Current user name is " + user.getShortName());
223     }
224     String currentUser = user.getShortName();
225     List<String> superUsers = Lists.asList(currentUser,
226         this.conf.getStrings(AccessControlLists.SUPERUSER_CONF_KEY, new String[0]));
227     return superUsers;
228   }
229 
230   @Override
231   public OperationStatus[] addLabels(List<byte[]> labels) throws IOException {
232     assert labelsRegion != null;
233     OperationStatus[] finalOpStatus = new OperationStatus[labels.size()];
234     List<Mutation> puts = new ArrayList<Mutation>(labels.size());
235     int i = 0;
236     for (byte[] label : labels) {
237       String labelStr = Bytes.toString(label);
238       if (this.labelsCache.getLabelOrdinal(labelStr) > 0) {
239         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
240             new LabelAlreadyExistsException("Label '" + labelStr + "' already exists"));
241       } else {
242         Put p = new Put(Bytes.toBytes(ordinalCounter));
243         p.addImmutable(LABELS_TABLE_FAMILY, LABEL_QUALIFIER, label, LABELS_TABLE_TAGS);
244         if (LOG.isDebugEnabled()) {
245           LOG.debug("Adding the label " + labelStr);
246         }
247         puts.add(p);
248         ordinalCounter++;
249       }
250       i++;
251     }
252     if (mutateLabelsRegion(puts, finalOpStatus)) {
253       updateZk(true);
254     }
255     return finalOpStatus;
256   }
257 
258   @Override
259   public OperationStatus[] setAuths(byte[] user, List<byte[]> authLabels) throws IOException {
260     assert labelsRegion != null;
261     OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
262     List<Mutation> puts = new ArrayList<Mutation>(authLabels.size());
263     int i = 0;
264     for (byte[] auth : authLabels) {
265       String authStr = Bytes.toString(auth);
266       int labelOrdinal = this.labelsCache.getLabelOrdinal(authStr);
267       if (labelOrdinal == 0) {
268         // This label is not yet added. 1st this should be added to the system
269         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
270             new InvalidLabelException("Label '" + authStr + "' doesn't exists"));
271       } else {
272         Put p = new Put(Bytes.toBytes(labelOrdinal));
273         p.addImmutable(LABELS_TABLE_FAMILY, user, DUMMY_VALUE, LABELS_TABLE_TAGS);
274         puts.add(p);
275       }
276       i++;
277     }
278     if (mutateLabelsRegion(puts, finalOpStatus)) {
279       updateZk(false);
280     }
281     return finalOpStatus;
282   }
283 
284   @Override
285   public OperationStatus[] clearAuths(byte[] user, List<byte[]> authLabels) throws IOException {
286     assert labelsRegion != null;
287     OperationStatus[] finalOpStatus = new OperationStatus[authLabels.size()];
288     List<String> currentAuths = this.getAuths(user, true);
289     List<Mutation> deletes = new ArrayList<Mutation>(authLabels.size());
290     int i = 0;
291     for (byte[] authLabel : authLabels) {
292       String authLabelStr = Bytes.toString(authLabel);
293       if (currentAuths.contains(authLabelStr)) {
294         int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabelStr);
295         assert labelOrdinal > 0;
296         Delete d = new Delete(Bytes.toBytes(labelOrdinal));
297         d.deleteColumns(LABELS_TABLE_FAMILY, user);
298         deletes.add(d);
299       } else {
300         // This label is not set for the user.
301         finalOpStatus[i] = new OperationStatus(OperationStatusCode.FAILURE,
302             new InvalidLabelException("Label '" + authLabelStr + "' is not set for the user "
303                 + Bytes.toString(user)));
304       }
305       i++;
306     }
307     if (mutateLabelsRegion(deletes, finalOpStatus)) {
308       updateZk(false);
309     }
310     return finalOpStatus;
311   }
312 
313   /**
314    * Adds the mutations to labels region and set the results to the finalOpStatus. finalOpStatus
315    * might have some entries in it where the OpStatus is FAILURE. We will leave those and set in
316    * others in the order.
317    * @param mutations
318    * @param finalOpStatus
319    * @return whether we need a ZK update or not.
320    */
321   private boolean mutateLabelsRegion(List<Mutation> mutations, OperationStatus[] finalOpStatus)
322       throws IOException {
323     OperationStatus[] opStatus = this.labelsRegion.batchMutate(mutations
324         .toArray(new Mutation[mutations.size()]));
325     int i = 0;
326     boolean updateZk = false;
327     for (OperationStatus status : opStatus) {
328       // Update the zk when atleast one of the mutation was added successfully.
329       updateZk = updateZk || (status.getOperationStatusCode() == OperationStatusCode.SUCCESS);
330       for (; i < finalOpStatus.length; i++) {
331         if (finalOpStatus[i] == null) {
332           finalOpStatus[i] = status;
333           break;
334         }
335       }
336     }
337     return updateZk;
338   }
339 
340   @Override
341   public List<String> getAuths(byte[] user, boolean systemCall) throws IOException {
342     assert (labelsRegion != null || systemCall);
343     if (systemCall || labelsRegion == null) {
344       return this.labelsCache.getAuths(Bytes.toString(user));
345     }
346     Scan s = new Scan();
347     s.addColumn(LABELS_TABLE_FAMILY, user);
348     Filter filter = VisibilityUtils.createVisibilityLabelFilter(this.labelsRegion,
349         new Authorizations(SYSTEM_LABEL));
350     s.setFilter(filter);
351     List<String> auths = new ArrayList<String>();
352     RegionScanner scanner = this.labelsRegion.getScanner(s);
353     List<Cell> results = new ArrayList<Cell>(1);
354     while (true) {
355       scanner.next(results);
356       if (results.isEmpty()) break;
357       Cell cell = results.get(0);
358       int ordinal = Bytes.toInt(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength());
359       String label = this.labelsCache.getLabel(ordinal);
360       if (label != null) {
361         auths.add(label);
362       }
363       results.clear();
364     }
365     return auths;
366   }
367 
368   @Override
369   public List<Tag> createVisibilityExpTags(String visExpression, boolean withSerializationFormat,
370       boolean checkAuths) throws IOException {
371     Set<Integer> auths = null;
372     if (checkAuths) {
373       auths = this.labelsCache.getAuthsAsOrdinals(VisibilityUtils.getActiveUser().getShortName());
374     }
375     return VisibilityUtils.createVisibilityExpTags(visExpression, withSerializationFormat,
376         checkAuths, auths, labelsCache);
377   }
378 
379   protected void updateZk(boolean labelAddition) throws IOException {
380     // We will add to zookeeper here.
381     // TODO we should add the delta only to zk. Else this will be a very heavy op and when there are
382     // so many labels and auth in the system, we will end up adding lots of data to zk. Most
383     // possibly we will exceed zk node data limit!
384     Pair<Map<String, Integer>, Map<String, List<Integer>>> labelsAndUserAuths =
385         extractLabelsAndAuths(getExistingLabelsWithAuths());
386     Map<String, Integer> existingLabels = labelsAndUserAuths.getFirst();
387     Map<String, List<Integer>> userAuths = labelsAndUserAuths.getSecond();
388     if (labelAddition) {
389       byte[] serialized = VisibilityUtils.getDataToWriteToZooKeeper(existingLabels);
390       this.labelsCache.writeToZookeeper(serialized, true);
391     } else {
392       byte[] serialized = VisibilityUtils.getUserAuthsDataToWriteToZooKeeper(userAuths);
393       this.labelsCache.writeToZookeeper(serialized, false);
394     }
395   }
396 
397   @Override
398   public VisibilityExpEvaluator getVisibilityExpEvaluator(Authorizations authorizations)
399       throws IOException {
400     // If a super user issues a get/scan, he should be able to scan the cells
401     // irrespective of the Visibility labels
402     if (isReadFromSuperUser()) {
403       return new VisibilityExpEvaluator() {
404         @Override
405         public boolean evaluate(Cell cell) throws IOException {
406           return true;
407         }
408       };
409     }
410     List<String> authLabels = null;
411     for (ScanLabelGenerator scanLabelGenerator : scanLabelGenerators) {
412       try {
413         // null authorizations to be handled inside SLG impl.
414         authLabels = scanLabelGenerator.getLabels(VisibilityUtils.getActiveUser(), authorizations);
415         authLabels = (authLabels == null) ? new ArrayList<String>() : authLabels;
416         authorizations = new Authorizations(authLabels);
417       } catch (Throwable t) {
418         LOG.error(t);
419         throw new IOException(t);
420       }
421     }
422     int labelsCount = this.labelsCache.getLabelsCount();
423     final BitSet bs = new BitSet(labelsCount + 1); // ordinal is index 1 based
424     if (authLabels != null) {
425       for (String authLabel : authLabels) {
426         int labelOrdinal = this.labelsCache.getLabelOrdinal(authLabel);
427         if (labelOrdinal != 0) {
428           bs.set(labelOrdinal);
429         }
430       }
431     }
432 
433     return new VisibilityExpEvaluator() {
434       @Override
435       public boolean evaluate(Cell cell) throws IOException {
436         boolean visibilityTagPresent = false;
437         // Save an object allocation where we can
438         if (cell.getTagsLengthUnsigned() > 0) {
439           Iterator<Tag> tagsItr = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
440               cell.getTagsLengthUnsigned());
441           while (tagsItr.hasNext()) {
442             boolean includeKV = true;
443             Tag tag = tagsItr.next();
444             if (tag.getType() == VISIBILITY_TAG_TYPE) {
445               visibilityTagPresent = true;
446               int offset = tag.getTagOffset();
447               int endOffset = offset + tag.getTagLength();
448               while (offset < endOffset) {
449                 Pair<Integer, Integer> result = StreamUtils
450                     .readRawVarint32(tag.getBuffer(), offset);
451                 int currLabelOrdinal = result.getFirst();
452                 if (currLabelOrdinal < 0) {
453                   // check for the absence of this label in the Scan Auth labels
454                   // ie. to check BitSet corresponding bit is 0
455                   int temp = -currLabelOrdinal;
456                   if (bs.get(temp)) {
457                     includeKV = false;
458                     break;
459                   }
460                 } else {
461                   if (!bs.get(currLabelOrdinal)) {
462                     includeKV = false;
463                     break;
464                   }
465                 }
466                 offset += result.getSecond();
467               }
468               if (includeKV) {
469                 // We got one visibility expression getting evaluated to true. Good to include this
470                 // KV in the result then.
471                 return true;
472               }
473             }
474           }
475         }
476         return !(visibilityTagPresent);
477       }
478     };
479   }
480 
481   protected boolean isReadFromSuperUser() throws IOException {
482     byte[] user = Bytes.toBytes(VisibilityUtils.getActiveUser().getShortName());
483     return havingSystemAuth(user);
484   }
485 
486   @Override
487   public boolean havingSystemAuth(byte[] user) throws IOException {
488     List<String> auths = this.getAuths(user, true);
489     if (LOG.isTraceEnabled()) {
490       LOG.trace("The auths for user " + Bytes.toString(user) + " are " + auths);
491     }
492     return auths.contains(SYSTEM_LABEL);
493   }
494 
495   @Override
496   public boolean matchVisibility(List<Tag> putVisTags, Byte putTagsFormat, List<Tag> deleteVisTags,
497       Byte deleteTagsFormat) throws IOException {
498     if ((deleteTagsFormat != null && deleteTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)
499         && (putTagsFormat == null || putTagsFormat == SORTED_ORDINAL_SERIALIZATION_FORMAT)) {
500       if (putVisTags.size() == 0) {
501         // Early out if there are no tags in the cell
502         return false;
503       }
504       if (putTagsFormat == null) {
505         return matchUnSortedVisibilityTags(putVisTags, deleteVisTags);
506       } else {
507         return matchOrdinalSortedVisibilityTags(putVisTags, deleteVisTags);
508       }
509     }
510     throw new IOException("Unexpected tag format passed for comparison, deleteTagsFormat : "
511         + deleteTagsFormat + ", putTagsFormat : " + putTagsFormat);
512   }
513 
514   /**
515    * @param putVisTags Visibility tags in Put Mutation
516    * @param deleteVisTags Visibility tags in Delete Mutation
517    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
518    * This is used when, at least one set of tags are not sorted based on the label ordinal.
519    */
520   private static boolean matchUnSortedVisibilityTags(List<Tag> putVisTags,
521       List<Tag> deleteVisTags) throws IOException {
522     return compareTagsOrdinals(sortTagsBasedOnOrdinal(putVisTags),
523         sortTagsBasedOnOrdinal(deleteVisTags));
524   }
525 
526   /**
527    * @param putVisTags Visibility tags in Put Mutation
528    * @param deleteVisTags Visibility tags in Delete Mutation
529    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
530    * This is used when both the set of tags are sorted based on the label ordinal.
531    */
532   private static boolean matchOrdinalSortedVisibilityTags(List<Tag> putVisTags,
533       List<Tag> deleteVisTags) {
534     boolean matchFound = false;
535     // If the size does not match. Definitely we are not comparing the equal tags.
536     if ((deleteVisTags.size()) == putVisTags.size()) {
537       for (Tag tag : deleteVisTags) {
538         matchFound = false;
539         for (Tag givenTag : putVisTags) {
540           if (Bytes.equals(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength(),
541               givenTag.getBuffer(), givenTag.getTagOffset(), givenTag.getTagLength())) {
542             matchFound = true;
543             break;
544           }
545         }
546         if (!matchFound) break;
547       }
548     }
549     return matchFound;
550   }
551 
552   private static List<List<Integer>> sortTagsBasedOnOrdinal(List<Tag> tags) throws IOException {
553     List<List<Integer>> fullTagsList = new ArrayList<List<Integer>>();
554     for (Tag tag : tags) {
555       if (tag.getType() == VISIBILITY_TAG_TYPE) {
556         getSortedTagOrdinals(fullTagsList, tag);
557       }
558     }
559     return fullTagsList;
560   }
561 
562   private static void getSortedTagOrdinals(List<List<Integer>> fullTagsList, Tag tag)
563       throws IOException {
564     List<Integer> tagsOrdinalInSortedOrder = new ArrayList<Integer>();
565     int offset = tag.getTagOffset();
566     int endOffset = offset + tag.getTagLength();
567     while (offset < endOffset) {
568       Pair<Integer, Integer> result = StreamUtils.readRawVarint32(tag.getBuffer(), offset);
569       tagsOrdinalInSortedOrder.add(result.getFirst());
570       offset += result.getSecond();
571     }
572     Collections.sort(tagsOrdinalInSortedOrder);
573     fullTagsList.add(tagsOrdinalInSortedOrder);
574   }
575 
576   /*
577    * @return true when all the visibility tags in Put matches with visibility tags in Delete.
578    */
579   private static boolean compareTagsOrdinals(List<List<Integer>> putVisTags,
580       List<List<Integer>> deleteVisTags) {
581     boolean matchFound = false;
582     if (deleteVisTags.size() == putVisTags.size()) {
583       for (List<Integer> deleteTagOrdinals : deleteVisTags) {
584         matchFound = false;
585         for (List<Integer> tagOrdinals : putVisTags) {
586           if (deleteTagOrdinals.equals(tagOrdinals)) {
587             matchFound = true;
588             break;
589           }
590         }
591         if (!matchFound) break;
592       }
593     }
594     return matchFound;
595   }
596 }