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

com.devonfw.cobigen.impl.util.ConfigurationClassLoaderUtil Maven / Gradle / Ivy

There is a newer version: 2021.12.006
Show newest version
package com.devonfw.cobigen.impl.util;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.devonfw.cobigen.api.exception.InvalidConfigurationException;
import com.devonfw.cobigen.impl.config.ConfigurationHolder;

/**
 * Utilities related to the retrieval of Templates utility classes
 */
public class ConfigurationClassLoaderUtil {

    /** Logger instance. */
    private static final Logger LOG = LoggerFactory.getLogger(ConfigurationClassLoaderUtil.class);

    /** Locations to check for template utility classes */
    private static final String[] classFolderLocations = new String[] { "target/classes" };

    /** Locations to check for context.xml */
    private static final String[] configFileLocations =
        new String[] { "context.xml", "src/main/templates/context.xml" };

    /**
     * Checks the ClassLoader for any context.xml provided either in configurationFolder or in
     * templates-plugin and returns its URL
     * @param classLoader
     *            ClassLoader to check resources from
     * @return URL of the context configuration file path
     * @throws InvalidConfigurationException
     *             if no configuration file was found
     */
    public static URL getContextConfiguration(ClassLoader classLoader) throws InvalidConfigurationException {
        URL contextConfigurationLocation = null;
        for (String possibleLocation : configFileLocations) {
            URL configLocation = classLoader.getResource(possibleLocation);
            if (configLocation != null) {
                contextConfigurationLocation = configLocation;
                LOG.debug("Found context.xml URL @ {}", contextConfigurationLocation);
                break;
            }
        }

        if (contextConfigurationLocation == null) {
            throw new InvalidConfigurationException("No context.xml could be found in the classpath!");
        }
        return contextConfigurationLocation;
    }

    /**
     * Initializes the ClassLoader with given URLs array
     * @param urls
     *            URL[] Array of URLs to load into ClassLoader
     * @param classLoader
     *            to add urls to
     * @return ClassLoader to load resources from
     */
    private static ClassLoader getUrlClassLoader(URL[] urls, ClassLoader classLoader) {
        ClassLoader inputClassLoader = null;
        if (classLoader != null) {
            inputClassLoader = URLClassLoader.newInstance(urls, classLoader);
        } else {
            inputClassLoader = URLClassLoader.newInstance(urls, ConfigurationClassLoaderUtil.class.getClassLoader());
        }

        return inputClassLoader;
    }

    /**
     * Adds folders to class loader urls e.g. src/main/templates for config.xml detection
     * @param configurationFolder
     *            Path configuration folder for which to generate paths
     * @return ArrayList of URLs
     * @throws MalformedURLException
     *             if the URL was malformed
     */
    private static List addFoldersToClassLoaderUrls(Path configurationFolder) throws MalformedURLException {
        List classLoaderUrls = new ArrayList<>();
        for (String possibleLocation : classFolderLocations) {
            Path folder = configurationFolder;
            folder = folder.resolve(possibleLocation);
            if (Files.exists(folder)) {
                classLoaderUrls.add(folder.toUri().toURL());
                LOG.debug("Added {} to class path", folder);
            }
        }
        return classLoaderUrls;
    }

    /**
     * Walks the class path in search of a 'context.xml' resource to identify the enclosing folder or jar
     * file. That location is then searched for class files and a list with those loaded classes is returned.
     * If the sources are not compiled, the templates will not be able to be generated.
     * @param configurationHolder
     *            {@link ConfigurationHolder}
     * @param classLoader
     *            ClassLoader to load jar from
     * @return a List of Classes for template generation.
     * @throws IOException
     *             if either templates jar or templates folder could not be read
     */
    public static List> resolveUtilClasses(ConfigurationHolder configurationHolder, ClassLoader classLoader)
        throws IOException {

        List> result = new LinkedList<>();
        List classLoaderUrls = new ArrayList<>(); // stores ClassLoader URLs

        if (configurationHolder.isJarConfig()) {
            result = resolveFromJar(classLoader, configurationHolder);
        } else {
            ClassLoader inputClassLoader = null;
            classLoaderUrls = addFoldersToClassLoaderUrls(configurationHolder.getConfigurationPath());
            inputClassLoader = getUrlClassLoader(classLoaderUrls.toArray(new URL[] {}), classLoader);
            result = resolveFromFolder(configurationHolder.getConfigurationPath(), inputClassLoader);
        }

        return result;
    }

    /**
     * Resolves utility classes from Folder
     *
     * @param templateRoot
     *            Path to template folder containing classes
     * @param inputClassLoader
     *            ClassLoader to use for storing of classes
     * @return List of classes to load utilities from
     */
    private static List> resolveFromFolder(Path templateRoot, ClassLoader inputClassLoader) {
        LOG.debug("Processing configuration from {}", templateRoot.toString());
        LOG.info("Searching for classes in configuration folder...");
        List foundPaths = new LinkedList<>();
        List> result = new ArrayList<>();

        try {
            foundPaths = walkTemplateFolder(templateRoot);
        } catch (IOException e) {
            LOG.error("Could not read templates folder", e);
        }
        if (foundPaths.size() > 0) {

            // clean up test classes
            Iterator it = foundPaths.iterator();
            while (it.hasNext()) {
                Path next = it.next();
                if (!templateRoot.relativize(next).startsWith("target/classes")) {
                    LOG.debug("    * Removed test class file {}", next);
                    it.remove();
                }
            }

            for (Path path : foundPaths) {
                try {
                    result.add(loadClassByPath(templateRoot.relativize(path), inputClassLoader));
                } catch (ClassNotFoundException e) {
                    LOG.error("Class could not be loaded into ClassLoader", e);
                }
            }
        } else {
            LOG.info("Could not find any compiled classes to be loaded as util classes in template folder.");
        }

        return result;
    }

