1
String CompilePath = "abc.java";
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
String classpath = System.getProperty("java.class.path");
System.setProperty("java.class.path", classpath + ";" + LocalMachine.home + "WebContent/WEB-INF/lib");
int result = compiler.run(null, null, null, CompilePath);

The above runs fine when executed as a JUnit test since all the jars required for compiling the abc.java file. But when the same code is being run in as server, it fails to find the required jar files. The output of System.getProperty("java.class.path") is E:\apache-tomcat-7.0.4\bin\bootstrap.jar;E:\apache-tomcat-7.0.4\bin\tomcat-juli.jar;C:\Program Files\Java\jdk1.6.0_21\lib\tools.jar

So, my question is how do I make the compiler refer to the jar files from the WEB-INF/lib directory?

Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
brayne
  • 1,355
  • 2
  • 16
  • 27

4 Answers4

3

You cannot depend on java.class.path to be set to anything in particular.

Java establishes this variable when it launches the entire JVM containing your servlet container. As it creates many different class loaders for many different other purposes, it does not change it. It cannot. There is only one value of java.class.path for the entire process, but there can be many different webapps, and indeed many different class loaders inside each webapp.

You will need your own explicit configuration mechanism to communicate the class path for this sort of compilation stuff, and to use getRealPath to construct the pathnames.

bmargulies
  • 97,814
  • 39
  • 186
  • 310
  • Now I understand that it does not make sense to change the classpath dynamically. But since the plain old java objects that are being referred by the servlets can successfully access all the jar files present in WEB-INF/lib directory, why does this path not appear in the classpath by default? – brayne Nov 27 '10 at 23:00
  • Because the classpath contains the jars themselves, not some directory that happens to contain the jars. – bmargulies Nov 27 '10 at 23:43
  • How does the explicit configuration mechanism works? Can you please refer me to some examples of the same? Thanks a lot. – brayne Nov 28 '10 at 21:55
  • I'd follow the breadcrumbs in the other answer for your specific issue. If you can just push your current class loader into this compiler thing to use, you don't need to deal with all of this. – bmargulies Nov 29 '10 at 04:05
2

So, my question is how do I make the compiler refer to the jar files from the WEB-INF/lib directory?

Provide that the webapp's WAR is expanded, you should be able to programmatically create a classpath string that corresponds to what the web container gives you. It is "simply" a matter of duplicating the effective class search path that the web container uses.

However, I suspect that passing a "classpath" argument to the compiler, explicitly or via the System properties is the wrong approach. I found the following in this IBM article.

Compiling Java source requires the following components:

  • A classpath, from which the compiler can resolve library classes. The compiler classpath is typically composed of an ordered list of file system directories and archive files (JAR or ZIP files) that contain previously compiled .class files. The classpath is implemented by a JavaFileManager that manages multiple source and class JavaFileObject instances and the ClassLoader passed to the JavaFileManager constructor. ...

So it would seem that the correct approach is to just grab the relevant classloader object and pass it to the JavaFileManager constructor.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • "So it would seem that the correct approach is to just grab the relevant classloader object and pass it to the JavaFileManager constructor." - given that JavaFileManager is an interface, I do not see how you can pass anything to its "constructor". If this approach seems possible, could you provide a code sample for illustrative purposes? ;-) – Neeme Praks Nov 25 '10 at 08:34
  • ok, JavaFileManager should actually read FileManagerImpl and it is given in the referenced develperWorks article (http://www.ibm.com/developerworks/java/library/j-jcomp/index.html). This article seems to be most useful so far, seems to nicely solve the issue of using already existing classpath object. – Neeme Praks Nov 25 '10 at 08:48
0

They way you are setting the java.class.path system property is asking for trouble - better not do that. More elegant approach would be to use the -classpath option to pass in the custom classpath. See How do I use JDK6 ToolProvider and JavaCompiler with the context classloader? for details.

Also this question can be useful reference: Using javax.tools.ToolProvider from a custom classloader?


As to building the actual classpath, you could cast the context classloader to URLClassLoader and get files from those URLs (as done in this answer).

Or you could use ServletContext.getRealPath(String) and build the entire classpath by hand:

ServletConfig cfg = ...; //obtained in Servlet.init(ServletConfig) method
ServletContex ctx = cfg.getServletContext();
String realWebInfPath = ctx.getRealPath("WEB-INF/lib");
//TODO use the realWebInfPath to create a File object and iterate over all JAR files

Warning: both approaches ONLY work if web application is expanded (not WAR file). If it is not expanded, you are out of luck.

Community
  • 1
  • 1
Neeme Praks
  • 8,956
  • 5
  • 47
  • 47
  • Although the solutions you mentioned seem useful, I found executing a batch script instead, wherein I can easily pass the classpath was a much easy way. I know this is not an ideal way by any means. – brayne Nov 29 '10 at 00:26
-2

Assuming you are using ANT to build your WAR file. You need to do something like below to include jars under WEB-INF/lib in your WAR. Modify the directory structure as it fits your app directory structure.

<war warfile="yourApp.war" webxml="WEB-INF/web.xml">
            <fileset dir="yourWarDir">
                <include name="**/*.jsp"/>  
                <include name="**/include/**"/>
            </fileset>
            <include name="WEB-INF/lib/*"/>
            <include name="WEB-INF/classes/**"/>

</war>
CoolBeans
  • 20,654
  • 10
  • 86
  • 101
  • I have a feeling that he is not asking for instructions on how to include JAR files in web application. – Neeme Praks Nov 25 '10 at 08:49
  • I'm not using ant to build. Eclipse does all the stuff in the web project. – brayne Nov 25 '10 at 11:56
  • @Neeme Praks - His problem lies in packaging the app. He is relying on the server classpath to find jar files that he includes in WEB-INF/lib folder. What does that tell you? – CoolBeans Nov 25 '10 at 16:22
  • @CoolBeans that tells me that he should use some other way to put together his classpath, as suggested by other answers. Your answer seems to suggest that he is not putting his JAR files in WAR. The fact if he is building a WAR or not is not even important, he could also run the webapp in-place, as "exploded" webapp. – Neeme Praks Nov 25 '10 at 16:27
  • So I am approaching it this way. If he is building a WAR file and WEB-INF/lib is included in that WAR file, then his problem is solved right? However, if he is running in place then he has to look for the classpath in a different way. He said he runs it in a server, which leads me to believe he is building a WAR file and deploying it to a web server. – CoolBeans Nov 25 '10 at 16:40
  • Even if he is building a WAR file and WEB-INF/lib is included, his problem is not solved - he does not know how to use that webapp classloader to compile his classes using JavaCompiler. That's why this is not a "packaging problem" - packaging does not really matter. – Neeme Praks Nov 26 '10 at 00:11