diff --git a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java index e154b583a78b..0d6874026cb2 100644 --- a/sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java +++ b/sdks/java/core/src/main/java/org/apache/beam/sdk/util/ApiSurface.java @@ -610,22 +610,18 @@ private boolean exposed(int modifiers) { //////////////////////////////////////////////////////////////////////////// + /** + * All classes transitively reachable via only public method signatures of the SDK. + * + *

Note that our idea of "public" does not include various internal-only APIs. + */ public static ApiSurface getSdkApiSurface() throws IOException { return ApiSurface.ofPackage("org.apache.beam") - .pruningPattern("org[.]apache[.]beam.*Test") - .pruningPattern("org[.]apache[.]beam.*Benchmark") - .pruningPrefix("org.apache.beam.integration") - .pruningPrefix("java") - .pruningPrefix("com.google.api") - .pruningPrefix("com.google.auth") - .pruningPrefix("com.google.bigtable.v1") - .pruningPrefix("com.google.cloud.bigtable.config") - .pruningPrefix("com.google.cloud.bigtable.grpc.Bigtable*Name") - .pruningPrefix("com.google.protobuf") - .pruningPrefix("org.joda.time") - .pruningPrefix("org.apache.avro") - .pruningPrefix("org.junit") - .pruningPrefix("com.fasterxml.jackson.annotation"); + .pruningPattern("org[.]apache[.]beam[.].*Test") + + // Exposes Guava, but not intended for users + .pruningClassName("org.apache.beam.sdk.util.common.ReflectHelpers") + .pruningPrefix("java"); } public static void main(String[] args) throws Exception { diff --git a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/ApiSurfaceTest.java b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/ApiSurfaceTest.java index b07167c2f4d3..6e87e91d8361 100644 --- a/sdks/java/core/src/test/java/org/apache/beam/sdk/util/ApiSurfaceTest.java +++ b/sdks/java/core/src/test/java/org/apache/beam/sdk/util/ApiSurfaceTest.java @@ -17,16 +17,21 @@ */ package org.apache.beam.sdk.util; +import static org.hamcrest.Matchers.anyOf; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.emptyIterable; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeDiagnosingMatcher; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -45,15 +50,7 @@ public class ApiSurfaceTest { @Test public void testOurApiSurface() throws Exception { - ApiSurface checkedApiSurface = ApiSurface.getSdkApiSurface() - .pruningClassName("org.apache.beam.sdk.runners.worker.StateFetcher") - .pruningClassName("org.apache.beam.sdk.util.common.ReflectHelpers") - .pruningClassName("org.apache.beam.sdk.DataflowMatchers") - .pruningClassName("org.apache.beam.sdk.TestUtils") - .pruningClassName("org.apache.beam.sdk.WindowMatchers") - .pruningClassName("org.apache.beam.sdk.transforms.display.DisplayDataMatchers"); - - checkedApiSurface.getExposedClasses(); + ApiSurface checkedApiSurface = ApiSurface.getSdkApiSurface(); Map, List>> disallowedClasses = Maps.newHashMap(); for (Class clazz : checkedApiSurface.getExposedClasses()) { @@ -75,8 +72,61 @@ public void testOurApiSurface() throws Exception { } } + @SuppressWarnings("unchecked") + private static final Set>> ALLOWED_PACKAGES = + ImmutableSet.of( + inPackage("org.apache.beam"), + inPackage("com.google.api.client"), + inPackage("com.google.api.services.bigquery"), + inPackage("com.google.api.services.dataflow"), + inPackage("com.google.api.services.datastore"), + inPackage("com.google.api.services.pubsub"), + inPackage("com.google.api.services.storage"), + inPackage("com.google.auth"), + inPackage("com.google.bigtable.v1"), + inPackage("com.google.cloud.bigtable.config"), + inPackage("com.google.cloud.bigtable.grpc"), + inPackage("com.google.protobuf"), + inPackage("com.fasterxml.jackson.annotation"), + inPackage("io.grpc"), + inPackage("org.apache.avro"), + inPackage("org.apache.commons.logging"), // via BigTable + inPackage("org.hamcrest"), // via DataflowMatchers + inPackage("org.codehaus.jackson"), // via Avro + inPackage("org.joda.time"), + inPackage("org.junit"), + inPackage("java")); + + @SuppressWarnings({"rawtypes", "unchecked"}) private boolean classIsAllowed(Class clazz) { - return clazz.getName().startsWith("org.apache.beam"); + // Safe cast inexpressible in Java without rawtypes + return anyOf((Iterable) ALLOWED_PACKAGES).matches(clazz); + } + + private static final Matcher> inPackage(String packageName) { + return new ClassInPackage(packageName); + } + + private static class ClassInPackage extends TypeSafeDiagnosingMatcher> { + + private final String packageName; + + public ClassInPackage(String packageName) { + this.packageName = packageName; + } + + @Override + public void describeTo(Description description) { + description.appendText("Class in package \""); + description.appendText(packageName); + description.appendText("\""); + } + + @Override + protected boolean matchesSafely(Class clazz, Description mismatchDescription) { + return clazz.getName().startsWith(packageName + "."); + } + } //////////////////////////////////////////////////////////////////////////////////