001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.thrift2; 019 020import static java.nio.ByteBuffer.wrap; 021import static org.junit.Assert.assertArrayEquals; 022import static org.junit.Assert.assertEquals; 023import static org.junit.Assert.assertNull; 024import static org.junit.Assert.fail; 025 026import java.io.IOException; 027import java.nio.ByteBuffer; 028import java.security.PrivilegedExceptionAction; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.Collections; 032import java.util.Comparator; 033import java.util.List; 034import org.apache.hadoop.conf.Configuration; 035import org.apache.hadoop.hbase.HBaseClassTestRule; 036import org.apache.hadoop.hbase.HBaseTestingUtil; 037import org.apache.hadoop.hbase.TableName; 038import org.apache.hadoop.hbase.client.Admin; 039import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 040import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 041import org.apache.hadoop.hbase.client.Connection; 042import org.apache.hadoop.hbase.client.ConnectionFactory; 043import org.apache.hadoop.hbase.client.TableDescriptor; 044import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 045import org.apache.hadoop.hbase.security.User; 046import org.apache.hadoop.hbase.security.UserProvider; 047import org.apache.hadoop.hbase.security.visibility.ScanLabelGenerator; 048import org.apache.hadoop.hbase.security.visibility.SimpleScanLabelGenerator; 049import org.apache.hadoop.hbase.security.visibility.VisibilityClient; 050import org.apache.hadoop.hbase.security.visibility.VisibilityConstants; 051import org.apache.hadoop.hbase.security.visibility.VisibilityTestUtil; 052import org.apache.hadoop.hbase.security.visibility.VisibilityUtils; 053import org.apache.hadoop.hbase.testclassification.ClientTests; 054import org.apache.hadoop.hbase.testclassification.MediumTests; 055import org.apache.hadoop.hbase.thrift2.generated.TAppend; 056import org.apache.hadoop.hbase.thrift2.generated.TAuthorization; 057import org.apache.hadoop.hbase.thrift2.generated.TCellVisibility; 058import org.apache.hadoop.hbase.thrift2.generated.TColumn; 059import org.apache.hadoop.hbase.thrift2.generated.TColumnIncrement; 060import org.apache.hadoop.hbase.thrift2.generated.TColumnValue; 061import org.apache.hadoop.hbase.thrift2.generated.TDelete; 062import org.apache.hadoop.hbase.thrift2.generated.TGet; 063import org.apache.hadoop.hbase.thrift2.generated.TIllegalArgument; 064import org.apache.hadoop.hbase.thrift2.generated.TIncrement; 065import org.apache.hadoop.hbase.thrift2.generated.TPut; 066import org.apache.hadoop.hbase.thrift2.generated.TResult; 067import org.apache.hadoop.hbase.thrift2.generated.TScan; 068import org.apache.hadoop.hbase.util.Bytes; 069import org.junit.AfterClass; 070import org.junit.Assert; 071import org.junit.Before; 072import org.junit.BeforeClass; 073import org.junit.ClassRule; 074import org.junit.Test; 075import org.junit.experimental.categories.Category; 076import org.slf4j.Logger; 077import org.slf4j.LoggerFactory; 078 079import org.apache.hadoop.hbase.shaded.protobuf.generated.VisibilityLabelsProtos.VisibilityLabelsResponse; 080 081@Category({ ClientTests.class, MediumTests.class }) 082public class TestThriftHBaseServiceHandlerWithLabels { 083 084 @ClassRule 085 public static final HBaseClassTestRule CLASS_RULE = 086 HBaseClassTestRule.forClass(TestThriftHBaseServiceHandlerWithLabels.class); 087 088 private static final Logger LOG = 089 LoggerFactory.getLogger(TestThriftHBaseServiceHandlerWithLabels.class); 090 private static final HBaseTestingUtil UTIL = new HBaseTestingUtil(); 091 092 // Static names for tables, columns, rows, and values 093 private static byte[] tableAname = Bytes.toBytes("tableA"); 094 private static byte[] familyAname = Bytes.toBytes("familyA"); 095 private static byte[] familyBname = Bytes.toBytes("familyB"); 096 private static byte[] qualifierAname = Bytes.toBytes("qualifierA"); 097 private static byte[] qualifierBname = Bytes.toBytes("qualifierB"); 098 private static byte[] valueAname = Bytes.toBytes("valueA"); 099 private static byte[] valueBname = Bytes.toBytes("valueB"); 100 private static ColumnFamilyDescriptor[] families = new ColumnFamilyDescriptor[] { 101 ColumnFamilyDescriptorBuilder.newBuilder(familyAname).setMaxVersions(3).build(), 102 ColumnFamilyDescriptorBuilder.newBuilder(familyBname).setMaxVersions(2).build() }; 103 104 private final static String TOPSECRET = "topsecret"; 105 private final static String PUBLIC = "public"; 106 private final static String PRIVATE = "private"; 107 private final static String CONFIDENTIAL = "confidential"; 108 private final static String SECRET = "secret"; 109 private static User SUPERUSER; 110 111 private static Configuration conf; 112 113 public void assertTColumnValuesEqual(List<TColumnValue> columnValuesA, 114 List<TColumnValue> columnValuesB) { 115 assertEquals(columnValuesA.size(), columnValuesB.size()); 116 Comparator<TColumnValue> comparator = new Comparator<TColumnValue>() { 117 @Override 118 public int compare(TColumnValue o1, TColumnValue o2) { 119 return Bytes.compareTo(Bytes.add(o1.getFamily(), o1.getQualifier()), 120 Bytes.add(o2.getFamily(), o2.getQualifier())); 121 } 122 }; 123 Collections.sort(columnValuesA, comparator); 124 Collections.sort(columnValuesB, comparator); 125 126 for (int i = 0; i < columnValuesA.size(); i++) { 127 TColumnValue a = columnValuesA.get(i); 128 TColumnValue b = columnValuesB.get(i); 129 assertArrayEquals(a.getFamily(), b.getFamily()); 130 assertArrayEquals(a.getQualifier(), b.getQualifier()); 131 assertArrayEquals(a.getValue(), b.getValue()); 132 } 133 } 134 135 @BeforeClass 136 public static void beforeClass() throws Exception { 137 SUPERUSER = User.createUserForTesting(conf, "admin", new String[] { "supergroup" }); 138 conf = UTIL.getConfiguration(); 139 conf.setClass(VisibilityUtils.VISIBILITY_LABEL_GENERATOR_CLASS, SimpleScanLabelGenerator.class, 140 ScanLabelGenerator.class); 141 conf.set("hbase.superuser", SUPERUSER.getShortName()); 142 VisibilityTestUtil.enableVisiblityLabels(conf); 143 UTIL.startMiniCluster(1); 144 // Wait for the labels table to become available 145 UTIL.waitTableEnabled(VisibilityConstants.LABELS_TABLE_NAME.getName(), 50000); 146 createLabels(); 147 Admin admin = UTIL.getAdmin(); 148 TableDescriptor tableDescriptor = TableDescriptorBuilder 149 .newBuilder(TableName.valueOf(tableAname)).setColumnFamilies(Arrays.asList(families)).build(); 150 admin.createTable(tableDescriptor); 151 admin.close(); 152 setAuths(); 153 } 154 155 private static void createLabels() throws IOException, InterruptedException { 156 PrivilegedExceptionAction<VisibilityLabelsResponse> action = 157 new PrivilegedExceptionAction<VisibilityLabelsResponse>() { 158 @Override 159 public VisibilityLabelsResponse run() throws Exception { 160 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET }; 161 try (Connection conn = ConnectionFactory.createConnection(conf)) { 162 VisibilityClient.addLabels(conn, labels); 163 } catch (Throwable t) { 164 throw new IOException(t); 165 } 166 return null; 167 } 168 }; 169 SUPERUSER.runAs(action); 170 } 171 172 private static void setAuths() throws IOException { 173 String[] labels = { SECRET, CONFIDENTIAL, PRIVATE, PUBLIC, TOPSECRET }; 174 try { 175 VisibilityClient.setAuths(UTIL.getConnection(), labels, User.getCurrent().getShortName()); 176 } catch (Throwable t) { 177 throw new IOException(t); 178 } 179 } 180 181 @AfterClass 182 public static void afterClass() throws Exception { 183 UTIL.shutdownMiniCluster(); 184 } 185 186 @Before 187 public void setup() throws Exception { 188 189 } 190 191 private ThriftHBaseServiceHandler createHandler() throws IOException { 192 return new ThriftHBaseServiceHandler(conf, UserProvider.instantiate(conf)); 193 } 194 195 @Test 196 public void testScanWithVisibilityLabels() throws Exception { 197 ThriftHBaseServiceHandler handler = createHandler(); 198 ByteBuffer table = wrap(tableAname); 199 200 // insert data 201 TColumnValue columnValue = 202 new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)); 203 List<TColumnValue> columnValues = new ArrayList<>(1); 204 columnValues.add(columnValue); 205 for (int i = 0; i < 10; i++) { 206 TPut put = new TPut(wrap(Bytes.toBytes("testScan" + i)), columnValues); 207 if (i == 5) { 208 put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC)); 209 } else { 210 put.setCellVisibility(new TCellVisibility() 211 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 212 } 213 handler.put(table, put); 214 } 215 216 // create scan instance 217 TScan scan = new TScan(); 218 List<TColumn> columns = new ArrayList<>(1); 219 TColumn column = new TColumn(); 220 column.setFamily(familyAname); 221 column.setQualifier(qualifierAname); 222 columns.add(column); 223 scan.setColumns(columns); 224 scan.setStartRow(Bytes.toBytes("testScan")); 225 scan.setStopRow(Bytes.toBytes("testScan\uffff")); 226 227 TAuthorization tauth = new TAuthorization(); 228 List<String> labels = new ArrayList<>(2); 229 labels.add(SECRET); 230 labels.add(PRIVATE); 231 tauth.setLabels(labels); 232 scan.setAuthorizations(tauth); 233 // get scanner and rows 234 int scanId = handler.openScanner(table, scan); 235 List<TResult> results = handler.getScannerRows(scanId, 10); 236 assertEquals(9, results.size()); 237 Assert.assertFalse(Bytes.equals(results.get(5).getRow(), Bytes.toBytes("testScan" + 5))); 238 for (int i = 0; i < 9; i++) { 239 if (i < 5) { 240 assertArrayEquals(Bytes.toBytes("testScan" + i), results.get(i).getRow()); 241 } else if (i == 5) { 242 continue; 243 } else { 244 assertArrayEquals(Bytes.toBytes("testScan" + (i + 1)), results.get(i).getRow()); 245 } 246 } 247 248 // check that we are at the end of the scan 249 results = handler.getScannerRows(scanId, 9); 250 assertEquals(0, results.size()); 251 252 // close scanner and check that it was indeed closed 253 handler.closeScanner(scanId); 254 try { 255 handler.getScannerRows(scanId, 9); 256 fail("Scanner id should be invalid"); 257 } catch (TIllegalArgument e) { 258 } 259 } 260 261 @Test 262 public void testGetScannerResultsWithAuthorizations() throws Exception { 263 ThriftHBaseServiceHandler handler = createHandler(); 264 ByteBuffer table = wrap(tableAname); 265 266 // insert data 267 TColumnValue columnValue = 268 new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname)); 269 List<TColumnValue> columnValues = new ArrayList<>(1); 270 columnValues.add(columnValue); 271 for (int i = 0; i < 20; i++) { 272 TPut put = 273 new TPut(wrap(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2))), columnValues); 274 if (i == 3) { 275 put.setCellVisibility(new TCellVisibility().setExpression(PUBLIC)); 276 } else { 277 put.setCellVisibility(new TCellVisibility() 278 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 279 } 280 handler.put(table, put); 281 } 282 283 // create scan instance 284 TScan scan = new TScan(); 285 List<TColumn> columns = new ArrayList<>(1); 286 TColumn column = new TColumn(); 287 column.setFamily(familyAname); 288 column.setQualifier(qualifierAname); 289 columns.add(column); 290 scan.setColumns(columns); 291 scan.setStartRow(Bytes.toBytes("testGetScannerResults")); 292 293 // get 5 rows and check the returned results 294 scan.setStopRow(Bytes.toBytes("testGetScannerResults05")); 295 TAuthorization tauth = new TAuthorization(); 296 List<String> labels = new ArrayList<>(2); 297 labels.add(SECRET); 298 labels.add(PRIVATE); 299 tauth.setLabels(labels); 300 scan.setAuthorizations(tauth); 301 List<TResult> results = handler.getScannerResults(table, scan, 5); 302 assertEquals(4, results.size()); 303 for (int i = 0; i < 4; i++) { 304 if (i < 3) { 305 assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i, (byte) 2)), 306 results.get(i).getRow()); 307 } else if (i == 3) { 308 continue; 309 } else { 310 assertArrayEquals(Bytes.toBytes("testGetScannerResults" + pad(i + 1, (byte) 2)), 311 results.get(i).getRow()); 312 } 313 } 314 } 315 316 @Test 317 public void testGetsWithLabels() throws Exception { 318 ThriftHBaseServiceHandler handler = createHandler(); 319 byte[] rowName = Bytes.toBytes("testPutGet"); 320 ByteBuffer table = wrap(tableAname); 321 322 List<TColumnValue> columnValues = new ArrayList<>(2); 323 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 324 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 325 TPut put = new TPut(wrap(rowName), columnValues); 326 327 put.setColumnValues(columnValues); 328 put.setCellVisibility(new TCellVisibility() 329 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 330 handler.put(table, put); 331 TGet get = new TGet(wrap(rowName)); 332 TAuthorization tauth = new TAuthorization(); 333 List<String> labels = new ArrayList<>(2); 334 labels.add(SECRET); 335 labels.add(PRIVATE); 336 tauth.setLabels(labels); 337 get.setAuthorizations(tauth); 338 TResult result = handler.get(table, get); 339 assertArrayEquals(rowName, result.getRow()); 340 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 341 assertTColumnValuesEqual(columnValues, returnedColumnValues); 342 } 343 344 @Test 345 public void testIncrementWithTags() throws Exception { 346 ThriftHBaseServiceHandler handler = createHandler(); 347 byte[] rowName = Bytes.toBytes("testIncrementWithTags"); 348 ByteBuffer table = wrap(tableAname); 349 350 List<TColumnValue> columnValues = new ArrayList<>(1); 351 columnValues 352 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 353 TPut put = new TPut(wrap(rowName), columnValues); 354 put.setColumnValues(columnValues); 355 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 356 handler.put(table, put); 357 358 List<TColumnIncrement> incrementColumns = new ArrayList<>(1); 359 incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname))); 360 TIncrement increment = new TIncrement(wrap(rowName), incrementColumns); 361 increment.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 362 handler.increment(table, increment); 363 364 TGet get = new TGet(wrap(rowName)); 365 TAuthorization tauth = new TAuthorization(); 366 List<String> labels = new ArrayList<>(1); 367 labels.add(SECRET); 368 tauth.setLabels(labels); 369 get.setAuthorizations(tauth); 370 TResult result = handler.get(table, get); 371 372 assertArrayEquals(rowName, result.getRow()); 373 assertEquals(1, result.getColumnValuesSize()); 374 TColumnValue columnValue = result.getColumnValues().get(0); 375 assertArrayEquals(Bytes.toBytes(2L), columnValue.getValue()); 376 } 377 378 @Test 379 public void testIncrementWithTagsWithNotMatchLabels() throws Exception { 380 ThriftHBaseServiceHandler handler = createHandler(); 381 byte[] rowName = Bytes.toBytes("testIncrementWithTagsWithNotMatchLabels"); 382 ByteBuffer table = wrap(tableAname); 383 384 List<TColumnValue> columnValues = new ArrayList<>(1); 385 columnValues 386 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 387 TPut put = new TPut(wrap(rowName), columnValues); 388 put.setColumnValues(columnValues); 389 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 390 handler.put(table, put); 391 392 List<TColumnIncrement> incrementColumns = new ArrayList<>(1); 393 incrementColumns.add(new TColumnIncrement(wrap(familyAname), wrap(qualifierAname))); 394 TIncrement increment = new TIncrement(wrap(rowName), incrementColumns); 395 increment.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 396 handler.increment(table, increment); 397 398 TGet get = new TGet(wrap(rowName)); 399 TAuthorization tauth = new TAuthorization(); 400 List<String> labels = new ArrayList<>(1); 401 labels.add(PUBLIC); 402 tauth.setLabels(labels); 403 get.setAuthorizations(tauth); 404 TResult result = handler.get(table, get); 405 assertNull(result.getRow()); 406 } 407 408 @Test 409 public void testAppend() throws Exception { 410 ThriftHBaseServiceHandler handler = createHandler(); 411 byte[] rowName = Bytes.toBytes("testAppend"); 412 ByteBuffer table = wrap(tableAname); 413 byte[] v1 = Bytes.toBytes(1L); 414 byte[] v2 = Bytes.toBytes(5L); 415 List<TColumnValue> columnValues = new ArrayList<>(1); 416 columnValues 417 .add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(Bytes.toBytes(1L)))); 418 TPut put = new TPut(wrap(rowName), columnValues); 419 put.setColumnValues(columnValues); 420 put.setCellVisibility(new TCellVisibility().setExpression(PRIVATE)); 421 handler.put(table, put); 422 423 List<TColumnValue> appendColumns = new ArrayList<>(1); 424 appendColumns.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(v2))); 425 TAppend append = new TAppend(wrap(rowName), appendColumns); 426 append.setCellVisibility(new TCellVisibility().setExpression(SECRET)); 427 handler.append(table, append); 428 429 TGet get = new TGet(wrap(rowName)); 430 TAuthorization tauth = new TAuthorization(); 431 List<String> labels = new ArrayList<>(1); 432 labels.add(SECRET); 433 tauth.setLabels(labels); 434 get.setAuthorizations(tauth); 435 TResult result = handler.get(table, get); 436 437 assertArrayEquals(rowName, result.getRow()); 438 assertEquals(1, result.getColumnValuesSize()); 439 TColumnValue columnValue = result.getColumnValues().get(0); 440 assertArrayEquals(Bytes.add(v1, v2), columnValue.getValue()); 441 } 442 443 @Test 444 public void testDeleteWithLabels() throws Exception { 445 ThriftHBaseServiceHandler handler = createHandler(); 446 byte[] rowName = "testPutGetDeleteGet".getBytes(); 447 ByteBuffer table = wrap(tableAname); 448 449 // common auths 450 TAuthorization tauth = new TAuthorization(); 451 List<String> labels = new ArrayList<String>(); 452 labels.add(SECRET); 453 labels.add(PRIVATE); 454 tauth.setLabels(labels); 455 456 // put 457 List<TColumnValue> columnValues = new ArrayList<TColumnValue>(); 458 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 459 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 460 TPut put = new TPut(wrap(rowName), columnValues); 461 462 put.setColumnValues(columnValues); 463 put.setCellVisibility(new TCellVisibility() 464 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 465 handler.put(table, put); 466 467 // verify put 468 TGet get = new TGet(wrap(rowName)); 469 get.setAuthorizations(tauth); 470 TResult result = handler.get(table, get); 471 assertArrayEquals(rowName, result.getRow()); 472 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 473 assertTColumnValuesEqual(columnValues, returnedColumnValues); 474 475 // delete 476 TDelete delete = new TDelete(wrap(rowName)); 477 delete.setCellVisibility(new TCellVisibility() 478 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 479 handler.deleteSingle(table, delete); 480 481 // verify delete 482 TGet get2 = new TGet(wrap(rowName)); 483 get2.setAuthorizations(tauth); 484 TResult result2 = handler.get(table, get2); 485 assertNull(result2.getRow()); 486 } 487 488 @Test 489 public void testDeleteWithLabelsNegativeTest() throws Exception { 490 ThriftHBaseServiceHandler handler = createHandler(); 491 byte[] rowName = "testPutGetTryDeleteGet".getBytes(); 492 ByteBuffer table = wrap(tableAname); 493 494 // common auths 495 TAuthorization tauth = new TAuthorization(); 496 List<String> labels = new ArrayList<String>(); 497 labels.add(SECRET); 498 labels.add(PRIVATE); 499 tauth.setLabels(labels); 500 501 // put 502 List<TColumnValue> columnValues = new ArrayList<TColumnValue>(); 503 columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); 504 columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); 505 TPut put = new TPut(wrap(rowName), columnValues); 506 507 put.setColumnValues(columnValues); 508 put.setCellVisibility(new TCellVisibility() 509 .setExpression("(" + SECRET + "|" + CONFIDENTIAL + ")" + "&" + "!" + TOPSECRET)); 510 handler.put(table, put); 511 512 // verify put 513 TGet get = new TGet(wrap(rowName)); 514 get.setAuthorizations(tauth); 515 TResult result = handler.get(table, get); 516 assertArrayEquals(rowName, result.getRow()); 517 List<TColumnValue> returnedColumnValues = result.getColumnValues(); 518 assertTColumnValuesEqual(columnValues, returnedColumnValues); 519 520 // _try_ delete with _no_ CellVisibility 521 TDelete delete = new TDelete(wrap(rowName)); 522 handler.deleteSingle(table, delete); 523 524 // verify delete did in fact _not_ work 525 TGet get2 = new TGet(wrap(rowName)); 526 get2.setAuthorizations(tauth); 527 TResult result2 = handler.get(table, get2); 528 assertArrayEquals(rowName, result2.getRow()); 529 } 530 531 /** 532 * Padding numbers to make comparison of sort order easier in a for loop The number to pad. 533 * @param n The number to pad. 534 * @param pad The length to pad up to. 535 * @return The padded number as a string. 536 */ 537 private String pad(int n, byte pad) { 538 String res = Integer.toString(n); 539 540 while (res.length() < pad) { 541 res = "0" + res; 542 } 543 544 return res; 545 } 546}