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