Menu

#17 DevLoader does not work on Tomcat 8.5.4

9.x.x
closed
MarkusK
DevLoader (1)
2016-09-15
2016-09-06
Georg Nepp
No

With Tomcat 8.0.32 everything works fine, but with Tomcat 8.5.4 the DevLoader does not work.

[DevLoader] Starting DevLoader modified by e.siffert (August 04 2014) for Tomcat 8: Apache Tomcat/8.5.4
[DevLoader] Error: Unable to install WebappClassLoader, received ClassLoader was null !

Regards, Georg

Discussion

  • Georg Nepp

    Georg Nepp - 2016-09-09

    My solution:
    package org.apache.catalina.loader;

    import java.io.File;
    import java.io.FileFilter;
    import java.io.FileReader;
    import java.io.IOException;
    import java.io.LineNumberReader;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    import java.util.StringTokenizer;

    import javax.servlet.ServletContext;

    import org.apache.catalina.Globals;
    import org.apache.catalina.LifecycleException;
    import org.apache.catalina.util.ServerInfo;

    public class DevLoader extends WebappLoader {

    private static final String webClassPathFile = ".#webclasspath";
    private static final String tomcatPluginFile = ".tomcatplugin";
    
    public DevLoader() {
        super();        
    }       
    public DevLoader(ClassLoader parent) {
        super(parent);
    }
    
    /**
     * @see org.apache.catalina.Lifecycle#start()
     */
    @Override
    public void startInternal() throws LifecycleException {
        log("Starting DevLoader modified by Space008 (2016.09.09) for Tomcat 8.5: " + ServerInfo.getServerInfo());
        super.startInternal();
    
        ClassLoader cl = super.getClassLoader();
        if (!(cl instanceof WebappClassLoaderBase)) {
            logError("Unable to install WebappClassLoaderBase, received ClassLoader was " + cl.getClass() + "!");
            return;
        }
        WebappClassLoaderBase devCl = (WebappClassLoaderBase) cl;
    
        List<String> webClassPathEntries = readWebClassPathEntries();
        StringBuilder classpath   = new StringBuilder();
        for (Iterator<String> it = webClassPathEntries.iterator(); it.hasNext();) {
            String entry = it.next();
            File f = new File(entry);
            if (f.exists()) {
                if (f.isDirectory() && !entry.endsWith("/")) {
                    f = new File(entry + "/");
                }
                try {
                    URL url = f.toURL(); // f.toURI().toURL(); should be used since Java 8
                    devCl.addURL(url);
                    classpath.append(f.toString()).append(File.pathSeparatorChar);
                    log("added to classpath: " + url.toString());
                } catch (MalformedURLException e) {
                    logError(entry + " invalid (MalformedURL)");
                }
            } else {
                logError(entry + " does not exist !");
            }
        }
    
        String cp = (String)getServletContext().getAttribute(Globals.CLASS_PATH_ATTR);
        StringTokenizer tokenizer = new StringTokenizer(cp, File.pathSeparatorChar + "");
        while(tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            // only on windows 
            if (token.charAt(0)=='/' && token.charAt(2)==':') {
                token = token.substring(1);
            }
            classpath.append(token).append(File.pathSeparatorChar);
        }
        getServletContext().setAttribute(Globals.CLASS_PATH_ATTR, classpath.toString());
        log("class path for our application class loader = " + classpath);
    }
    
    protected void log(String msg) {
        System.out.println("[DevLoader] " + msg);
    }
    protected void logError(String msg) {
        System.err.println("[DevLoader] Error: " + msg);
    }
    
    protected List<String> readWebClassPathEntries() {
        List<String> rc = null;
    
        File prjDir = getProjectRootDir();
        if (prjDir == null) {
            return new ArrayList<>();
        }
        log("projectdir=" + prjDir.getAbsolutePath());
    
        rc = loadWebClassPathFile(prjDir);
    
        if (rc == null) rc = new ArrayList<String>(); // should not happen !
        return rc;
    }
    
    protected File getProjectRootDir() {
        File rootDir = getWebappDir();
        FileFilter filter = new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.getName().equalsIgnoreCase(webClassPathFile) ||
                        file.getName().equalsIgnoreCase(tomcatPluginFile));
            }
        };
        while(rootDir != null) {
            File[] files = rootDir.listFiles(filter);
            if (files != null && files.length >= 1) {
                return files[0].getParentFile();
            }
            rootDir = rootDir.getParentFile();
        }
        return null;
    }
    
    protected List<String> loadWebClassPathFile(File prjDir) {
        File cpFile = new File(prjDir, webClassPathFile);
        if (cpFile.exists()) {          
            try (FileReader reader = new FileReader(cpFile); LineNumberReader lr = new LineNumberReader(reader)) {
                List<String> rc = new ArrayList<>();
                String line;
                while((line = lr.readLine()) != null) {
                    line = line.replace('\\', '/');
                    rc.add(line);
                }
                return rc;
            } catch(IOException ioEx) {
                return null;
            }           
        } else {
            return null;
        }
    }
    
    protected ServletContext getServletContext() {
        return getContext().getServletContext();
    }
    
    protected File getWebappDir() {     
        File webAppDir = new File(getServletContext().getRealPath("/"));
        return webAppDir;
    }
    

    }

     
  • MarkusK

    MarkusK - 2016-09-09

    @Gregor: Could you please provide some bits of explanation on what you changed? And maybe highlight what you have changed?

    When having enough understood I am happy to apply the patch and do a fresh DevLoader release.

     
  • Jean-François Morin

    I did the same patch before seeing the version above... Looks like I searched the wrong place!

    Anyway, the problem comes from Tomcat 8.5.x (8.5.5 in my case) using a ParallelWebappClassLoader instead of a WebappClassLoader. However, both classes extend WebappClassLoaderBase, which defines the addURL() method required later in the startInternal() method.

    By the way, while doing my own patch (which is similar to the one above including some Java 7 code optimization — looks like Georg Nepp and I had the same ideas), I configured the project to use Maven and I tricked my pom.xml file into generating a JAR identical in structure to the original, including the DevLoader.java source file. It you are interested in using Maven to facilitate dependency and build management, please tell me and I will provide you with my Maven project structure.

     
  • MarkusK

    MarkusK - 2016-09-15
    • status: open --> closed
    • Milestone: Open --> 9.0.0
     
  • MarkusK

    MarkusK - 2016-09-15

    @Gregor Nepp: I applied your change and uploaded a new DevLoader jar file. Thank you for your patch!

    @Jean-Francois Morin: I'd be happy to have the DevLoader build automated. I did not consider yet to automate it at all because I only once had to build a new jar. Please open a new ticket containing a patch and maybe a few bits of explanation.

     

Log in to post a comment.