4

I have an existing "Example Webapp" that references "Example Library" using Maven. I'm running Tomcat 7 inside Eclipse 4.3RC3 with the m2e plugin. When I launch Example Webapp on Tomcat inside Eclipse, I have verified that the example-library.jar is probably getting deployed in the Tomcat instance's WEB-INF/lib folder.

The Example Webapp has code that compiles certain classes on the fly using JavaCompiler.CompilationTask. These dynamically generated classes reference classes in example-library.jar. Unfortunately the compile task is failing because the referenced classes cannot be found.

I understand that I can set the JavaCompiler classpath, but System.getProperty("java.class.path") only returns me the Tomcat classpath, not the webapp classpath:

C:\bin\tomcat\bin\bootstrap.jar;C:\bin\tomcat\bin\tomcat-juli.jar;C:\bin\jdk6\lib\tools.jar

Other have said that I need to get the real path of WEB-INF/lib from the servlet context, but the class generation code doesn't know anything about a servlet context --- it is written to be agnostic of whether it is used on the client or on the server.

In another question, one answer indicated I could enumerate the classloader URLs, and sure enough this provides me with the jars in WEB-INF/lib, but when I provide this as a -classpath option to compiler.getTask(), the task still fails because it can't find the referenced classes.

How can I simply provide the classpath of the currently executing code to the JavaCompiler instance so that it will find the classes from the libraries in WEB-INF/lib? (A similar question was raised but never answered regarding referencing jars within ear files using JavaCompiler.)

Example: In an attempt to get things working at any cost, I even tried to hard-code the classpath. For example, I have foobar.lib in my webapp lib directory, so I used the following code, modified from the answers I indicated above:

List<String> options = new ArrayList<String>();
options.add("-classpath");
options.add("C:\\work\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp0\\wtpwebapps\\FooBar\\WEB-INF\\lib\\foobar.jar");
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits);
boolean success = task.call();

In the end success is false, and my diaognostics indicates package com.example.foo.bar does not exist..., even though that package is in foobar.jar.

Community
  • 1
  • 1
Garret Wilson
  • 18,219
  • 30
  • 144
  • 272

3 Answers3

1

Put example-library.jar somewhere in your file system and pass that location to the code that runs JavaCompiler (the -classpath option). If you use an exploded WAR file to deploy, you can of course point it to the physical location within the WEB-INF/lib folder. The point is that you only need one configurable parameter in your webapp to do this, which can be a properties file entry, -D system property, database row or something else entirely.

Sample code (tested in Tomcat 7 and OpenJDK 1.7 on Fedora 18 x64):

private File compile(File javaFile, String classpath) throws IOException {
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
    Iterable<? extends JavaFileObject> compilationUnit = fileManager.getJavaFileObjects(javaFile);
    List<String> options = classpath != null ? Arrays.asList("-classpath", classpath) : null;
    StringWriter output = new StringWriter();
    try {
        boolean successful = compiler.getTask(output, fileManager, null, options, null, compilationUnit).call();
        if (!successful) {
            throw new CompilationException("Failed to compile: " + javaFile, output.toString());
        }
        return firstClassFileFrom(javaFile.getParentFile());
    } finally {
        fileManager.close();
    }
}

private File firstClassFileFrom(File directory) {
    return directory.listFiles(new FilenameFilter() {
        @Override
        public boolean accept(File dir, String name) {
            return name.endsWith(".class");
        }
    })[0];
}

See https://github.com/jpalomaki/compiler for a runnable sample webapp.

Jukka
  • 4,583
  • 18
  • 14
  • Did you read my question carefully, as well as the links I provided? How is your answer different than the approaches I have already tried, which moreover provided actual code? – Garret Wilson Jun 10 '13 at 13:53
  • Yes, I did read your question. You provided a link to a stackoverflow answer that showed you how you can set the JavaCompiler -classpath, and then you asked "how you can simply provide the classpath of currently executing code to JavaCompiler". Now just create the classpath string (e.g. /path/to/webapp/WEB-INF/lib/example-library.jar) and pass it on to javaCompiler. No need for your component to know anything about Servlet Contexts or classloader URLs. – Jukka Jun 10 '13 at 14:17
  • I had already tried that, and it doesn't work. I've updated the question to include an example of what I've tried. Maybe you can point out what I'm doing incorrectly. – Garret Wilson Jun 10 '13 at 16:05
  • Have you tried this when `classpath` is the path to a jar file in `WEB-INF/lib`, and the Java class you are compiling depends on packages and classes inside that jar file? – Garret Wilson Jun 11 '13 at 14:00
  • Yeah, I used Guava's @Beta annotation as an example dependency to test JavaCompiler classpath, e.g. /home/jpalomaki/dev/workspaces/default/.metadata/.plugins/org.eclipse.wst.server.core/tmp1/wtpwebapps/compiler/WEB-INF/lib/guava-14.0.1.jar – Jukka Jun 11 '13 at 15:05
  • I still can't get this to work. Have you tried this with the class doing the compiling placed inside the jar that has the dependencies? In other words, if by `com.example.MyCompiler` and `com.example.Dependency` are in `.../WEB-INF/lib/library.jar`, will `MyCompiler`'s instance of `JavaCompiler` still find `Dependency` if you put `.../WEB-INF/lib/library.jar` in a compiler `-classpath` option? I'm about to give up and simply provide `library.jar` in the initial Tomcat startup classpath as I mention at http://stackoverflow.com/questions/17029828 . – Garret Wilson Jun 13 '13 at 15:58
  • In my case the class invoking JavaCompiler was not in the JAR but a simple servlet class in WEB-INF/classes. And to be honest, I can't see why the approach you describe does not work. Classloading can be tricky so the best bet is to stick to something simple. You might also consider separating the compiler code from the dependencies into separate projects. That way you would have more leeway in choosing the JAR locations if nothing more. – Jukka Jun 13 '13 at 18:44
  • Jukka, believe me, if I could change this project I would, but sometimes we have to go with what our clients dictate. Simple would be nice, I agree! :) – Garret Wilson Jun 13 '13 at 19:45
0

i met the same question. The reason is not "-classpath" .

my code :

   String classpath ="xx/WEB-INF/clases ";
   List<String> options = classpath != null ? Arrays.asList("-d", classpath,"-cp",classpath) : null;
   CompilationTask task = compiler.getTask(null, javaDinamicoManager, diagnostics,
          options, null, Arrays.asList(sourceObj));
   boolean result = task.call();

the “result” will return true .

liu
  • 1
0

You could follow the answer provided for the even more specific question of how to load dependencies of compiled code from within a web app running directly from an unexpanded WAR file (there are no JAR files to reference - only the container's class loder knows how to access the classes): https://stackoverflow.com/a/45038007/2546679

haui
  • 567
  • 5
  • 18