View Javadoc

1   package liquibase.servicelocator;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.lang.reflect.Modifier;
6   import java.net.URL;
7   import java.util.ArrayList;
8   import java.util.Arrays;
9   import java.util.Enumeration;
10  import java.util.HashMap;
11  import java.util.HashSet;
12  import java.util.List;
13  import java.util.Map;
14  import java.util.jar.Manifest;
15  
16  import liquibase.exception.ServiceNotFoundException;
17  import liquibase.exception.UnexpectedLiquibaseException;
18  import liquibase.logging.Logger;
19  import liquibase.logging.core.DefaultLogger;
20  import liquibase.resource.ClassLoaderResourceAccessor;
21  import liquibase.resource.ResourceAccessor;
22  import liquibase.util.StringUtils;
23  
24  public class ServiceLocator {
25  
26      private static ServiceLocator instance;
27  
28      static {
29          try {
30              Class<?> scanner = Class.forName("Liquibase.ServiceLocator.ClrServiceLocator, Liquibase");
31              instance = (ServiceLocator) scanner.newInstance();
32          } catch (Exception e) {
33              instance = new ServiceLocator();
34          }
35      }
36  
37      private ResourceAccessor resourceAccessor;
38  
39      private Map<Class, List<Class>> classesBySuperclass;
40      private List<String> packagesToScan;
41      private Logger logger = new DefaultLogger(); // cannot look up regular logger because you get a stackoverflow since
42      // we are in the servicelocator
43      private PackageScanClassResolver classResolver;
44  
45      private ServiceLocator() {
46          setResourceAccessor(new ClassLoaderResourceAccessor());
47      }
48  
49      private ServiceLocator(ResourceAccessor accessor) {
50          setResourceAccessor(accessor);
51      }
52  
53      public static ServiceLocator getInstance() {
54          return instance;
55      }
56  
57      public void setResourceAccessor(ResourceAccessor resourceAccessor) {
58          this.resourceAccessor = resourceAccessor;
59          this.classesBySuperclass = new HashMap<Class, List<Class>>();
60  
61          if (WebSpherePackageScanClassResolver.isWebSphereClassLoader(this.getClass().getClassLoader())) {
62              logger.debug("Using WebSphere Specific Class Resolver");
63              this.classResolver = new WebSpherePackageScanClassResolver("liquibase/parser/core/xml/dbchangelog-2.0.xsd");
64          } else {
65              this.classResolver = new DefaultPackageScanClassResolver();
66          }
67          this.classResolver.setClassLoaders(new HashSet<ClassLoader>(Arrays.asList(new ClassLoader[] { resourceAccessor
68                  .toClassLoader() })));
69  
70          packagesToScan = new ArrayList<String>();
71          String packagesToScanSystemProp = System.getProperty("liquibase.scan.packages");
72          if ((packagesToScanSystemProp != null)
73                  && ((packagesToScanSystemProp = StringUtils.trimToNull(packagesToScanSystemProp)) != null)) {
74              for (String value : packagesToScanSystemProp.split(",")) {
75                  addPackageToScan(value);
76              }
77          } else {
78              Enumeration<URL> manifests = null;
79              try {
80                  manifests = resourceAccessor.getResources("META-INF/MANIFEST.MF");
81                  while (manifests.hasMoreElements()) {
82                      URL url = manifests.nextElement();
83                      InputStream is = url.openStream();
84                      Manifest manifest = new Manifest(is);
85                      String attributes = StringUtils.trimToNull(manifest.getMainAttributes().getValue(
86                              "Liquibase-Package"));
87                      if (attributes != null) {
88                          for (Object value : attributes.split(",")) {
89                              addPackageToScan(value.toString());
90                          }
91                      }
92                      is.close();
93                  }
94              } catch (IOException e) {
95                  throw new UnexpectedLiquibaseException(e);
96              }
97  
98              if (packagesToScan.size() == 0) {
99                  addPackageToScan("liquibase.change");
100                 addPackageToScan("liquibase.database");
101                 addPackageToScan("liquibase.parser");
102                 addPackageToScan("liquibase.precondition");
103                 addPackageToScan("liquibase.serializer");
104                 addPackageToScan("liquibase.sqlgenerator");
105                 addPackageToScan("liquibase.executor");
106                 addPackageToScan("liquibase.snapshot");
107                 addPackageToScan("liquibase.logging");
108                 addPackageToScan("liquibase.ext");
109             }
110         }
111     }
112 
113     public void addPackageToScan(String packageName) {
114         packagesToScan.add(packageName);
115     }
116 
117     public Class findClass(Class requiredInterface) throws ServiceNotFoundException {
118         Class[] classes = findClasses(requiredInterface);
119         if (PrioritizedService.class.isAssignableFrom(requiredInterface)) {
120             PrioritizedService returnObject = null;
121             for (Class clazz : classes) {
122                 PrioritizedService newInstance;
123                 try {
124                     newInstance = (PrioritizedService) clazz.newInstance();
125                 } catch (Exception e) {
126                     throw new UnexpectedLiquibaseException(e);
127                 }
128 
129                 if (returnObject == null || newInstance.getPriority() > returnObject.getPriority()) {
130                     returnObject = newInstance;
131                 }
132             }
133 
134             if (returnObject == null) {
135                 throw new ServiceNotFoundException("Could not find implementation of " + requiredInterface.getName());
136             }
137             return returnObject.getClass();
138         }
139 
140         if (classes.length != 1) {
141             throw new ServiceNotFoundException("Could not find unique implementation of " + requiredInterface.getName()
142                     + ".  Found " + classes.length + " implementations");
143         }
144 
145         return classes[0];
146     }
147 
148     public Class[] findClasses(Class requiredInterface) throws ServiceNotFoundException {
149         logger.debug("ServiceLocator.findClasses for " + requiredInterface.getName());
150 
151         try {
152             Class.forName(requiredInterface.getName());
153 
154             if (!classesBySuperclass.containsKey(requiredInterface)) {
155                 classesBySuperclass.put(requiredInterface, findClassesImpl(requiredInterface));
156             }
157         } catch (Exception e) {
158             throw new ServiceNotFoundException(e);
159         }
160 
161         List<Class> classes = classesBySuperclass.get(requiredInterface);
162         HashSet<Class> uniqueClasses = new HashSet<Class>(classes);
163         return uniqueClasses.toArray(new Class[uniqueClasses.size()]);
164     }
165 
166     public Object newInstance(Class requiredInterface) throws ServiceNotFoundException {
167         try {
168             return findClass(requiredInterface).newInstance();
169         } catch (Exception e) {
170             throw new ServiceNotFoundException(e);
171         }
172     }
173 
174     private List<Class> findClassesImpl(Class requiredInterface) throws Exception {
175         logger.debug("ServiceLocator finding classes matching interface " + requiredInterface.getName());
176 
177         List<Class> classes = new ArrayList<Class>();
178 
179         classResolver.addClassLoader(resourceAccessor.toClassLoader());
180         for (Class<?> clazz : classResolver.findImplementations(requiredInterface,
181                 packagesToScan.toArray(new String[packagesToScan.size()]))) {
182             if (clazz.getAnnotation(LiquibaseService.class) != null
183                     && clazz.getAnnotation(LiquibaseService.class).skip()) {
184                 continue;
185             }
186 
187             if (!Modifier.isAbstract(clazz.getModifiers()) && !Modifier.isInterface(clazz.getModifiers())
188                     && Modifier.isPublic(clazz.getModifiers())) {
189                 try {
190                     clazz.getConstructor();
191                     logger.debug(clazz.getName() + " matches " + requiredInterface.getName());
192 
193                     classes.add(clazz);
194                 } catch (NoSuchMethodException e) {
195                     logger.info("Can not use " + clazz
196                             + " as a Liquibase service because it does not have a no-argument constructor");
197                 }
198             }
199         }
200 
201         return classes;
202     }
203 
204     public static void reset() {
205         instance = new ServiceLocator();
206     }
207 
208     protected Logger getLogger() {
209         return logger;
210     }
211 }