View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase;
20  
21  import java.io.IOException;
22  import java.lang.annotation.Annotation;
23  import java.lang.reflect.Modifier;
24  import java.util.Set;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.hbase.classification.InterfaceAudience;
29  import org.apache.hadoop.hbase.classification.InterfaceStability;
30  import org.apache.hadoop.hbase.testclassification.SmallTests;
31  import org.apache.hadoop.hbase.ClassFinder.And;
32  import org.apache.hadoop.hbase.ClassFinder.FileNameFilter;
33  import org.apache.hadoop.hbase.ClassFinder.Not;
34  import org.apache.hadoop.hbase.ClassTestFinder.TestClassFilter;
35  import org.apache.hadoop.hbase.ClassTestFinder.TestFileNameFilter;
36  import org.junit.Assert;
37  import org.junit.Test;
38  import org.junit.experimental.categories.Category;
39  
40  /**
41   * Test cases for ensuring our client visible classes have annotations
42   * for {@link InterfaceAudience}.
43   *
44   * All classes in hbase-client and hbase-common module MUST have InterfaceAudience
45   * annotations. All InterfaceAudience.Public annotated classes MUST also have InterfaceStability
46   * annotations. Think twice about marking an interface InterfaceAudience.Public. Make sure that
47   * it is an interface, not a class (for most cases), and clients will actually depend on it. Once
48   * something is marked with Public, we cannot change the signatures within the major release. NOT
49   * everything in the hbase-client module or every java public class has to be marked with
50   * InterfaceAudience.Public. ONLY the ones that an hbase application will directly use (Table, Get,
51   * etc, versus ProtobufUtil).
52   *
53   * Also note that HBase has it's own annotations in hbase-annotations module with the same names
54   * as in Hadoop. You should use the HBase's classes.
55   *
56   * See https://hadoop.apache.org/docs/current/hadoop-project-dist/hadoop-common/InterfaceClassification.html
57   * and https://issues.apache.org/jira/browse/HBASE-10462.
58   */
59  @Category(SmallTests.class)
60  public class TestInterfaceAudienceAnnotations {
61  
62    private static final Log LOG = LogFactory.getLog(TestInterfaceAudienceAnnotations.class);
63  
64    /** Selects classes with generated in their package name */
65    class GeneratedClassFilter implements ClassFinder.ClassFilter {
66      @Override
67      public boolean isCandidateClass(Class<?> c) {
68        return c.getPackage().getName().contains("generated");
69      }
70    }
71  
72    /** Selects classes with one of the {@link InterfaceAudience} annotation in their class
73     * declaration.
74     */
75    class InterfaceAudienceAnnotatedClassFilter implements ClassFinder.ClassFilter {
76      @Override
77      public boolean isCandidateClass(Class<?> c) {
78        if (getAnnotation(c) != null) {
79          // class itself has a declared annotation.
80          return true;
81        }
82  
83        // If this is an internal class, look for the encapsulating class to see whether it has
84        // annotation. All inner classes of private classes are considered annotated.
85        return isAnnotatedPrivate(c.getEnclosingClass());
86      }
87  
88      private boolean isAnnotatedPrivate(Class<?> c) {
89        if (c == null) {
90          return false;
91        }
92  
93        Class<?> ann = getAnnotation(c);
94        if (ann != null &&
95          !InterfaceAudience.Public.class.equals(ann)) {
96          return true;
97        }
98  
99        return isAnnotatedPrivate(c.getEnclosingClass());
100     }
101 
102     protected Class<?> getAnnotation(Class<?> c) {
103       // we should get only declared annotations, not inherited ones
104       Annotation[] anns = c.getDeclaredAnnotations();
105 
106       for (Annotation ann : anns) {
107         // Hadoop clearly got it wrong for not making the annotation values (private, public, ..)
108         // an enum instead we have three independent annotations!
109         Class<?> type = ann.annotationType();
110         if (isInterfaceAudienceClass(type)) {
111           return type;
112         }
113       }
114       return null;
115     }
116   }
117 
118   /** Selects classes with one of the {@link InterfaceStability} annotation in their class
119    * declaration.
120    */
121   class InterfaceStabilityAnnotatedClassFilter implements ClassFinder.ClassFilter {
122     @Override
123     public boolean isCandidateClass(Class<?> c) {
124       if (getAnnotation(c) != null) {
125         // class itself has a declared annotation.
126         return true;
127       }
128       return false;
129     }
130 
131     protected Class<?> getAnnotation(Class<?> c) {
132       // we should get only declared annotations, not inherited ones
133       Annotation[] anns = c.getDeclaredAnnotations();
134 
135       for (Annotation ann : anns) {
136         // Hadoop clearly got it wrong for not making the annotation values (private, public, ..)
137         // an enum instead we have three independent annotations!
138         Class<?> type = ann.annotationType();
139         if (isInterfaceStabilityClass(type)) {
140           return type;
141         }
142       }
143       return null;
144     }
145   }
146 
147   /** Selects classes with one of the {@link InterfaceAudience.Public} annotation in their
148    * class declaration.
149    */
150   class InterfaceAudiencePublicAnnotatedClassFilter extends InterfaceAudienceAnnotatedClassFilter {
151     @Override
152     public boolean isCandidateClass(Class<?> c) {
153       return (InterfaceAudience.Public.class.equals(getAnnotation(c)));
154     }
155   }
156 
157   /**
158    * Selects InterfaceAudience or InterfaceStability classes. Don't go meta!!!
159    */
160   class IsInterfaceStabilityClassFilter implements ClassFinder.ClassFilter {
161     @Override
162     public boolean isCandidateClass(Class<?> c) {
163       return
164           isInterfaceAudienceClass(c) ||
165           isInterfaceStabilityClass(c);
166     }
167   }
168 
169   private boolean isInterfaceAudienceClass(Class<?> c) {
170     return
171         c.equals(InterfaceAudience.Public.class) ||
172         c.equals(InterfaceAudience.Private.class) ||
173         c.equals(InterfaceAudience.LimitedPrivate.class);
174   }
175 
176   private boolean isInterfaceStabilityClass(Class<?> c) {
177     return
178         c.equals(InterfaceStability.Stable.class) ||
179         c.equals(InterfaceStability.Unstable.class) ||
180         c.equals(InterfaceStability.Evolving.class);
181   }
182 
183   /** Selects classes that are declared public */
184   class PublicClassFilter implements ClassFinder.ClassFilter {
185     @Override
186     public boolean isCandidateClass(Class<?> c) {
187       int mod = c.getModifiers();
188       return Modifier.isPublic(mod);
189     }
190   }
191 
192   /** Selects paths (jars and class dirs) only from the main code, not test classes */
193   class MainCodeResourcePathFilter implements ClassFinder.ResourcePathFilter {
194     @Override
195     public boolean isCandidatePath(String resourcePath, boolean isJar) {
196       return !resourcePath.contains("test-classes") &&
197           !resourcePath.contains("tests.jar");
198     }
199   }
200 
201   /**
202    * Selects classes that appear to be source instrumentation from Clover.
203    * Clover generates instrumented code in order to calculate coverage. Part of the
204    * generated source is a static inner class on each source class.
205    *
206    * - has an enclosing class
207    * - enclosing class is not an interface
208    * - name starts with "__CLR"
209    */
210   class CloverInstrumentationFilter implements ClassFinder.ClassFilter {
211     @Override
212     public boolean isCandidateClass(Class<?> clazz) {
213       boolean clover = false;
214       final Class<?> enclosing = clazz.getEnclosingClass();
215       if (enclosing != null) {
216         if (!(enclosing.isInterface())) {
217           clover = clazz.getSimpleName().startsWith("__CLR");
218         }
219       }
220       return clover;
221     }
222   }
223 
224   /**
225    * Checks whether all the classes in client and common modules contain
226    * {@link InterfaceAudience} annotations.
227    */
228   @Test
229   public void testInterfaceAudienceAnnotation()
230       throws ClassNotFoundException, IOException, LinkageError {
231 
232     // find classes that are:
233     // In the main jar
234     // AND are not in a hadoop-compat module
235     // AND are public
236     // NOT test classes
237     // AND NOT generated classes
238     // AND are NOT annotated with InterfaceAudience
239     // AND are NOT from Clover rewriting sources
240     ClassFinder classFinder = new ClassFinder(
241       new And(new MainCodeResourcePathFilter(),
242               new TestFileNameFilter()),
243       new Not((FileNameFilter)new TestFileNameFilter()),
244       new And(new PublicClassFilter(),
245               new Not(new TestClassFilter()),
246               new Not(new GeneratedClassFilter()),
247               new Not(new IsInterfaceStabilityClassFilter()),
248               new Not(new InterfaceAudienceAnnotatedClassFilter()),
249               new Not(new CloverInstrumentationFilter()))
250     );
251 
252     Set<Class<?>> classes = classFinder.findClasses(false);
253 
254     LOG.info("These are the classes that DO NOT have @InterfaceAudience annotation:");
255     for (Class<?> clazz : classes) {
256       LOG.info(clazz);
257     }
258 
259     Assert.assertEquals("All classes should have @InterfaceAudience annotation",
260       0, classes.size());
261   }
262 
263   /**
264    * Checks whether all the classes in client and common modules that are marked
265    * InterfaceAudience.Public also have {@link InterfaceStability} annotations.
266    */
267   @Test
268   public void testInterfaceStabilityAnnotation()
269       throws ClassNotFoundException, IOException, LinkageError {
270 
271     // find classes that are:
272     // In the main jar
273     // AND are not in a hadoop-compat module
274     // AND are public
275     // NOT test classes
276     // AND NOT generated classes
277     // AND are annotated with InterfaceAudience.Public
278     // AND NOT annotated with InterfaceStability
279     ClassFinder classFinder = new ClassFinder(
280       new And(new MainCodeResourcePathFilter(),
281               new TestFileNameFilter()),
282       new Not((FileNameFilter)new TestFileNameFilter()),
283       new And(new PublicClassFilter(),
284               new Not(new TestClassFilter()),
285               new Not(new GeneratedClassFilter()),
286               new InterfaceAudiencePublicAnnotatedClassFilter(),
287               new Not(new IsInterfaceStabilityClassFilter()),
288               new Not(new InterfaceStabilityAnnotatedClassFilter()))
289     );
290 
291     Set<Class<?>> classes = classFinder.findClasses(false);
292 
293     LOG.info("These are the classes that DO NOT have @InterfaceStability annotation:");
294     for (Class<?> clazz : classes) {
295       LOG.info(clazz);
296     }
297 
298     Assert.assertEquals("All classes that are marked with @InterfaceAudience.Public should "
299         + "have @InterfaceStability annotation as well",
300       0, classes.size());
301   }
302 }