001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.camel.impl;
018
019 import java.io.File;
020 import java.io.FileInputStream;
021 import java.io.IOException;
022 import java.io.InputStream;
023 import java.lang.annotation.Annotation;
024 import java.net.URL;
025 import java.net.URLConnection;
026 import java.net.URLDecoder;
027 import java.util.Arrays;
028 import java.util.Collections;
029 import java.util.Enumeration;
030 import java.util.HashSet;
031 import java.util.LinkedHashSet;
032 import java.util.Set;
033 import java.util.jar.JarEntry;
034 import java.util.jar.JarInputStream;
035
036 import org.apache.camel.spi.PackageScanClassResolver;
037 import org.apache.camel.spi.PackageScanFilter;
038 import org.apache.camel.util.ObjectHelper;
039 import org.apache.commons.logging.Log;
040 import org.apache.commons.logging.LogFactory;
041
042 /**
043 * Default implement of {@link org.apache.camel.spi.PackageScanClassResolver}
044 */
045 public class DefaultPackageScanClassResolver implements PackageScanClassResolver {
046
047 protected static final transient Log LOG = LogFactory.getLog(DefaultPackageScanClassResolver.class);
048 private Set<ClassLoader> classLoaders;
049
050 public void addClassLoader(ClassLoader classLoader) {
051 getClassLoaders().add(classLoader);
052 }
053
054 public Set<ClassLoader> getClassLoaders() {
055 if (classLoaders == null) {
056 classLoaders = new HashSet<ClassLoader>();
057 ClassLoader ccl = Thread.currentThread().getContextClassLoader();
058 if (ccl != null) {
059 if (LOG.isTraceEnabled()) {
060 LOG.trace("The thread context class loader: " + ccl + " is used to load the class");
061 }
062 classLoaders.add(ccl);
063 }
064 classLoaders.add(DefaultPackageScanClassResolver.class.getClassLoader());
065 }
066 return classLoaders;
067 }
068
069 public void setClassLoaders(Set<ClassLoader> classLoaders) {
070 this.classLoaders = classLoaders;
071 }
072
073 @SuppressWarnings("unchecked")
074 public Set<Class> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
075 if (packageNames == null) {
076 return Collections.EMPTY_SET;
077 }
078
079 if (LOG.isDebugEnabled()) {
080 LOG.debug("Searching for annotations of " + annotation.getName() + " in packages: " + Arrays.asList(packageNames));
081 }
082
083 PackageScanFilter test = new AnnotatedWithPackageScanFilter(annotation, true);
084 Set<Class> classes = new LinkedHashSet<Class>();
085 for (String pkg : packageNames) {
086 find(test, pkg, classes);
087 }
088
089 if (LOG.isDebugEnabled()) {
090 LOG.debug("Found: " + classes);
091 }
092
093 return classes;
094 }
095
096 @SuppressWarnings("unchecked")
097 public Set<Class> findAnnotated(Set<Class<? extends Annotation>> annotations, String... packageNames) {
098 if (packageNames == null) {
099 return Collections.EMPTY_SET;
100 }
101
102 if (LOG.isDebugEnabled()) {
103 LOG.debug("Searching for annotations of " + annotations + " in packages: " + Arrays.asList(packageNames));
104 }
105
106 PackageScanFilter test = new AnnotatedWithAnyPackageScanFilter(annotations, true);
107 Set<Class> classes = new LinkedHashSet<Class>();
108 for (String pkg : packageNames) {
109 find(test, pkg, classes);
110 }
111
112 if (LOG.isDebugEnabled()) {
113 LOG.debug("Found: " + classes);
114 }
115
116 return classes;
117 }
118
119 @SuppressWarnings("unchecked")
120 public Set<Class> findImplementations(Class parent, String... packageNames) {
121 if (packageNames == null) {
122 return Collections.EMPTY_SET;
123 }
124
125 if (LOG.isDebugEnabled()) {
126 LOG.debug("Searching for implementations of " + parent.getName() + " in packages: " + Arrays.asList(packageNames));
127 }
128
129 PackageScanFilter test = new AssignableToPackageScanFilter(parent);
130 Set<Class> classes = new LinkedHashSet<Class>();
131 for (String pkg : packageNames) {
132 find(test, pkg, classes);
133 }
134
135 if (LOG.isDebugEnabled()) {
136 LOG.debug("Found: " + classes);
137 }
138
139 return classes;
140 }
141
142 @SuppressWarnings("unchecked")
143 public Set<Class> findByFilter(PackageScanFilter filter, String... packageNames) {
144 if (packageNames == null) {
145 return Collections.EMPTY_SET;
146 }
147
148 Set<Class> classes = new LinkedHashSet<Class>();
149 for (String pkg : packageNames) {
150 find(filter, pkg, classes);
151 }
152
153 if (LOG.isDebugEnabled()) {
154 LOG.debug("Found: " + classes);
155 }
156
157 return classes;
158 }
159
160
161 protected void find(PackageScanFilter test, String packageName, Set<Class> classes) {
162 packageName = packageName.replace('.', '/');
163
164 Set<ClassLoader> set = getClassLoaders();
165
166 for (ClassLoader classLoader : set) {
167 find(test, packageName, classLoader, classes);
168 }
169 }
170
171 protected void find(PackageScanFilter test, String packageName, ClassLoader loader, Set<Class> classes) {
172 if (LOG.isTraceEnabled()) {
173 LOG.trace("Searching for: " + test + " in package: " + packageName + " using classloader: "
174 + loader.getClass().getName());
175 }
176
177 Enumeration<URL> urls;
178 try {
179 urls = getResources(loader, packageName);
180 if (!urls.hasMoreElements()) {
181 LOG.trace("No URLs returned by classloader");
182 }
183 } catch (IOException ioe) {
184 LOG.warn("Could not read package: " + packageName, ioe);
185 return;
186 }
187
188 while (urls.hasMoreElements()) {
189 URL url = null;
190 try {
191 url = urls.nextElement();
192 if (LOG.isTraceEnabled()) {
193 LOG.trace("URL from classloader: " + url);
194 }
195
196 String urlPath = url.getFile();
197 urlPath = URLDecoder.decode(urlPath, "UTF-8");
198 if (LOG.isTraceEnabled()) {
199 LOG.trace("Decoded urlPath: " + urlPath);
200 }
201
202 // If it's a file in a directory, trim the stupid file: spec
203 if (urlPath.startsWith("file:")) {
204 urlPath = urlPath.substring(5);
205 }
206
207 // osgi bundles should be skipped
208 if (url.toString().startsWith("bundle:") || urlPath.startsWith("bundle:")) {
209 LOG.trace("It's a virtual osgi bundle, skipping");
210 continue;
211 }
212
213 // Else it's in a JAR, grab the path to the jar
214 if (urlPath.indexOf('!') > 0) {
215 urlPath = urlPath.substring(0, urlPath.indexOf('!'));
216 }
217
218 if (LOG.isTraceEnabled()) {
219 LOG.trace("Scanning for classes in [" + urlPath + "] matching criteria: " + test);
220 }
221
222 File file = new File(urlPath);
223 if (file.isDirectory()) {
224 if (LOG.isDebugEnabled()) {
225 LOG.debug("Loading from directory: " + file);
226 }
227 loadImplementationsInDirectory(test, packageName, file, classes);
228 } else {
229 InputStream stream;
230 if (urlPath.startsWith("http:")) {
231 // load resources using http such as java webstart
232 LOG.debug("The current jar is accessed via http");
233 URL urlStream = new URL(urlPath);
234 URLConnection con = urlStream.openConnection();
235 // disable cache mainly to avoid jar file locking on Windows
236 con.setUseCaches(false);
237 stream = con.getInputStream();
238 } else {
239 stream = new FileInputStream(file);
240 }
241
242 if (LOG.isDebugEnabled()) {
243 LOG.debug("Loading from jar: " + file);
244 }
245 loadImplementationsInJar(test, packageName, stream, urlPath, classes);
246 }
247 } catch (IOException ioe) {
248 LOG.warn("Could not read entries in url: " + url, ioe);
249 }
250 }
251 }
252
253 /**
254 * Strategy to get the resources by the given classloader.
255 * <p/>
256 * Notice that in WebSphere platforms there is a {@link WebSpherePacakageScanClassResolver}
257 * to take care of WebSphere's odditiy of resource loading.
258 *
259 * @param loader the classloader
260 * @param packageName the packagename for the package to load
261 * @return URL's for the given package
262 * @throws IOException is thrown by the classloader
263 */
264 protected Enumeration<URL> getResources(ClassLoader loader, String packageName) throws IOException {
265 if (LOG.isTraceEnabled()) {
266 LOG.trace("Getting resource URL for package: " + packageName + " with classloader: " + loader);
267 }
268 return loader.getResources(packageName);
269 }
270
271
272
273
274 /**
275 * Finds matches in a physical directory on a filesystem. Examines all files
276 * within a directory - if the File object is not a directory, and ends with
277 * <i>.class</i> the file is loaded and tested to see if it is acceptable
278 * according to the Test. Operates recursively to find classes within a
279 * folder structure matching the package structure.
280 *
281 * @param test a Test used to filter the classes that are discovered
282 * @param parent the package name up to this directory in the package
283 * hierarchy. E.g. if /classes is in the classpath and we wish to
284 * examine files in /classes/org/apache then the values of
285 * <i>parent</i> would be <i>org/apache</i>
286 * @param location a File object representing a directory
287 */
288 private void loadImplementationsInDirectory(PackageScanFilter test, String parent, File location, Set<Class> classes) {
289 File[] files = location.listFiles();
290 StringBuilder builder = null;
291
292 for (File file : files) {
293 builder = new StringBuilder(100);
294 String name = file.getName();
295 if (name != null) {
296 name = name.trim();
297 builder.append(parent).append("/").append(name);
298 String packageOrClass = parent == null ? name : builder.toString();
299
300 if (file.isDirectory()) {
301 loadImplementationsInDirectory(test, packageOrClass, file, classes);
302 } else if (name.endsWith(".class")) {
303 addIfMatching(test, packageOrClass, classes);
304 }
305 }
306 }
307 }
308
309 /**
310 * Finds matching classes within a jar files that contains a folder
311 * structure matching the package structure. If the File is not a JarFile or
312 * does not exist a warning will be logged, but no error will be raised.
313 *
314 * @param test a Test used to filter the classes that are discovered
315 * @param parent the parent package under which classes must be in order to
316 * be considered
317 * @param stream the inputstream of the jar file to be examined for classes
318 * @param urlPath the url of the jar file to be examined for classes
319 */
320 private void loadImplementationsInJar(PackageScanFilter test, String parent, InputStream stream, String urlPath, Set<Class> classes) {
321 JarInputStream jarStream = null;
322 try {
323 jarStream = new JarInputStream(stream);
324
325 JarEntry entry;
326 while ((entry = jarStream.getNextJarEntry()) != null) {
327 String name = entry.getName();
328 if (name != null) {
329 name = name.trim();
330 if (!entry.isDirectory() && name.startsWith(parent) && name.endsWith(".class")) {
331 addIfMatching(test, name, classes);
332 }
333 }
334 }
335 } catch (IOException ioe) {
336 LOG.error("Could not search jar file '" + urlPath + "' for classes matching criteria: " + test
337 + " due to an IOException: " + ioe.getMessage(), ioe);
338 } finally {
339 ObjectHelper.close(jarStream, urlPath, LOG);
340 }
341 }
342
343 /**
344 * Add the class designated by the fully qualified class name provided to
345 * the set of resolved classes if and only if it is approved by the Test
346 * supplied.
347 *
348 * @param test the test used to determine if the class matches
349 * @param fqn the fully qualified name of a class
350 */
351 @SuppressWarnings("unchecked")
352 protected void addIfMatching(PackageScanFilter test, String fqn, Set<Class> classes) {
353 try {
354 String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
355 Set<ClassLoader> set = getClassLoaders();
356 boolean found = false;
357 for (ClassLoader classLoader : set) {
358 if (LOG.isTraceEnabled()) {
359 LOG.trace("Testing for class " + externalName + " matches criteria [" + test + "] using ClassLoader:" + classLoader);
360 }
361 try {
362 Class type = classLoader.loadClass(externalName);
363 if (LOG.isTraceEnabled()) {
364 LOG.trace("Loaded the class: " + type + " in classloader: " + classLoader);
365 }
366 if (test.matches(type)) {
367 if (LOG.isTraceEnabled()) {
368 LOG.trace("Found class: " + type + " which matches the filter in classloader: " + classLoader);
369 }
370 classes.add(type);
371 }
372 found = true;
373 break;
374 } catch (ClassNotFoundException e) {
375 LOG.debug("Could not find class '" + fqn + "' in classloader: " + classLoader
376 + ". Reason: " + e, e);
377 } catch (NoClassDefFoundError e) {
378 LOG.debug("Could not find the class defintion '" + fqn + "' in classloader: " + classLoader
379 + ". Reason: " + e, e);
380 }
381 }
382 if (!found) {
383 LOG.warn("Could not find class '" + fqn + "' in any classloaders: " + set);
384 }
385 } catch (Exception e) {
386 LOG.warn("Could not examine class '" + fqn + "' due to a " + e.getClass().getName()
387 + " with message: " + e.getMessage(), e);
388 }
389 }
390
391 }