/** * All classes transitively reachable via only public method signatures of the SDK. * * <p>Note that our idea of "public" does not include various internal-only APIs. */ public static ApiSurface getSdkApiSurface(final ClassLoader classLoader) throws IOException { return ApiSurface.ofPackage("org.apache.beam", classLoader) .pruningPattern("org[.]apache[.]beam[.].*Test") // Exposes Guava, but not intended for users .pruningClassName("org.apache.beam.sdk.util.common.ReflectHelpers") // test only .pruningClassName("org.apache.beam.sdk.testing.InterceptingUrlClassLoader") // test only .pruningPrefix("org.apache.beam.model.") .pruningPrefix("org.apache.beam.vendor.") .pruningPrefix("java"); }
/** See {@link ApiSurface#containsOnlyPackages(Set)}. */ public static Matcher<ApiSurface> containsOnlyPackages(final String... packageNames) { return containsOnlyPackages(Sets.newHashSet(packageNames)); }
/** Returns an {@link ApiSurface} object representing just the surface of the given class. */ public static ApiSurface ofClass(Class<?> clazz) { return ApiSurface.empty().includingClass(clazz); }
@Test public void testGcpCoreApiSurface() throws Exception { final Package thisPackage = getClass().getPackage(); final ClassLoader thisClassLoader = getClass().getClassLoader(); final ApiSurface apiSurface = ApiSurface.ofPackage(thisPackage, thisClassLoader) .pruningPattern("org[.]apache[.]beam[.].*Test.*") .pruningPattern("org[.]apache[.]beam[.].*IT") .pruningPattern("java[.]lang.*") .pruningPattern("java[.]util.*"); @SuppressWarnings("unchecked") final Set<Matcher<Class<?>>> allowedClasses = ImmutableSet.of( classesInPackage("com.google.api.client.googleapis"), classesInPackage("com.google.api.client.http"), classesInPackage("com.google.api.client.json"), classesInPackage("com.google.api.client.util"), classesInPackage("com.google.api.services.storage"), classesInPackage("com.google.auth"), classesInPackage("com.fasterxml.jackson.annotation"), classesInPackage("java"), classesInPackage("javax"), classesInPackage("org.apache.beam.sdk"), classesInPackage("org.joda.time")); assertThat(apiSurface, containsOnlyClassesMatching(allowedClasses)); } }
final ClassLoader thisClassLoader = getClass().getClassLoader(); ApiSurface apiSurface = ApiSurface.ofPackage(thisPackage, thisClassLoader) .pruningClass(Pipeline.class) .pruningClass(PipelineRunner.class) .pruningClass(PipelineOptions.class) .pruningClass(PipelineOptionsRegistrar.class) .pruningClass(PipelineOptions.DirectRunner.class) .pruningClass(DisplayData.Builder.class) .pruningClass(MetricResults.class) .pruningClass(DirectGraphs.class) .pruningClass( WatermarkManager.class /* TODO: BEAM-4237 Consider moving to local-java */) .pruningClass(ExecutableGraphBuilder.class) .pruningPattern( "org[.]apache[.]beam[.]runners[.]direct[.]portable.*" .pruningPattern("org[.]apache[.]beam[.].*Test.*") .pruningPattern("org[.]apache[.]beam[.].*IT") .pruningPattern("java[.]io.*") .pruningPattern("java[.]lang.*") .pruningPattern("java[.]util.*"); assertThat(apiSurface, containsOnlyPackages(allowed));
@Test public void testIgnoreAll() throws Exception { ApiSurface apiSurface = ApiSurface.ofClass(ExposedWildcardBound.class) .includingClass(Object.class) .includingClass(ApiSurface.class) .pruningPattern(".*"); assertThat(apiSurface.getExposedClasses(), emptyIterable()); }
/** See {@link #pruningPattern(Pattern)}. */ public ApiSurface pruningPattern(String patternString) { return pruningPattern(Pattern.compile(patternString)); }
/** Returns an {@link ApiSurface} object representing the given package and all subpackages. */ public static ApiSurface ofPackage(Package aPackage, ClassLoader classLoader) throws IOException { return ofPackage(aPackage.getName(), classLoader); }
@SuppressWarnings({"rawtypes", "unchecked"}) private void assertExposed(final Class classToExamine, final Class... exposedClasses) { final ApiSurface apiSurface = ApiSurface.ofClass(classToExamine).pruningPrefix("java"); final ImmutableSet<Matcher<Class<?>>> allowed = FluentIterable.from( Iterables.concat(Sets.newHashSet(classToExamine), Sets.newHashSet(exposedClasses))) .transform(Matchers::<Class<?>>equalTo) .toSet(); assertThat(apiSurface, containsOnlyClassesMatching(allowed)); }
@Test public void testprunedPattern() throws Exception { ApiSurface apiSurface = ApiSurface.ofClass(NotPruned.class).pruningClass(PrunedPattern.class); assertThat(apiSurface.getExposedClasses(), containsInAnyOrder((Class) NotPruned.class)); }
/** * A factory method to create an {@link ApiSurface} matcher, producing a positive match if the * queried api surface contains classes ONLY from specified package names. */ public static Matcher<ApiSurface> containsOnlyPackages(final Set<String> packageNames) { final Function<String, Matcher<Class<?>>> packageNameToClassMatcher = ApiSurface::classesInPackage; final ImmutableSet<Matcher<Class<?>>> classesInPackages = FluentIterable.from(packageNames).transform(packageNameToClassMatcher).toSet(); return containsOnlyClassesMatching(classesInPackages); }
private boolean verifyNoDisallowed( final ApiSurface checkedApiSurface, final Set<Matcher<Class<?>>> allowedClasses, final Description mismatchDescription) { /* <helper_lambdas> */ final Function<Class<?>, List<Class<?>>> toExposure = checkedApiSurface::getAnyExposurePath; final Maps.EntryTransformer<Class<?>, List<Class<?>>, String> toMessage = (aClass, exposure) -> aClass + " exposed via:\n\t\t" + Joiner.on("\n\t\t").join(exposure); final Predicate<Class<?>> disallowed = aClass -> !classIsAllowed(aClass, allowedClasses); /* </helper_lambdas> */ final FluentIterable<Class<?>> disallowedClasses = FluentIterable.from(checkedApiSurface.getExposedClasses()).filter(disallowed); final ImmutableMap<Class<?>, List<Class<?>>> exposures = Maps.toMap(disallowedClasses, toExposure); final ImmutableList<String> messages = FluentIterable.from(Maps.transformEntries(exposures, toMessage).values()) .toSortedList(Ordering.natural()); if (!messages.isEmpty()) { mismatchDescription.appendText( "The following disallowed classes appeared on the API surface:\n\t" + Joiner.on("\n\t").join(messages)); } return messages.isEmpty(); }
/** * Returns an {@link ApiSurface} like this one, but pruning references from the provided class. */ public ApiSurface pruningClass(Class<?> clazz) { return pruningClassName(clazz.getName()); }
ApiSurface.ofPackage(thisPackage, thisClassLoader) .pruningPattern(BigqueryMatcher.class.getName()) .pruningPattern(BigqueryClient.class.getName()) .pruningPattern("org[.]apache[.]beam[.].*Test.*") .pruningPattern("org[.]apache[.]beam[.].*IT") .pruningPattern("java[.]lang.*") .pruningPattern("java[.]util.*"); classesInPackage("com.google.api.core"), classesInPackage("com.google.api.client.googleapis"), classesInPackage("com.google.api.client.http"), classesInPackage("com.google.api.client.json"), classesInPackage("com.google.api.client.util"), classesInPackage("com.google.api.services.bigquery.model"), classesInPackage("com.google.auth"), classesInPackage("com.google.bigtable.v2"), classesInPackage("com.google.cloud.bigtable.config"), classesInPackage("com.google.spanner.v1"), Matchers.equalTo(com.google.api.gax.rpc.ApiException.class), Matchers.<Class<?>>equalTo(com.google.api.gax.paging.Page.class), Matchers.<Class<?>>equalTo(com.google.cloud.Date.class), Matchers.<Class<?>>equalTo(com.google.cloud.Timestamp.class), classesInPackage("com.google.cloud.spanner"), classesInPackage("com.google.spanner.admin.database.v1"), classesInPackage("com.google.datastore.v1"), classesInPackage("com.google.protobuf"), classesInPackage("com.google.type"), classesInPackage("com.fasterxml.jackson.annotation"),
/** * Returns an {@link ApiSurface} like this one, but pruning transitive references from classes * whose full name (including package) begins with the provided prefix. */ public ApiSurface pruningPrefix(String prefix) { return pruningPattern(Pattern.compile(Pattern.quote(prefix) + ".*")); }
FluentIterable.from(checkedApiSurface.getExposedClasses()) .anyMatch(classMatcher::matches);
/** Returns an {@link ApiSurface} like this one, but pruning references from the named class. */ public ApiSurface pruningClassName(String className) { return pruningPattern(Pattern.compile(Pattern.quote(className))); }
@Test public void testSdkApiSurface() throws Exception { @SuppressWarnings("unchecked") final Set<String> allowed = ImmutableSet.of( "org.apache.beam", "com.fasterxml.jackson.annotation", "com.fasterxml.jackson.core", "com.fasterxml.jackson.databind", "org.apache.avro", "org.hamcrest", // via DataflowMatchers "org.codehaus.jackson", // via Avro "org.joda.time", "org.junit"); assertThat(getSdkApiSurface(getClass().getClassLoader()), containsOnlyPackages(allowed)); } }