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 public
235     // NOT test classes
236     // AND NOT generated classes
237     // AND are NOT annotated with InterfaceAudience
238     // AND are NOT from Clover rewriting sources
239     ClassFinder classFinder = new ClassFinder(
240       new MainCodeResourcePathFilter(),
241       new Not((FileNameFilter)new TestFileNameFilter()),
242       new And(new PublicClassFilter(),
243               new Not(new TestClassFilter()),
244               new Not(new GeneratedClassFilter()),
245               new Not(new IsInterfaceStabilityClassFilter()),
246               new Not(new InterfaceAudienceAnnotatedClassFilter()),
247               new Not(new CloverInstrumentationFilter()))
248     );
249 
250     Set<Class<?>> classes = classFinder.findClasses(false);
251 
252     LOG.info("These are the classes that DO NOT have @InterfaceAudience annotation:");
253     for (Class<?> clazz : classes) {
254       LOG.info(clazz);
255     }
256 
257     Assert.assertEquals("All classes should have @InterfaceAudience annotation",
258       0, classes.size());
259   }
260 
261   /**
262    * Checks whether all the classes in client and common modules that are marked
263    * InterfaceAudience.Public also have {@link InterfaceStability} annotations.
264    */
265   @Test
266   public void testInterfaceStabilityAnnotation()
267       throws ClassNotFoundException, IOException, LinkageError {
268 
269     // find classes that are:
270     // In the main jar
271     // AND are public
272     // NOT test classes
273     // AND NOT generated classes
274     // AND are annotated with InterfaceAudience.Public
275     // AND NOT annotated with InterfaceStability
276     ClassFinder classFinder = new ClassFinder(
277       new MainCodeResourcePathFilter(),
278       new Not((FileNameFilter)new TestFileNameFilter()),
279       new And(new PublicClassFilter(),
280               new Not(new TestClassFilter()),
281               new Not(new GeneratedClassFilter()),
282               new InterfaceAudiencePublicAnnotatedClassFilter(),
283               new Not(new IsInterfaceStabilityClassFilter()),
284               new Not(new InterfaceStabilityAnnotatedClassFilter()))
285     );
286 
287     Set<Class<?>> classes = classFinder.findClasses(false);
288 
289     LOG.info("These are the classes that DO NOT have @InterfaceStability annotation:");
290     for (Class<?> clazz : classes) {
291       LOG.info(clazz);
292     }
293 
294     Assert.assertEquals("All classes that are marked with @InterfaceAudience.Public should "
295         + "have @InterfaceStability annotation as well",
296       0, classes.size());
297   }
298 }