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

com.devonfw.cobigen.impl.util.TemplatesClassloaderUtil 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.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.FileSystem;
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;

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

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

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

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

    /**
     * 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, TemplatesClassloaderUtil.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 ArrayList addFoldersToClassLoaderUrls(Path configurationFolder) throws MalformedURLException {
        ArrayList 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 configurationFolder
     *            Path to add to ClassLoader
     * @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(Path configurationFolder, ClassLoader classLoader)
        throws IOException {
        List> result = new LinkedList<>();
        ArrayList classLoaderUrls = new ArrayList<>(); // stores ClassLoader URLs
        Path templateRoot = null;
        ClassLoader inputClassLoader = null;
        URL contextConfigurationLocation = null;
        if (configurationFolder != null) {
            templateRoot = configurationFolder;
            classLoaderUrls = addFoldersToClassLoaderUrls(configurationFolder);
            inputClassLoader = getUrlClassLoader(classLoaderUrls.toArray(new URL[] {}), classLoader);
            contextConfigurationLocation = configurationFolder.toUri().toURL();
        } else {
            inputClassLoader = classLoader;
            contextConfigurationLocation = getContextConfiguration(inputClassLoader);
        }

        if (contextConfigurationLocation.toString().startsWith("jar")) {
            result = resolveFromJar(result, inputClassLoader, contextConfigurationLocation);
        } else {
            result = resolveFromFolder(result, templateRoot, inputClassLoader);
        }

        return result;
    }

    /**
     * Resolves utility classes from Folder
     *
     * @param result
     *            List to store utility classes in
     * @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(List> result, Path templateRoot,
        ClassLoader inputClassLoader) {
        LOG.debug("Processing configuration folder {}", templateRoot.toString());
        LOG.info("Searching for classes in configuration folder...");
        List foundPaths = new LinkedList<>();

        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 result
     *            List to store utility classes in
     * @param inputClassLoader
     *            ClassLoader to use for storing of classes
     * @param contextConfigurationLocation
     *            URL to check for classes
     * @return List of classes to load utilities from
     */
    private static List> resolveFromJar(List> result, ClassLoader inputClassLoader,
        URL contextConfigurationLocation) {
        LOG.debug("Processing configuration archive {}", contextConfigurationLocation);
        LOG.info("Searching for classes in configuration archive...");

        List foundClasses = new LinkedList<>();
        try {
            // Get the URI of the jar from the URL of the contained context.xml
            URI jarUri = URI.create(contextConfigurationLocation.toString().split("!")[0]);

            // Make sure to create file system for jar file
            FileSystem jarfs = FileSystemUtil.getOrCreateFileSystem(jarUri);

            foundClasses = walkJarFile(jarUri, jarfs);
        } 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 jarUri
     *            URI of jar file
     * @param jarfs
     *            FileSystem of jar file
     * @return List of file paths containing class files
     * @throws IOException
     *             if file could not be visited
     */
    private static List walkJarFile(URI jarUri, FileSystem jarfs) throws IOException {
        List foundClasses = new LinkedList<>();
        // walk the jar file
        LOG.debug("Searching for classes in {}", jarUri);
        Files.walkFileTree(jarfs.getPath("/"), 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