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.types; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022 023import java.lang.reflect.Constructor; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Comparator; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.testclassification.MiscTests; 029import org.apache.hadoop.hbase.testclassification.SmallTests; 030import org.apache.hadoop.hbase.util.Bytes; 031import org.apache.hadoop.hbase.util.Order; 032import org.apache.hadoop.hbase.util.PositionedByteRange; 033import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange; 034import org.junit.ClassRule; 035import org.junit.Test; 036import org.junit.experimental.categories.Category; 037import org.junit.runner.RunWith; 038import org.junit.runners.Parameterized; 039import org.junit.runners.Parameterized.Parameters; 040 041/** 042 * This class both tests and demonstrates how to construct compound rowkeys 043 * from a POJO. The code under test is {@link Struct}. 044 * {@link SpecializedPojo1Type1} demonstrates how one might create their own 045 * custom data type extension for an application POJO. 046 */ 047@RunWith(Parameterized.class) 048@Category({MiscTests.class, SmallTests.class}) 049public class TestStruct { 050 @ClassRule 051 public static final HBaseClassTestRule CLASS_RULE = 052 HBaseClassTestRule.forClass(TestStruct.class); 053 054 @Parameterized.Parameter() 055 public Struct generic; 056 057 @SuppressWarnings("rawtypes") 058 @Parameterized.Parameter(value = 1) 059 public DataType specialized; 060 061 @Parameterized.Parameter(value = 2) 062 public Object[][] constructorArgs; 063 064 @Parameters 065 public static Collection<Object[]> params() { 066 Object[][] pojo1Args = { 067 new Object[] { "foo", 5, 10.001 }, 068 new Object[] { "foo", 100, 7.0 }, 069 new Object[] { "foo", 100, 10.001 }, 070 new Object[] { "bar", 5, 10.001 }, 071 new Object[] { "bar", 100, 10.001 }, 072 new Object[] { "baz", 5, 10.001 }, 073 }; 074 075 Object[][] pojo2Args = { 076 new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") }, 077 new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") }, 078 new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") }, 079 new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] }, 080 new Object[] { new byte[0], new byte[0], "", new byte[0] }, 081 }; 082 083 Object[][] params = new Object[][] { 084 { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args }, 085 { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args }, 086 }; 087 return Arrays.asList(params); 088 } 089 090 static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR = (o1, o2) -> { 091 if (o1 == o2) { 092 return 0; 093 } 094 095 if (null == o1) { 096 return -1; 097 } 098 099 if (null == o2) { 100 return 1; 101 } 102 103 return Bytes.compareTo(o1, o2); 104 }; 105 106 /** 107 * A simple object to serialize. 108 */ 109 private static class Pojo1 implements Comparable<Pojo1> { 110 final String stringFieldAsc; 111 final int intFieldAsc; 112 final double doubleFieldAsc; 113 final transient String str; 114 115 public Pojo1(Object... argv) { 116 stringFieldAsc = (String) argv[0]; 117 intFieldAsc = (Integer) argv[1]; 118 doubleFieldAsc = (Double) argv[2]; 119 str = new StringBuilder() 120 .append("{ ") 121 .append(null == stringFieldAsc ? "" : "\"") 122 .append(stringFieldAsc) 123 .append(null == stringFieldAsc ? "" : "\"").append(", ") 124 .append(intFieldAsc).append(", ") 125 .append(doubleFieldAsc) 126 .append(" }") 127 .toString(); 128 } 129 130 @Override 131 public String toString() { 132 return str; 133 } 134 135 @Override 136 public int compareTo(Pojo1 o) { 137 int cmp = stringFieldAsc.compareTo(o.stringFieldAsc); 138 if (cmp != 0) { 139 return cmp; 140 } 141 cmp = Integer.compare(intFieldAsc, o.intFieldAsc); 142 if (cmp != 0) { 143 return cmp; 144 } 145 return Double.compare(doubleFieldAsc, o.doubleFieldAsc); 146 } 147 148 @Override 149 public int hashCode() { 150 final int prime = 31; 151 int result = 1; 152 long temp; 153 temp = Double.doubleToLongBits(doubleFieldAsc); 154 result = prime * result + (int) (temp ^ (temp >>> 32)); 155 result = prime * result + intFieldAsc; 156 result = prime * result + ((stringFieldAsc == null) ? 0 : stringFieldAsc.hashCode()); 157 return result; 158 } 159 160 @Override 161 public boolean equals(Object obj) { 162 if (this == obj) { 163 return true; 164 } 165 if (obj == null) { 166 return false; 167 } 168 if (getClass() != obj.getClass()) { 169 return false; 170 } 171 Pojo1 other = (Pojo1) obj; 172 if (Double.doubleToLongBits(doubleFieldAsc) != 173 Double.doubleToLongBits(other.doubleFieldAsc)) { 174 return false; 175 } 176 if (intFieldAsc != other.intFieldAsc) { 177 return false; 178 } 179 if (stringFieldAsc == null) { 180 return other.stringFieldAsc == null; 181 } else { 182 return stringFieldAsc.equals(other.stringFieldAsc); 183 } 184 } 185 } 186 187 /** 188 * A simple object to serialize. 189 */ 190 private static class Pojo2 implements Comparable<Pojo2> { 191 final byte[] byteField1Asc; 192 final byte[] byteField2Dsc; 193 final String stringFieldDsc; 194 final byte[] byteField3Dsc; 195 final transient String str; 196 197 public Pojo2(Object... vals) { 198 byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : null; 199 byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : null; 200 stringFieldDsc = vals.length > 2 ? (String) vals[2] : null; 201 byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : null; 202 str = new StringBuilder() 203 .append("{ ") 204 .append(Bytes.toStringBinary(byteField1Asc)).append(", ") 205 .append(Bytes.toStringBinary(byteField2Dsc)).append(", ") 206 .append(null == stringFieldDsc ? "" : "\"") 207 .append(stringFieldDsc) 208 .append(null == stringFieldDsc ? "" : "\"").append(", ") 209 .append(Bytes.toStringBinary(byteField3Dsc)) 210 .append(" }") 211 .toString(); 212 } 213 214 @Override 215 public String toString() { 216 return str; 217 } 218 219 @Override 220 public int compareTo(Pojo2 o) { 221 int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc); 222 if (cmp != 0) { 223 return cmp; 224 } 225 cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc); 226 if (cmp != 0) { 227 return cmp; 228 } 229 230 if (null == stringFieldDsc) { 231 cmp = 1; 232 } else if (null == o.stringFieldDsc) { 233 cmp = -1; 234 } else if (stringFieldDsc.equals(o.stringFieldDsc)) { 235 cmp = 0; 236 } else { 237 cmp = -stringFieldDsc.compareTo(o.stringFieldDsc); 238 } 239 240 if (cmp != 0) { 241 return cmp; 242 } 243 return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc); 244 } 245 246 @Override 247 public int hashCode() { 248 final int prime = 31; 249 int result = 1; 250 result = prime * result + Arrays.hashCode(byteField1Asc); 251 result = prime * result + Arrays.hashCode(byteField2Dsc); 252 result = prime * result + Arrays.hashCode(byteField3Dsc); 253 result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode()); 254 return result; 255 } 256 257 @Override 258 public boolean equals(Object obj) { 259 if (this == obj) { 260 return true; 261 } 262 if (obj == null) { 263 return false; 264 } 265 if (getClass() != obj.getClass()) { 266 return false; 267 } 268 Pojo2 other = (Pojo2) obj; 269 if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) { 270 return false; 271 } 272 if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) { 273 return false; 274 } 275 if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) { 276 return false; 277 } 278 if (stringFieldDsc == null) { 279 return other.stringFieldDsc == null; 280 } else { 281 return stringFieldDsc.equals(other.stringFieldDsc); 282 } 283 } 284 } 285 286 /** 287 * A custom data type implementation specialized for {@link Pojo1}. 288 */ 289 private static class SpecializedPojo1Type1 implements DataType<Pojo1> { 290 private static final RawStringTerminated stringField = new RawStringTerminated("/"); 291 private static final RawInteger intField = new RawInteger(); 292 private static final RawDouble doubleField = new RawDouble(); 293 294 /** 295 * The {@link Struct} equivalent of this type. 296 */ 297 public static Struct GENERIC = new StructBuilder().add(stringField).add(intField) 298 .add(doubleField).toStruct(); 299 300 @Override 301 public boolean isOrderPreserving() { 302 return true; 303 } 304 305 @Override 306 public Order getOrder() { 307 return null; 308 } 309 310 @Override 311 public boolean isNullable() { 312 return false; 313 } 314 315 @Override 316 public boolean isSkippable() { 317 return true; 318 } 319 320 @Override 321 public int encodedLength(Pojo1 val) { 322 return stringField.encodedLength(val.stringFieldAsc) + 323 intField.encodedLength(val.intFieldAsc) + 324 doubleField.encodedLength(val.doubleFieldAsc); 325 } 326 327 @Override 328 public Class<Pojo1> encodedClass() { 329 return Pojo1.class; 330 } 331 332 @Override 333 public int skip(PositionedByteRange src) { 334 int skipped = stringField.skip(src); 335 skipped += intField.skip(src); 336 skipped += doubleField.skip(src); 337 return skipped; 338 } 339 340 @Override 341 public Pojo1 decode(PositionedByteRange src) { 342 Object[] ret = new Object[3]; 343 ret[0] = stringField.decode(src); 344 ret[1] = intField.decode(src); 345 ret[2] = doubleField.decode(src); 346 return new Pojo1(ret); 347 } 348 349 @Override 350 public int encode(PositionedByteRange dst, Pojo1 val) { 351 int written = stringField.encode(dst, val.stringFieldAsc); 352 written += intField.encode(dst, val.intFieldAsc); 353 written += doubleField.encode(dst, val.doubleFieldAsc); 354 return written; 355 } 356 } 357 358 /** 359 * A custom data type implementation specialized for {@link Pojo2}. 360 */ 361 private static class SpecializedPojo2Type1 implements DataType<Pojo2> { 362 private static RawBytesTerminated byteField1 = new RawBytesTerminated("/"); 363 private static RawBytesTerminated byteField2 = 364 new RawBytesTerminated(Order.DESCENDING, "/"); 365 private static RawStringTerminated stringField = 366 new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 }); 367 private static RawBytes byteField3 = RawBytes.DESCENDING; 368 369 /** 370 * The {@link Struct} equivalent of this type. 371 */ 372 public static Struct GENERIC = new StructBuilder().add(byteField1).add(byteField2) 373 .add(stringField).add(byteField3).toStruct(); 374 375 @Override 376 public boolean isOrderPreserving() { 377 return true; 378 } 379 380 @Override 381 public Order getOrder() { 382 return null; 383 } 384 385 @Override 386 public boolean isNullable() { 387 return false; 388 } 389 390 @Override 391 public boolean isSkippable() { 392 return true; 393 } 394 395 @Override 396 public int encodedLength(Pojo2 val) { 397 return byteField1.encodedLength(val.byteField1Asc) + 398 byteField2.encodedLength(val.byteField2Dsc) + 399 stringField.encodedLength(val.stringFieldDsc) + 400 byteField3.encodedLength(val.byteField3Dsc); 401 } 402 403 @Override 404 public Class<Pojo2> encodedClass() { 405 return Pojo2.class; 406 } 407 408 @Override 409 public int skip(PositionedByteRange src) { 410 int skipped = byteField1.skip(src); 411 skipped += byteField2.skip(src); 412 skipped += stringField.skip(src); 413 skipped += byteField3.skip(src); 414 return skipped; 415 } 416 417 @Override 418 public Pojo2 decode(PositionedByteRange src) { 419 Object[] ret = new Object[4]; 420 ret[0] = byteField1.decode(src); 421 ret[1] = byteField2.decode(src); 422 ret[2] = stringField.decode(src); 423 ret[3] = byteField3.decode(src); 424 return new Pojo2(ret); 425 } 426 427 @Override 428 public int encode(PositionedByteRange dst, Pojo2 val) { 429 int written = byteField1.encode(dst, val.byteField1Asc); 430 written += byteField2.encode(dst, val.byteField2Dsc); 431 written += stringField.encode(dst, val.stringFieldDsc); 432 written += byteField3.encode(dst, val.byteField3Dsc); 433 return written; 434 } 435 } 436 437 @Test 438 @SuppressWarnings("unchecked") 439 public void testOrderPreservation() throws Exception { 440 Object[] vals = new Object[constructorArgs.length]; 441 PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length]; 442 PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length]; 443 Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class); 444 for (int i = 0; i < vals.length; i++) { 445 vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] }); 446 encodedGeneric[i] = new SimplePositionedMutableByteRange( 447 generic.encodedLength(constructorArgs[i])); 448 encodedSpecialized[i] = new SimplePositionedMutableByteRange( 449 specialized.encodedLength(vals[i])); 450 } 451 452 // populate our arrays 453 for (int i = 0; i < vals.length; i++) { 454 generic.encode(encodedGeneric[i], constructorArgs[i]); 455 encodedGeneric[i].setPosition(0); 456 specialized.encode(encodedSpecialized[i], vals[i]); 457 encodedSpecialized[i].setPosition(0); 458 assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes()); 459 } 460 461 Arrays.sort(vals); 462 Arrays.sort(encodedGeneric); 463 Arrays.sort(encodedSpecialized); 464 465 for (int i = 0; i < vals.length; i++) { 466 assertEquals( 467 "Struct encoder does not preserve sort order at position " + i, 468 vals[i], 469 ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) })); 470 assertEquals( 471 "Specialized encoder does not preserve sort order at position " + i, 472 vals[i], specialized.decode(encodedSpecialized[i])); 473 } 474 } 475}