View Javadoc
1   package com.atlassian.plugin.spring.scanner.core;
2   
3   import com.atlassian.plugin.spring.scanner.annotation.Profile;
4   import javassist.bytecode.ClassFile;
5   import org.slf4j.Logger;
6   
7   import java.io.BufferedInputStream;
8   import java.io.DataInputStream;
9   import java.io.File;
10  import java.io.FileInputStream;
11  import java.io.IOException;
12  import java.io.InputStream;
13  import java.net.URISyntaxException;
14  import java.net.URL;
15  import java.util.Collections;
16  import java.util.List;
17  import java.util.Map;
18  import java.util.Set;
19  import java.util.TreeMap;
20  import java.util.TreeSet;
21  
22  /**
23   * This little guy can help find the package-info.java of a given class file and cache what profiles are in play and
24   * also what profiles are in a given class file.
25   */
26  class ProfileFinder {
27      private static final String PACKAGE_INFO = "package-info";
28      private final Set<URL> buildPath;
29      private final Logger log;
30      private final JavassistHelper javassistHelper;
31      private final Map<String, Set<String>> mapOfPackagesToProfiles = new TreeMap<String, Set<String>>();
32  
33      public ProfileFinder(final Set<URL> buildPath, final Logger log) {
34          this.buildPath = buildPath;
35          this.log = log;
36          this.javassistHelper = new JavassistHelper();
37      }
38  
39      boolean isPackageClass(ClassFile classFile) {
40          return classFile.getName().endsWith("." + PACKAGE_INFO);
41      }
42  
43      /**
44       * Called to get the Profiles that apply to this class file
45       *
46       * @param classFile the class file in question
47       * @return a set of applicable profiles
48       */
49      Set<String> getProfiles(ClassFile classFile) {
50          String classFileName = classFile.getName();
51          if (log.isDebugEnabled()) {
52              log.debug(String.format("Checking for profile annotations on %s", classFileName));
53          }
54  
55          if (isPackageClass(classFile)) {
56              recordPackageClassProfileAnnotations(classFile);
57              return Collections.emptySet();
58          } else {
59              Set<String> packageInfoProfileAnnotations = getPackageInfoProfileAnnotations(classFile);
60              Set<String> classSpecificProfiles = findProfiles(classFile);
61              //
62              // if we have specific @Profile annotations on the class then they override the package level ones
63              if (!classSpecificProfiles.isEmpty()) {
64                  return classSpecificProfiles;
65              }
66              return packageInfoProfileAnnotations;
67          }
68      }
69  
70      /**
71       * Records in the cache just the package info class file profiles
72       *
73       * @param packageInfoClassFile should only be a package-info.class
74       */
75      private void recordPackageClassProfileAnnotations(final ClassFile packageInfoClassFile) {
76          String packageName = getClassPackageAsFileName(packageInfoClassFile);
77          Set<String> profiles = mapOfPackagesToProfiles.get(packageName);
78          //
79          // it possible that we have seen a package class member BEFORE we have seen the package-info class
80          // and hence the other paths will have recorded the package-info profile information already
81          //
82          if (profiles == null) {
83              mapOfPackagesToProfiles.put(packageName, findProfiles(packageInfoClassFile));
84          }
85      }
86  
87      private Set<String> getPackageInfoProfileAnnotations(final ClassFile classFile) {
88          //
89          // we need to look to see if we have seen this package before
90          String packageName = getClassPackageAsFileName(classFile);
91          Set<String> profiles = mapOfPackagesToProfiles.get(packageName);
92          if (profiles != null) {
93              return profiles;
94          }
95          //
96          // ok we haven't see the package-info class yet.  So we do a preemptive lookup
97  
98          ClassFile packageInfoClassFile = loadPackageInfoClass(packageName);
99          if (packageInfoClassFile != null) {
100             recordPackageClassProfileAnnotations(packageInfoClassFile);
101         } else {
102             //
103             // ok we have no package-info class so record that for posterity
104             mapOfPackagesToProfiles.put(packageName, Collections.<String>emptySet());
105         }
106         //
107         // now lookup again cause it is now recorded
108         return mapOfPackagesToProfiles.get(packageName);
109     }
110 
111     private ClassFile loadPackageInfoClass(final String packageName) {
112         for (URL url : buildPath) {
113             try {
114                 File parentFile = new File(url.toURI());
115                 File packageInfoFile = new File(parentFile, packageName + "/" + "package-info.class");
116                 if (!packageInfoFile.exists()) {
117                     continue;
118                 }
119 
120                 try (InputStream inputStream = new FileInputStream(packageInfoFile)) {
121                     DataInputStream dis = new DataInputStream(new BufferedInputStream(inputStream));
122                     return new ClassFile(dis);
123                 } catch (IOException e) {
124                     log.error(String.format("Could not create class file from %s", packageInfoFile), e);
125                 }
126             } catch (URISyntaxException e) {
127                 log.error(String.format("Unable to load package-info file %s from %s", packageName, url));
128             }
129         }
130         return null;
131     }
132 
133     private Set<String> findProfiles(final ClassFile classFile) {
134         List<String> classAnnotationNames = javassistHelper.getClassAnnotationNames(classFile);
135         Set<String> profiles = new TreeSet<String>();
136         for (String annotationType : classAnnotationNames) {
137             if (Profile.class.getCanonicalName().equals(annotationType)) {
138                 Set<String> profileValues = javassistHelper.getAnnotationMemberSet(classFile, annotationType, "value");
139                 profiles.addAll(profileValues);
140             }
141             //
142             // we have support for Spring 3.1 profile in a preemptive attempt that MAYBE one day we might get there :)
143             if ("org.springframework.context.annotation.Profile".equals(annotationType)) {
144                 Set<String> profileValues = javassistHelper.getAnnotationMemberSet(classFile, annotationType, "value");
145                 profiles.addAll(profileValues);
146             }
147         }
148         return profiles;
149     }
150 
151     private String getClassPackageAsFileName(final ClassFile classFile) {
152         String fileName = classFile.getName();
153         int lastDot = fileName.lastIndexOf('.');
154         if (lastDot == -1) {
155             return fileName;
156         }
157         String packageName = fileName.substring(0, lastDot);
158         return packageName.replace('.', '/');
159     }
160 }