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.types;
19  
20  import static org.junit.Assert.assertArrayEquals;
21  import static org.junit.Assert.assertEquals;
22  
23  import java.lang.reflect.Constructor;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Comparator;
27  
28  import org.apache.hadoop.hbase.SmallTests;
29  import org.apache.hadoop.hbase.util.Bytes;
30  import org.apache.hadoop.hbase.util.Order;
31  import org.apache.hadoop.hbase.util.PositionedByteRange;
32  import org.apache.hadoop.hbase.util.SimplePositionedByteRange;
33  import org.junit.Test;
34  import org.junit.experimental.categories.Category;
35  import org.junit.runner.RunWith;
36  import org.junit.runners.Parameterized;
37  import org.junit.runners.Parameterized.Parameters;
38  
39  /**
40   * This class both tests and demonstrates how to construct compound rowkeys
41   * from a POJO. The code under test is {@link Struct}.
42   * {@link SpecializedPojo1Type1} demonstrates how one might create their own
43   * custom data type extension for an application POJO.
44   */
45  @RunWith(Parameterized.class)
46  @Category(SmallTests.class)
47  public class TestStruct {
48  
49    private Struct generic;
50    @SuppressWarnings("rawtypes")
51    private DataType specialized;
52    private Object[][] constructorArgs;
53  
54    public TestStruct(Struct generic, @SuppressWarnings("rawtypes") DataType specialized,
55        Object[][] constructorArgs) {
56      this.generic = generic;
57      this.specialized = specialized;
58      this.constructorArgs = constructorArgs;
59    }
60  
61    @Parameters
62    public static Collection<Object[]> params() {
63      Object[][] pojo1Args = {
64          new Object[] { "foo", 5,   10.001 },
65          new Object[] { "foo", 100, 7.0    },
66          new Object[] { "foo", 100, 10.001 },
67          new Object[] { "bar", 5,   10.001 },
68          new Object[] { "bar", 100, 10.001 },
69          new Object[] { "baz", 5,   10.001 },
70      };
71  
72      Object[][] pojo2Args = {
73          new Object[] { new byte[0], "it".getBytes(), "was", "the".getBytes() },
74          new Object[] { "best".getBytes(), new byte[0], "of", "times,".getBytes() },
75          new Object[] { "it".getBytes(), "was".getBytes(), "", "the".getBytes() },
76          new Object[] { "worst".getBytes(), "of".getBytes(), "times,", new byte[0] },
77          new Object[] { new byte[0], new byte[0], "", new byte[0] },
78      };
79  
80      Object[][] params = new Object[][] {
81          { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args },
82          { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args },
83      };
84      return Arrays.asList(params);
85    }
86  
87    static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR =
88        new Comparator<byte[]>() {
89          @Override
90          public int compare(byte[] o1, byte[] o2) {
91            if (o1 == o2) return 0;
92            if (null == o1) return -1;
93            if (null == o2) return 1;
94            return Bytes.compareTo(o1, o2);
95          }
96        };
97  
98    /**
99     * A simple object to serialize.
100    */
101   private static class Pojo1 implements Comparable<Pojo1> {
102     final String stringFieldAsc;
103     final int intFieldAsc;
104     final double doubleFieldAsc;
105     final transient String str;
106 
107     public Pojo1(Object... argv) {
108       stringFieldAsc = (String) argv[0];
109       intFieldAsc = (Integer) argv[1];
110       doubleFieldAsc = (Double) argv[2];
111       str = new StringBuilder()
112             .append("{ ")
113             .append(null == stringFieldAsc ? "" : "\"")
114             .append(stringFieldAsc)
115             .append(null == stringFieldAsc ? "" : "\"").append(", ")
116             .append(intFieldAsc).append(", ")
117             .append(doubleFieldAsc)
118             .append(" }")
119             .toString();
120     }
121 
122     @Override
123     public String toString() {
124       return str;
125     }
126 
127     @Override
128     public int compareTo(Pojo1 o) {
129       int cmp = stringFieldAsc.compareTo(o.stringFieldAsc);
130       if (cmp != 0) return cmp;
131       cmp = Integer.valueOf(intFieldAsc).compareTo(Integer.valueOf(o.intFieldAsc));
132       if (cmp != 0) return cmp;
133       return Double.compare(doubleFieldAsc, o.doubleFieldAsc);
134     }
135 
136     @Override
137     public boolean equals(Object o) {
138       if (this == o) return true;
139       if (null == o) return false;
140       if (!(o instanceof Pojo1)) return false;
141       Pojo1 that = (Pojo1) o;
142       return 0 == this.compareTo(that);
143     }
144   }
145 
146   /**
147    * A simple object to serialize.
148    */
149   private static class Pojo2 implements Comparable<Pojo2> {
150     final byte[] byteField1Asc;
151     final byte[] byteField2Dsc;
152     final String stringFieldDsc;
153     final byte[] byteField3Dsc;
154     final transient String str;
155 
156     public Pojo2(Object... vals) {
157       byte[] empty = new byte[0];
158       byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : empty;
159       byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : empty;
160       stringFieldDsc = vals.length > 2 ? (String) vals[2] : "";
161       byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : empty;
162       str = new StringBuilder()
163             .append("{ ")
164             .append(Bytes.toStringBinary(byteField1Asc)).append(", ")
165             .append(Bytes.toStringBinary(byteField2Dsc)).append(", ")
166             .append(null == stringFieldDsc ? "" : "\"")
167             .append(stringFieldDsc)
168             .append(null == stringFieldDsc ? "" : "\"").append(", ")
169             .append(Bytes.toStringBinary(byteField3Dsc))
170             .append(" }")
171             .toString();
172     }
173 
174     @Override
175     public String toString() {
176       return str;
177     }
178 
179     @Override
180     public int compareTo(Pojo2 o) {
181       int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc);
182       if (cmp != 0) return cmp;
183       cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc);
184       if (cmp != 0) return cmp;
185       if (stringFieldDsc == o.stringFieldDsc) cmp = 0;
186       else if (null == stringFieldDsc) cmp = 1;
187       else if (null == o.stringFieldDsc) cmp = -1;
188       else cmp = -stringFieldDsc.compareTo(o.stringFieldDsc);
189       if (cmp != 0) return cmp;
190       return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc);
191     }
192 
193     @Override
194     public boolean equals(Object o) {
195       if (this == o) return true;
196       if (null == o) return false;
197       if (!(o instanceof Pojo2)) return false;
198       Pojo2 that = (Pojo2) o;
199       return 0 == this.compareTo(that);
200     }
201   }
202 
203   /**
204    * A custom data type implementation specialized for {@link Pojo1}.
205    */
206   private static class SpecializedPojo1Type1 implements DataType<Pojo1> {
207 
208     private static final RawStringTerminated stringField = new RawStringTerminated("/");
209     private static final RawInteger intField = new RawInteger();
210     private static final RawDouble doubleField = new RawDouble();
211 
212     /**
213      * The {@link Struct} equivalent of this type.
214      */
215     public static Struct GENERIC =
216         new StructBuilder().add(stringField)
217                            .add(intField)
218                            .add(doubleField)
219                            .toStruct();
220 
221     @Override
222     public boolean isOrderPreserving() { return true; }
223 
224     @Override
225     public Order getOrder() { return null; }
226 
227     @Override
228     public boolean isNullable() { return false; }
229 
230     @Override
231     public boolean isSkippable() { return true; }
232 
233     @Override
234     public int encodedLength(Pojo1 val) {
235       return
236           stringField.encodedLength(val.stringFieldAsc) +
237           intField.encodedLength(val.intFieldAsc) +
238           doubleField.encodedLength(val.doubleFieldAsc);
239     }
240 
241     @Override
242     public Class<Pojo1> encodedClass() { return Pojo1.class; }
243 
244     @Override
245     public int skip(PositionedByteRange src) {
246       int skipped = stringField.skip(src);
247       skipped += intField.skip(src);
248       skipped += doubleField.skip(src);
249       return skipped;
250     }
251 
252     @Override
253     public Pojo1 decode(PositionedByteRange src) {
254       Object[] ret = new Object[3];
255       ret[0] = stringField.decode(src);
256       ret[1] = intField.decode(src);
257       ret[2] = doubleField.decode(src);
258       return new Pojo1(ret);
259     }
260 
261     @Override
262     public int encode(PositionedByteRange dst, Pojo1 val) {
263       int written = stringField.encode(dst, val.stringFieldAsc);
264       written += intField.encode(dst, val.intFieldAsc);
265       written += doubleField.encode(dst, val.doubleFieldAsc);
266       return written;
267     }
268   }
269 
270   /**
271    * A custom data type implementation specialized for {@link Pojo2}.
272    */
273   private static class SpecializedPojo2Type1 implements DataType<Pojo2> {
274 
275     private static RawBytesTerminated byteField1 = new RawBytesTerminated("/");
276     private static RawBytesTerminated byteField2 =
277         new RawBytesTerminated(Order.DESCENDING, "/");
278     private static RawStringTerminated stringField =
279         new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 });
280     private static RawBytes byteField3 = RawBytes.DESCENDING;
281 
282     /**
283      * The {@link Struct} equivalent of this type.
284      */
285     public static Struct GENERIC =
286         new StructBuilder().add(byteField1)
287                            .add(byteField2)
288                            .add(stringField)
289                            .add(byteField3)
290                            .toStruct();
291 
292     @Override
293     public boolean isOrderPreserving() { return true; }
294 
295     @Override
296     public Order getOrder() { return null; }
297 
298     @Override
299     public boolean isNullable() { return false; }
300 
301     @Override
302     public boolean isSkippable() { return true; }
303 
304     @Override
305     public int encodedLength(Pojo2 val) {
306       return
307           byteField1.encodedLength(val.byteField1Asc) +
308           byteField2.encodedLength(val.byteField2Dsc) +
309           stringField.encodedLength(val.stringFieldDsc) +
310           byteField3.encodedLength(val.byteField3Dsc);
311     }
312 
313     @Override
314     public Class<Pojo2> encodedClass() { return Pojo2.class; }
315 
316     @Override
317     public int skip(PositionedByteRange src) {
318       int skipped = byteField1.skip(src);
319       skipped += byteField2.skip(src);
320       skipped += stringField.skip(src);
321       skipped += byteField3.skip(src);
322       return skipped;
323     }
324 
325     @Override
326     public Pojo2 decode(PositionedByteRange src) {
327       Object[] ret = new Object[4];
328       ret[0] = byteField1.decode(src);
329       ret[1] = byteField2.decode(src);
330       ret[2] = stringField.decode(src);
331       ret[3] = byteField3.decode(src);
332       return new Pojo2(ret);
333     }
334 
335     @Override
336     public int encode(PositionedByteRange dst, Pojo2 val) {
337       int written = byteField1.encode(dst, val.byteField1Asc);
338       written += byteField2.encode(dst, val.byteField2Dsc);
339       written += stringField.encode(dst, val.stringFieldDsc);
340       written += byteField3.encode(dst, val.byteField3Dsc);
341       return written;
342     }
343   }
344 
345   @Test
346   @SuppressWarnings("unchecked")
347   public void testOrderPreservation() throws Exception {
348     Object[] vals = new Object[constructorArgs.length];
349     PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length];
350     PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length];
351     Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class);
352     for (int i = 0; i < vals.length; i++) {
353       vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] });
354       encodedGeneric[i] = new SimplePositionedByteRange(generic.encodedLength(constructorArgs[i]));
355       encodedSpecialized[i] = new SimplePositionedByteRange(specialized.encodedLength(vals[i]));
356     }
357 
358     // populate our arrays
359     for (int i = 0; i < vals.length; i++) {
360       generic.encode(encodedGeneric[i], constructorArgs[i]);
361       encodedGeneric[i].setPosition(0);
362       specialized.encode(encodedSpecialized[i], vals[i]);
363       encodedSpecialized[i].setPosition(0);
364       assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes());
365     }
366 
367     Arrays.sort(vals);
368     Arrays.sort(encodedGeneric);
369     Arrays.sort(encodedSpecialized);
370 
371     for (int i = 0; i < vals.length; i++) {
372       assertEquals(
373         "Struct encoder does not preserve sort order at position " + i,
374         vals[i],
375         ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) }));
376       assertEquals(
377         "Specialized encoder does not preserve sort order at position " + i,
378         vals[i], specialized.decode(encodedSpecialized[i]));
379     }
380   }
381 }