/**
* 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
*
* http://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.hadoop.hbase;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.classification.InterfaceStability;
import org.apache.hadoop.hbase.testclassification.SmallTests;
import org.apache.hadoop.hbase.ClassFinder.And;
import org.apache.hadoop.hbase.ClassFinder.FileNameFilter;
import org.apache.hadoop.hbase.ClassFinder.Not;
import org.apache.hadoop.hbase.ClassTestFinder.TestClassFilter;
import org.apache.hadoop.hbase.ClassTestFinder.TestFileNameFilter;
import org.junit.Assert;
import org.junit.Test;
import org.junit.experimental.categories.Category;
/**
* Test cases for ensuring our client visible classes have annotations
* for {@link InterfaceAudience}.
*
* All classes in hbase-client and hbase-common module MUST have InterfaceAudience
* annotations. All InterfaceAudience.Public annotated classes MUST also have InterfaceStability
* annotations. Think twice about marking an interface InterfaceAudience.Public. Make sure that
* it is an interface, not a class (for most cases), and clients will actually depend on it. Once
* something is marked with Public, we cannot change the signatures within the major release. NOT
* everything in the hbase-client module or every java public class has to be marked with
* InterfaceAudience.Public. ONLY the ones that an hbase application will directly use (Table, Get,
* etc, versus ProtobufUtil).
*
* Also note that HBase has it's own annotations in hbase-annotations module with the same names
* as in Hadoop. You should use the HBase's classes.
*
* See https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/InterfaceClassification.html
* and https://issues.apache.org/jira/browse/HBASE-10462.
*/
@Category(SmallTests.class)
public class TestInterfaceAudienceAnnotations {
private static final Log LOG = LogFactory.getLog(TestInterfaceAudienceAnnotations.class);
/** Selects classes with generated in their package name */
class GeneratedClassFilter implements ClassFinder.ClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
return c.getPackage().getName().contains("generated");
}
}
/** Selects classes with one of the {@link InterfaceAudience} annotation in their class
* declaration.
*/
class InterfaceAudienceAnnotatedClassFilter implements ClassFinder.ClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
if (getAnnotation(c) != null) {
// class itself has a declared annotation.
return true;
}
// If this is an internal class, look for the encapsulating class to see whether it has
// annotation. All inner classes of private classes are considered annotated.
return isAnnotatedPrivate(c.getEnclosingClass());
}
private boolean isAnnotatedPrivate(Class<?> c) {
if (c == null) {
return false;
}
Class<?> ann = getAnnotation(c);
if (ann != null &&
!InterfaceAudience.Public.class.equals(ann)) {
return true;
}
return isAnnotatedPrivate(c.getEnclosingClass());
}
protected Class<?> getAnnotation(Class<?> c) {
// we should get only declared annotations, not inherited ones
Annotation[] anns = c.getDeclaredAnnotations();
for (Annotation ann : anns) {
// Hadoop clearly got it wrong for not making the annotation values (private, public, ..)
// an enum instead we have three independent annotations!
Class<?> type = ann.annotationType();
if (isInterfaceAudienceClass(type)) {
return type;
}
}
return null;
}
}
/** Selects classes with one of the {@link InterfaceStability} annotation in their class
* declaration.
*/
class InterfaceStabilityAnnotatedClassFilter implements ClassFinder.ClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
if (getAnnotation(c) != null) {
// class itself has a declared annotation.
return true;
}
return false;
}
protected Class<?> getAnnotation(Class<?> c) {
// we should get only declared annotations, not inherited ones
Annotation[] anns = c.getDeclaredAnnotations();
for (Annotation ann : anns) {
// Hadoop clearly got it wrong for not making the annotation values (private, public, ..)
// an enum instead we have three independent annotations!
Class<?> type = ann.annotationType();
if (isInterfaceStabilityClass(type)) {
return type;
}
}
return null;
}
}
/** Selects classes with one of the {@link InterfaceAudience.Public} annotation in their
* class declaration.
*/
class InterfaceAudiencePublicAnnotatedClassFilter extends InterfaceAudienceAnnotatedClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
return (InterfaceAudience.Public.class.equals(getAnnotation(c)));
}
}
/**
* Selects InterfaceAudience or InterfaceStability classes. Don't go meta!!!
*/
class IsInterfaceStabilityClassFilter implements ClassFinder.ClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
return
isInterfaceAudienceClass(c) ||
isInterfaceStabilityClass(c);
}
}
private boolean isInterfaceAudienceClass(Class<?> c) {
return
c.equals(InterfaceAudience.Public.class) ||
c.equals(InterfaceAudience.Private.class) ||
c.equals(InterfaceAudience.LimitedPrivate.class);
}
private boolean isInterfaceStabilityClass(Class<?> c) {
return
c.equals(InterfaceStability.Stable.class) ||
c.equals(InterfaceStability.Unstable.class) ||
c.equals(InterfaceStability.Evolving.class);
}
/** Selects classes that are declared public */
class PublicClassFilter implements ClassFinder.ClassFilter {
@Override
public boolean isCandidateClass(Class<?> c) {
int mod = c.getModifiers();
return Modifier.isPublic(mod);
}
}
/** Selects paths (jars and class dirs) only from the main code, not test classes */
class MainCodeResourcePathFilter implements ClassFinder.ResourcePathFilter {
@Override
public boolean isCandidatePath(String resourcePath, boolean isJar) {
return !resourcePath.contains("test-classes") &&
!resourcePath.contains("tests.jar");
}
}
/**
* Checks whether all the classes in client and common modules contain
* {@link InterfaceAudience} annotations.
*/
@Test
public void testInterfaceAudienceAnnotation()
throws ClassNotFoundException, IOException, LinkageError {
// find classes that are:
// In the main jar
// AND are public
// NOT test classes
// AND NOT generated classes
// AND are NOT annotated with InterfaceAudience
ClassFinder classFinder = new ClassFinder(
new MainCodeResourcePathFilter(),
new Not((FileNameFilter)new TestFileNameFilter()),
new And(new PublicClassFilter(),
new Not(new TestClassFilter()),
new Not(new GeneratedClassFilter()),
new Not(new IsInterfaceStabilityClassFilter()),
new Not(new InterfaceAudienceAnnotatedClassFilter()))
);
Set<Class<?>> classes = classFinder.findClasses(false);
LOG.info("These are the classes that DO NOT have @InterfaceAudience annotation:");
for (Class<?> clazz : classes) {
LOG.info(clazz);
}
Assert.assertEquals("All classes should have @InterfaceAudience annotation",
0, classes.size());
}
/**
* Checks whether all the classes in client and common modules that are marked
* InterfaceAudience.Public also have {@link InterfaceStability} annotations.
*/
@Test
public void testInterfaceStabilityAnnotation()
throws ClassNotFoundException, IOException, LinkageError {
// find classes that are:
// In the main jar
// AND are public
// NOT test classes
// AND NOT generated classes
// AND are annotated with InterfaceAudience.Public
// AND NOT annotated with InterfaceStability
ClassFinder classFinder = new ClassFinder(
new MainCodeResourcePathFilter(),
new Not((FileNameFilter)new TestFileNameFilter()),
new And(new PublicClassFilter(),
new Not(new TestClassFilter()),
new Not(new GeneratedClassFilter()),
new InterfaceAudiencePublicAnnotatedClassFilter(),
new Not(new IsInterfaceStabilityClassFilter()),
new Not(new InterfaceStabilityAnnotatedClassFilter()))
);
Set<Class<?>> classes = classFinder.findClasses(false);
LOG.info("These are the classes that DO NOT have @InterfaceStability annotation:");
for (Class<?> clazz : classes) {
LOG.info(clazz);
}
Assert.assertEquals("All classes that are marked with @InterfaceAudience.Public should "
+ "have @InterfaceStability annotation as well",
0, classes.size());
}
}