You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by rs...@apache.org on 2022/03/18 19:20:12 UTC

[avro] branch master updated: AVRO-3441: ServiceLoader for LogicalTypeFactory (#1590)

This is an automated email from the ASF dual-hosted git repository.

rskraba pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/master by this push:
     new ee4725c  AVRO-3441: ServiceLoader for LogicalTypeFactory (#1590)
ee4725c is described below

commit ee4725c64807549ec74e20e83d35cfc1fe8e90a8
Author: Oscar Westra van Holthe - Kind <op...@users.noreply.github.com>
AuthorDate: Fri Mar 18 20:18:52 2022 +0100

    AVRO-3441: ServiceLoader for LogicalTypeFactory (#1590)
    
    Automatically register LogicalTypeFactory classes using the Java 6
    service loader upon startup. This works for any LogicalTypeFactory that
    does not need constructor parameters.
---
 .../main/java/org/apache/avro/LogicalTypes.java    | 23 +++++++++++++++++
 .../org/apache/avro/DummyLogicalTypeFactory.java   | 30 ++++++++++++++++++++++
 .../test/java/org/apache/avro/TestLogicalType.java | 19 ++++++++++----
 ...org.apache.avro.LogicalTypes$LogicalTypeFactory | 17 ++++++++++++
 4 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
index 5b03e15..7bb00f8 100644
--- a/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
+++ b/lang/java/avro/src/main/java/org/apache/avro/LogicalTypes.java
@@ -21,6 +21,7 @@ package org.apache.avro;
 import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
+import java.util.ServiceLoader;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.slf4j.Logger;
@@ -30,6 +31,22 @@ public class LogicalTypes {
 
   private static final Logger LOG = LoggerFactory.getLogger(LogicalTypes.class);
 
+  /**
+   * Factory interface and SPI for logical types. A {@code LogicalTypeFactory} can
+   * be registered in two ways:
+   *
+   * <ol>
+   * <li>Manually, via {@link #register(LogicalTypeFactory)} or
+   * {@link #register(String, LogicalTypeFactory)}</li>
+   *
+   * <li>Automatically, when the {@code LogicalTypeFactory} implementation is a
+   * public class with a public no-arg constructor, is named in a file called
+   * {@code /META-INF/services/org.apache.avro.LogicalTypes$LogicalTypeFactory},
+   * and both are available in the classpath</li>
+   * </ol>
+   *
+   * @see ServiceLoader
+   */
   public interface LogicalTypeFactory {
     LogicalType fromSchema(Schema schema);
 
@@ -40,6 +57,12 @@ public class LogicalTypes {
 
   private static final Map<String, LogicalTypeFactory> REGISTERED_TYPES = new ConcurrentHashMap<>();
 
+  static {
+    for (LogicalTypeFactory logicalTypeFactory : ServiceLoader.load(LogicalTypeFactory.class)) {
+      register(logicalTypeFactory);
+    }
+  }
+
   /**
    * Register a logical type.
    *
diff --git a/lang/java/avro/src/test/java/org/apache/avro/DummyLogicalTypeFactory.java b/lang/java/avro/src/test/java/org/apache/avro/DummyLogicalTypeFactory.java
new file mode 100644
index 0000000..4957e37
--- /dev/null
+++ b/lang/java/avro/src/test/java/org/apache/avro/DummyLogicalTypeFactory.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.avro;
+
+public class DummyLogicalTypeFactory implements LogicalTypes.LogicalTypeFactory {
+  @Override
+  public LogicalType fromSchema(Schema schema) {
+    return LogicalTypes.date();
+  }
+
+  @Override
+  public String getTypeName() {
+    return "service-example";
+  }
+}
diff --git a/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java b/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
index 7b1f5bf..deadb2e 100644
--- a/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
+++ b/lang/java/avro/src/test/java/org/apache/avro/TestLogicalType.java
@@ -26,6 +26,9 @@ import org.hamcrest.collection.IsMapContaining;
 import org.junit.Assert;
 import org.junit.Test;
 
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.instanceOf;
+
 public class TestLogicalType {
 
   @Test
@@ -285,14 +288,20 @@ public class TestLogicalType {
         IsMapContaining.hasEntry("logicalTypeName", factory));
   }
 
+  @Test
+  public void testRegisterLogicalTypeFactoryByServiceLoader() {
+    MatcherAssert.assertThat(LogicalTypes.getCustomRegisteredTypes(),
+        IsMapContaining.hasEntry(equalTo("service-example"), instanceOf(LogicalTypes.LogicalTypeFactory.class)));
+  }
+
   public static void assertEqualsTrue(String message, Object o1, Object o2) {
-    Assert.assertTrue("Should be equal (forward): " + message, o1.equals(o2));
-    Assert.assertTrue("Should be equal (reverse): " + message, o2.equals(o1));
+    Assert.assertEquals("Should be equal (forward): " + message, o1, o2);
+    Assert.assertEquals("Should be equal (reverse): " + message, o2, o1);
   }
 
   public static void assertEqualsFalse(String message, Object o1, Object o2) {
-    Assert.assertFalse("Should be equal (forward): " + message, o1.equals(o2));
-    Assert.assertFalse("Should be equal (reverse): " + message, o2.equals(o1));
+    Assert.assertNotEquals("Should be equal (forward): " + message, o1, o2);
+    Assert.assertNotEquals("Should be equal (reverse): " + message, o2, o1);
   }
 
   /**
@@ -305,7 +314,7 @@ public class TestLogicalType {
    * @param callable           A Callable that is expected to throw the exception
    */
   public static void assertThrows(String message, Class<? extends Exception> expected, String containedInMessage,
-      Callable callable) {
+      Callable<?> callable) {
     try {
       callable.call();
       Assert.fail("No exception was thrown (" + message + "), expected: " + expected.getName());
diff --git a/lang/java/avro/src/test/resources/META-INF/services/org.apache.avro.LogicalTypes$LogicalTypeFactory b/lang/java/avro/src/test/resources/META-INF/services/org.apache.avro.LogicalTypes$LogicalTypeFactory
new file mode 100644
index 0000000..e111a25
--- /dev/null
+++ b/lang/java/avro/src/test/resources/META-INF/services/org.apache.avro.LogicalTypes$LogicalTypeFactory
@@ -0,0 +1,17 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+org.apache.avro.DummyLogicalTypeFactory