All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.camel.impl.engine.DefaultPackageScanClassResolver Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.impl.engine;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.camel.NonManagedService;
import org.apache.camel.impl.scan.AnnotatedWithAnyPackageScanFilter;
import org.apache.camel.impl.scan.AnnotatedWithPackageScanFilter;
import org.apache.camel.impl.scan.AssignableToPackageScanFilter;
import org.apache.camel.impl.scan.CompositePackageScanFilter;
import org.apache.camel.spi.PackageScanClassResolver;
import org.apache.camel.spi.PackageScanFilter;
import org.apache.camel.support.LRUCacheFactory;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Default implement of {@link org.apache.camel.spi.PackageScanClassResolver}
 */
public class DefaultPackageScanClassResolver extends BasePackageScanResolver
        implements PackageScanClassResolver, NonManagedService {
    private static final Logger LOG = LoggerFactory.getLogger(DefaultPackageScanClassResolver.class);

    private volatile Map> jarCache;
    private Set scanFilters;

    @Override
    public void addFilter(PackageScanFilter filter) {
        if (scanFilters == null) {
            scanFilters = new LinkedHashSet<>();
        }
        scanFilters.add(filter);
    }

    @Override
    public void removeFilter(PackageScanFilter filter) {
        if (scanFilters != null) {
            scanFilters.remove(filter);
        }
    }

    @Override
    public Set> findAnnotated(Class annotation, String... packageNames) {
        if (packageNames == null) {
            return Collections.emptySet();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Searching for annotations of {} in packages: {}", annotation.getName(), Arrays.asList(packageNames));
        }

        PackageScanFilter test = getCompositeFilter(new AnnotatedWithPackageScanFilter(annotation, true));
        Set> classes = new LinkedHashSet<>();
        for (String pkg : packageNames) {
            find(test, pkg, classes);
        }

        LOG.debug("Found: {}", classes);

        return classes;
    }

    @Override
    public Set> findAnnotated(Set> annotations, String... packageNames) {
        if (packageNames == null) {
            return Collections.emptySet();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Searching for annotations of {} in packages: {}", annotations, Arrays.asList(packageNames));
        }

        PackageScanFilter test = getCompositeFilter(new AnnotatedWithAnyPackageScanFilter(annotations, true));
        Set> classes = new LinkedHashSet<>();
        for (String pkg : packageNames) {
            find(test, pkg, classes);
        }

        LOG.debug("Found: {}", classes);

        return classes;
    }

    @Override
    public Set> findImplementations(Class parent, String... packageNames) {
        if (packageNames == null) {
            return Collections.emptySet();
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Searching for implementations of {} in packages: {}", parent.getName(), Arrays.asList(packageNames));
        }

        PackageScanFilter test = getCompositeFilter(new AssignableToPackageScanFilter(parent));
        Set> classes = new LinkedHashSet<>();
        for (String pkg : packageNames) {
            find(test, pkg, classes);
        }

        LOG.debug("Found: {}", classes);

        return classes;
    }

    @Override
    public Set> findByFilter(PackageScanFilter filter, String... packageNames) {
        if (packageNames == null) {
            return Collections.emptySet();
        }

        Set> classes = new LinkedHashSet<>();
        for (String pkg : packageNames) {
            find(filter, pkg, classes);
        }

        LOG.debug("Found: {}", classes);

        return classes;
    }

    protected void find(PackageScanFilter test, String packageName, Set> classes) {
        // special for root package
        if (".".equals(packageName)) {
            packageName = "";
        } else {
            packageName = packageName.replace('.', '/');
        }

        Set set = getClassLoaders();

        for (ClassLoader classLoader : set) {
            find(test, packageName, classLoader, classes);
        }
    }

    protected void find(PackageScanFilter test, String packageName, ClassLoader loader, Set> classes) {
        if (LOG.isTraceEnabled()) {
            LOG.trace("Searching for: {} in package: {} using classloader: {}",
                    test, packageName, loader.getClass().getName());
        }

        Enumeration urls = getUrls(packageName, loader);
        if (urls == null) {
            return;
        }

        while (urls.hasMoreElements()) {
            URL url = null;
            try {
                url = urls.nextElement();
                LOG.trace("URL from classloader: {}", url);

                url = customResourceLocator(url);

                String urlPath = parseUrlPath(url);
                if (urlPath == null) {
                    continue;
                }

                LOG.trace("Scanning for classes in: {} matching criteria: {}", urlPath, test);

                File file = new File(urlPath);
                if (file.isDirectory()) {
                    LOG.trace("Loading from directory using file: {}", file);
                    loadImplementationsInDirectory(test, packageName, file, classes);
                } else {
                    InputStream stream = null;
                    try {
                        if (urlPath.startsWith("http:") || urlPath.startsWith("https:")
                                || urlPath.startsWith("sonicfs:")
                                || isAcceptableScheme(urlPath)) {
                            // load resources using http/https, sonicfs and other acceptable scheme
                            // sonic ESB requires to be loaded using a regular URLConnection
                            LOG.trace("Loading from jar using url: {}", urlPath);
                            URL urlStream = new URL(urlPath);
                            URLConnection con = urlStream.openConnection();
                            // disable cache mainly to avoid jar file locking on Windows
                            con.setUseCaches(false);
                            stream = con.getInputStream();
                        } else {
                            LOG.trace("Loading from jar using file: {}", file);
                            stream = new FileInputStream(file);
                        }

                        // only create jar cache on-demand when needed
                        if (jarCache == null) {
                            // use a soft cache so it can be claimed if needed
                            jarCache = LRUCacheFactory.newLRUSoftCache(1000);
                        }

                        loadImplementationsInJar(test, packageName, stream, urlPath, classes, jarCache);
                    } finally {
                        IOHelper.close(stream);
                    }
                }
            } catch (IOException e) {
                // use debug logging to avoid being to noisy in logs
                LOG.debug("Cannot read entries in url: {}", url, e);
            }
        }
    }

    private PackageScanFilter getCompositeFilter(PackageScanFilter filter) {
        if (scanFilters != null) {
            CompositePackageScanFilter composite = new CompositePackageScanFilter(scanFilters);
            composite.addFilter(filter);
            return composite;
        }
        return filter;
    }

    /**
     * Finds matches in a physical directory on a filesystem. Examines all files within a directory - if the File object
     * is not a directory, and ends with .class the file is loaded and tested to see if it is acceptable
     * according to the Test. Operates recursively to find classes within a folder structure matching the package
     * structure.
     *
     * @param test     a Test used to filter the classes that are discovered
     * @param parent   the package name up to this directory in the package hierarchy. E.g. if /classes is in the
     *                 classpath and we wish to examine files in /classes/org/apache then the values of parent
     *                 would be org/apache
     * @param location a File object representing a directory
     */
    private void loadImplementationsInDirectory(PackageScanFilter test, String parent, File location, Set> classes) {
        File[] files = location.listFiles();
        StringBuilder builder;

        // this will prevent NullPointerException, but there are no means to tell caller class that the location folder is empty
        if (files == null) {
            return;
        }

        for (File file : files) {
            builder = new StringBuilder(100);
            final String name = file.getName().trim();
            builder.append(parent).append("/").append(name);
            String packageOrClass = parent == null ? name : builder.toString();

            if (file.isDirectory()) {
                loadImplementationsInDirectory(test, packageOrClass, file, classes);
            } else if (name.endsWith(".class")) {
                addIfMatching(test, packageOrClass, classes);
            }
        }
    }

    /**
     * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
     * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
     *
     * @param test     a Test used to filter the classes that are discovered
     * @param parent   the parent package under which classes must be in order to be considered
     * @param stream   the inputstream of the jar file to be examined for classes
     * @param urlPath  the url of the jar file to be examined for classes
     * @param classes  to add found and matching classes
     * @param jarCache cache for JARs to speedup loading
     */
    private void loadImplementationsInJar(
            PackageScanFilter test, String parent, InputStream stream,
            String urlPath, Set> classes, Map> jarCache) {
        ObjectHelper.notNull(classes, "classes");

        List entries = jarCache != null ? jarCache.get(urlPath) : null;
        if (entries == null) {
            entries = doLoadJarClassEntries(stream, urlPath);
            if (jarCache != null) {
                jarCache.put(urlPath, entries);
                LOG.trace("Cached {} JAR with {} entries", urlPath, entries.size());
            }
        } else {
            LOG.trace("Using cached {} JAR with {} entries", urlPath, entries.size());
        }

        doLoadImplementationsInJar(test, parent, entries, classes);
    }

    /**
     * Loads all the class entries from the JAR.
     *
     * @param  stream  the inputstream of the jar file to be examined for classes
     * @param  urlPath the url of the jar file to be examined for classes
     * @return         all the .class entries from the JAR
     */
    protected List doLoadJarClassEntries(InputStream stream, String urlPath) {
        List entries = new ArrayList<>();

        JarInputStream jarStream = null;
        try {
            jarStream = new JarInputStream(stream);

            JarEntry entry;
            while ((entry = jarStream.getNextJarEntry()) != null) {
                final String name = entry.getName().trim();
                if (!entry.isDirectory() && name.endsWith(".class")) {
                    entries.add(name);
                }
            }
        } catch (IOException ioe) {
            LOG.warn("Cannot search jar file '{}' due to an IOException: {}", urlPath, ioe.getMessage(), ioe);
        } finally {
            IOHelper.close(jarStream, urlPath, LOG);
        }

        return entries;
    }

    /**
     * Adds all the matching implementations from from the JAR entries to the classes.
     *
     * @param test    a Test used to filter the classes that are discovered
     * @param parent  the parent package under which classes must be in order to be considered
     * @param entries the .class entries from the JAR
     * @param classes to add found and matching classes
     */
    private void doLoadImplementationsInJar(
            PackageScanFilter test, String parent, List entries, Set> classes) {
        for (String entry : entries) {
            if (entry.startsWith(parent)) {
                addIfMatching(test, entry, classes);
            }
        }
    }

    /**
     * Add the class designated by the fully qualified class name provided to the set of resolved classes if and only if
     * it is approved by the Test supplied.
     *
     * @param test the test used to determine if the class matches
     * @param fqn  the fully qualified name of a class
     */
    protected void addIfMatching(PackageScanFilter test, String fqn, Set> classes) {
        try {
            String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
            Set set = getClassLoaders();
            boolean found = false;
            for (ClassLoader classLoader : set) {
                if (LOG.isTraceEnabled()) {
                    LOG.trace("Testing for class {} matches criteria [{}] using classloader: {}", externalName, test,
                            classLoader);
                }
                try {
                    Class type = classLoader.loadClass(externalName);
                    LOG.trace("Loaded the class: {} in classloader: {}", type, classLoader);
                    if (test.matches(type)) {
                        LOG.trace("Found class: {} which matches the filter in classloader: {}", type, classLoader);
                        classes.add(type);
                    }
                    found = true;
                    break;
                } catch (ClassNotFoundException e) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Cannot find class '{}' in classloader: {}. Reason: {}", fqn, classLoader,
                                e.getMessage(), e);
                    }
                } catch (NoClassDefFoundError e) {
                    if (LOG.isTraceEnabled()) {
                        LOG.trace("Cannot find the class definition '{}' in classloader: {}. Reason: {}", fqn, classLoader,
                                e.getMessage(), e);
                    }
                }
            }
            if (!found) {
                LOG.debug("Cannot find class '{}' in any classloaders: {}", fqn, set);
            }
        } catch (Exception e) {
            LOG.warn("Cannot examine class '{}' due to a {} with message: {}", fqn, e.getClass().getName(),
                    e.getMessage(), e);

        }
    }

    @Override
    public void clearCache() {
        if (jarCache != null) {
            jarCache.clear();
            jarCache = null;
        }
    }

    @Override
    protected void doStop() throws Exception {
        clearCache();
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy