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.filter;
019
020import static org.junit.Assert.assertNull;
021import static org.junit.Assert.assertTrue;
022import static org.junit.Assert.fail;
023
024import java.io.IOException;
025import java.math.BigDecimal;
026import java.nio.charset.Charset;
027import java.util.Collections;
028import java.util.regex.Pattern;
029import org.apache.commons.io.IOUtils;
030import org.apache.commons.text.StringSubstitutor;
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.hbase.HBaseClassTestRule;
033import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
034import org.apache.hadoop.hbase.HBaseConfiguration;
035import org.apache.hadoop.hbase.HBaseTestingUtil;
036import org.apache.hadoop.hbase.testclassification.FilterTests;
037import org.apache.hadoop.hbase.testclassification.SmallTests;
038import org.apache.hadoop.hbase.util.Bytes;
039import org.apache.hadoop.hbase.util.ClassLoaderTestHelper;
040import org.junit.AfterClass;
041import org.junit.ClassRule;
042import org.junit.Test;
043import org.junit.experimental.categories.Category;
044import org.junit.runner.RunWith;
045import org.junit.runners.Parameterized;
046
047import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
048import org.apache.hadoop.hbase.shaded.protobuf.generated.ComparatorProtos;
049
050@RunWith(Parameterized.class)
051@Category({ FilterTests.class, SmallTests.class })
052public class TestComparatorSerialization {
053
054  @ClassRule
055  public static final HBaseClassTestRule CLASS_RULE =
056    HBaseClassTestRule.forClass(TestComparatorSerialization.class);
057
058  @Parameterized.Parameter(0)
059  public boolean allowFastReflectionFallthrough;
060
061  @Parameterized.Parameters(name = "{index}: allowFastReflectionFallthrough={0}")
062  public static Iterable<Object[]> data() {
063    return HBaseCommonTestingUtil.BOOLEAN_PARAMETERIZED;
064  }
065
066  @AfterClass
067  public static void afterClass() throws Exception {
068    // set back to true so that it doesn't affect any other tests
069    ProtobufUtil.setAllowFastReflectionFallthrough(true);
070  }
071
072  @Test
073  public void testBinaryComparator() throws Exception {
074    BinaryComparator binaryComparator = new BinaryComparator(Bytes.toBytes("binaryComparator"));
075    assertTrue(binaryComparator.areSerializedFieldsEqual(
076      ProtobufUtil.toComparator(ProtobufUtil.toComparator(binaryComparator))));
077  }
078
079  @Test
080  public void testBinaryPrefixComparator() throws Exception {
081    BinaryPrefixComparator binaryPrefixComparator =
082      new BinaryPrefixComparator(Bytes.toBytes("binaryPrefixComparator"));
083    assertTrue(binaryPrefixComparator.areSerializedFieldsEqual(
084      ProtobufUtil.toComparator(ProtobufUtil.toComparator(binaryPrefixComparator))));
085  }
086
087  @Test
088  public void testBitComparator() throws Exception {
089    BitComparator bitComparator =
090      new BitComparator(Bytes.toBytes("bitComparator"), BitComparator.BitwiseOp.XOR);
091    assertTrue(bitComparator.areSerializedFieldsEqual(
092      ProtobufUtil.toComparator(ProtobufUtil.toComparator(bitComparator))));
093  }
094
095  @Test
096  public void testNullComparator() throws Exception {
097    NullComparator nullComparator = new NullComparator();
098    assertTrue(nullComparator.areSerializedFieldsEqual(
099      ProtobufUtil.toComparator(ProtobufUtil.toComparator(nullComparator))));
100  }
101
102  @Test
103  public void testRegexStringComparator() throws Exception {
104    // test without specifying flags
105    RegexStringComparator regexStringComparator = new RegexStringComparator(".+-2");
106    assertTrue(regexStringComparator.areSerializedFieldsEqual(
107      ProtobufUtil.toComparator(ProtobufUtil.toComparator(regexStringComparator))));
108
109    // test with specifying flags
110    try {
111      new RegexStringComparator("regex", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
112    } catch (Throwable t) {
113      assertNull("Exception occurred while created the RegexStringComparator object", t);
114    }
115  }
116
117  @Test
118  public void testSubstringComparator() throws Exception {
119    SubstringComparator substringComparator = new SubstringComparator("substr");
120    assertTrue(substringComparator.areSerializedFieldsEqual(
121      ProtobufUtil.toComparator(ProtobufUtil.toComparator(substringComparator))));
122  }
123
124  @Test
125  public void testBigDecimalComparator() throws Exception {
126    BigDecimal bigDecimal = new BigDecimal(Double.MIN_VALUE);
127    BigDecimalComparator bigDecimalComparator = new BigDecimalComparator(bigDecimal);
128    assertTrue(bigDecimalComparator.areSerializedFieldsEqual(
129      ProtobufUtil.toComparator(ProtobufUtil.toComparator(bigDecimalComparator))));
130  }
131
132  /**
133   * Test that we can load and deserialize custom comparators. Good to have generally, but also
134   * proves that this still works after HBASE-27276 despite not going through our fast function
135   * caches.
136   */
137  @Test
138  public void testCustomComparator() throws Exception {
139    ByteArrayComparable baseFilter = new BinaryComparator("foo".getBytes());
140    ComparatorProtos.Comparator proto = ProtobufUtil.toComparator(baseFilter);
141    String suffix = "" + System.currentTimeMillis() + allowFastReflectionFallthrough;
142    String className = "CustomLoadedComparator" + suffix;
143    proto = proto.toBuilder().setName(className).build();
144
145    Configuration conf = HBaseConfiguration.create();
146    HBaseTestingUtil testUtil = new HBaseTestingUtil();
147    String dataTestDir = testUtil.getDataTestDir().toString();
148
149    // First make sure the test bed is clean, delete any pre-existing class.
150    // Below toComparator call is expected to fail because the comparator is not loaded now
151    ClassLoaderTestHelper.deleteClass(className, dataTestDir, conf);
152    try {
153      ProtobufUtil.toComparator(proto);
154      fail("expected to fail");
155    } catch (IOException e) {
156      // do nothing, this is expected
157    }
158
159    // Write a jar to be loaded into the classloader
160    String code = StringSubstitutor.replace(
161      IOUtils.toString(getClass().getResourceAsStream("/CustomLoadedComparator.java.template"),
162        Charset.defaultCharset()),
163      Collections.singletonMap("suffix", suffix));
164    ClassLoaderTestHelper.buildJar(dataTestDir, className, code,
165      ClassLoaderTestHelper.localDirPath(conf));
166
167    // Disallow fallthrough at first. We expect below to fail because the custom comparator is not
168    // available at initialization so not in the cache.
169    ProtobufUtil.setAllowFastReflectionFallthrough(false);
170    try {
171      ProtobufUtil.toComparator(proto);
172      fail("expected to fail");
173    } catch (IOException e) {
174      // do nothing, this is expected
175    }
176
177    // Now the deserialization should pass with fallthrough enabled. This proves that custom
178    // comparators can work despite not being supported by cache.
179    ProtobufUtil.setAllowFastReflectionFallthrough(true);
180    ProtobufUtil.toComparator(proto);
181  }
182
183}