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();
42
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 }