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    * Checks whether all the classes in client and common modules contain
203    * {@link InterfaceAudience} annotations.
204    */
205   @Test
206   public void testInterfaceAudienceAnnotation()
207       throws ClassNotFoundException, IOException, LinkageError {
208 
209     // find classes that are:
210     // In the main jar
211     // AND are public
212     // NOT test classes
213     // AND NOT generated classes
214     // AND are NOT annotated with InterfaceAudience
215     ClassFinder classFinder = new ClassFinder(
216       new MainCodeResourcePathFilter(),
217       new Not((FileNameFilter)new TestFileNameFilter()),
218       new And(new PublicClassFilter(),
219               new Not(new TestClassFilter()),
220               new Not(new GeneratedClassFilter()),
221               new Not(new IsInterfaceStabilityClassFilter()),
222               new Not(new InterfaceAudienceAnnotatedClassFilter()))
223     );
224 
225     Set<Class<?>> classes = classFinder.findClasses(false);
226 
227     LOG.info("These are the classes that DO NOT have @InterfaceAudience annotation:");
228     for (Class<?> clazz : classes) {
229       LOG.info(clazz);
230     }
231 
232     Assert.assertEquals("All classes should have @InterfaceAudience annotation",
233       0, classes.size());
234   }
235 
236   /**
237    * Checks whether all the classes in client and common modules that are marked
238    * InterfaceAudience.Public also have {@link InterfaceStability} annotations.
239    */
240   @Test
241   public void testInterfaceStabilityAnnotation()
242       throws ClassNotFoundException, IOException, LinkageError {
243 
244     // find classes that are:
245     // In the main jar
246     // AND are public
247     // NOT test classes
248     // AND NOT generated classes
249     // AND are annotated with InterfaceAudience.Public
250     // AND NOT annotated with InterfaceStability
251     ClassFinder classFinder = new ClassFinder(
252       new MainCodeResourcePathFilter(),
253       new Not((FileNameFilter)new TestFileNameFilter()),
254       new And(new PublicClassFilter(),
255               new Not(new TestClassFilter()),
256               new Not(new GeneratedClassFilter()),
257               new InterfaceAudiencePublicAnnotatedClassFilter(),
258               new Not(new IsInterfaceStabilityClassFilter()),
259               new Not(new InterfaceStabilityAnnotatedClassFilter()))
260     );
261 
262     Set<Class<?>> classes = classFinder.findClasses(false);
263 
264     LOG.info("These are the classes that DO NOT have @InterfaceStability annotation:");
265     for (Class<?> clazz : classes) {
266       LOG.info(clazz);
267     }
268 
269     Assert.assertEquals("All classes that are marked with @InterfaceAudience.Public should "
270         + "have @InterfaceStability annotation as well",
271       0, classes.size());
272   }
273 }