    /**
     * Resolves utility classes from Jar archive
     *
     * @param inputClassLoader
     *            ClassLoader to use for storing of classes
     * @param configurationHolder
     *            configuration holder
     * @return List of classes to load utilities from
     */
    private static List> resolveFromJar(ClassLoader inputClassLoader,
        ConfigurationHolder configurationHolder) {
        LOG.debug("Processing configuration archive {}", configurationHolder.getConfigurationLocation());
        LOG.info("Searching for classes in configuration archive...");

        List> result = new ArrayList<>();
        List foundClasses = new LinkedList<>();
        try {
            foundClasses = walkJarFile(configurationHolder);
        } catch (IOException e) {
            LOG.error("Could not read templates jar file", e);
        }
        if (foundClasses.size() > 0) {
            for (String className : foundClasses) {
                try {
                    result.add(inputClassLoader.loadClass(className));
                } catch (ClassNotFoundException e) {
                    LOG.warn("Could not load {} from classpath", className);
                    LOG.debug("Class was not found", e);
                }
            }
        } else {
            LOG.info("Could not find any compiled classes to be loaded as util classes in jar file.");
        }
        return result;
    }

    /**
     * Walks the template folder in search of utility classes
     *
     * @param templateRoot
     *            Path to template folder
     * @return List of paths containing a class file
     * @throws IOException
     *             if file could not be visited
     */
    private static List walkTemplateFolder(Path templateRoot) throws IOException {
        final List foundPaths = new LinkedList<>();
        Files.walkFileTree(templateRoot, new SimpleFileVisitor() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (file.toString().endsWith(".class")) {
                    foundPaths.add(file);
                    LOG.debug("    * Found class file {}", file);
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // Log errors but do not throw an exception
                LOG.warn("An IOException occurred while reading file on path {} with message: {}", file,
                    exc.getMessage(), LOG.isDebugEnabled() ? exc : null);
                return FileVisitResult.CONTINUE;
            }
        });
        return foundPaths;
    }

    /**
     * Walks the jar file in search of utility classes
     *
     * @param configurationHolder
     *            the holder of the parsed configuration
     * @return List of file paths containing class files
     * @throws IOException
     *             if file could not be visited
     */
    private static List walkJarFile(ConfigurationHolder configurationHolder) throws IOException {
        List foundClasses = new LinkedList<>();
        // walk the jar file
        LOG.debug("Searching for classes in {}", configurationHolder.getConfigurationLocation());
        Files.walkFileTree(configurationHolder.getConfigurationPath(), new SimpleFileVisitor() {

            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                if (file.toString().endsWith(".class")) {
                    LOG.debug("    * Found class file {}", file);
                    // remove the leading '/' and the trailing '.class'
                    String fileName = file.toString().substring(1, file.toString().length() - 6);
                    // replace the path separator '/' with package separator '.' and add it to the
                    // list of found files
                    foundClasses.add(fileName.replace("/", "."));
                }
                return FileVisitResult.CONTINUE;
            }

            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                // Log errors but do not throw an exception
                LOG.warn("An IOException occurred while reading file on path {} with message: {}", file,
                    exc.getMessage(), LOG.isDebugEnabled() ? exc : null);
                return FileVisitResult.CONTINUE;
            }
        });
        return foundClasses;
    }

    /**
     * Tries to load a class over it's file path. If the path is /a/b/c/Some.class this method tries to load
     * the following classes in this order: 
     * 
  • Some
  • *
  • c.Some
  • *
  • b.c.Some
  • *
  • a.b.c.Some * @param classPath * the {@link Path} of the Class file * @param cl * the used ClassLoader * @return Class of the class file * @throws ClassNotFoundException * if no class could be found all the way up to the path root */ private static Class loadClassByPath(Path classPath, ClassLoader cl) throws ClassNotFoundException { // Get a list with all path segments, starting with the class name Queue pathSegments = new LinkedList<>(); // Split the path by the systems file separator and without the .class suffix for (int i = classPath.getNameCount() - 1; i > -1; i--) { pathSegments.add(FilenameUtils.removeExtension(classPath.getName(i).getFileName().toString())); } if (!pathSegments.isEmpty()) { String className = ""; while (!pathSegments.isEmpty()) { if (className == "") { className = pathSegments.poll(); } else { className = pathSegments.poll() + "." + className; } try { return cl.loadClass(className); } catch (ClassNotFoundException | NoClassDefFoundError e) { continue; } } } throw new ClassNotFoundException("Could not find class on path " + classPath.toString()); } }




  • © 2015 - 2024 Weber Informatics LLC | Privacy Policy