Find your application Missing Jars, Playing with apache BCEL

adevedo's picture
0
No votes yet

As saied by apache, The Byte Code Engineering Library (Apache Commons BCEL™) is intended to give users a convenient way to analyze, create, and manipulate (binary) Java class files (those ending with .class). Classes are represented by objects which contain all the symbolic information of the given class: methods, fields and byte code instructions, in particular. Here, we will explain a way to check if your application is missing any Jars that may happen when moving your application from testing/development environments to production ones.

Let's explain the required with example:

Once upon a time, I developed a web application that connects to some WebService, I've built the client using JBoss 6 wsconsume tool, everything were going perfectly on my local machine (weblogic 10.3.6 with development mode on), but once I moved the application to another production like environment (weblogic 10.3.3 with production mode on), the application started to complain about a missing class used by the WebService client. There is no problem with fixing "ClassNotFoundException" issues, as the exception already tells you what is the missing class, but this was not the case with me. The used library by the WebService client that produces the error "ClassNotFoundException" catch the exception and throws another "ClassNotFoundException", but in the second one, it set the following error message "...ClassNotFoundException: Engine failed to initialize" !!!, and then I got totally stuck with this exception. After digging in google about the issue, I found that one of the library classes has a static Logger field and that my application is missing the library "Apache Commons Logging" that has that class inside.

The required is to develop a tool that scans all application classes and all referenced classes by application classes and so on and check if any referenced classes are missing from the class path or all required classes are available and everything is OK.

The tool code:

  1. package com.myapp;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.HashSet;
  5. import java.util.LinkedList;
  6. import java.util.Queue;
  7.  
  8. import org.apache.bcel.Repository;
  9. import org.apache.bcel.classfile.Constant;
  10. import org.apache.bcel.classfile.ConstantClass;
  11. import org.apache.bcel.classfile.ConstantUtf8;
  12. import org.apache.bcel.classfile.JavaClass;
  13.  
  14. public class MissingJarsScanner {
  15.        
  16.         public static void scanClass(String entryClass) {
  17.                 HashSet<String> foundClasses = new HashSet<String>();
  18.                 Queue<String> classesQueue = new LinkedList<String>();
  19.                 foundClasses.add(entryClass);
  20.                 classesQueue.add(entryClass);
  21.                 ArrayList<String> notFound = new ArrayList<String>();
  22.  
  23.                 while (!classesQueue.isEmpty()) {
  24.                         String classToScan = classesQueue.poll();
  25.                         try {
  26.                                 System.out.println("Scaning class : " + classToScan);
  27.                                 JavaClass mainClass = Repository.lookupClass(classToScan);
  28.                                 for (Constant constant : mainClass.getConstantPool()
  29.                                                 .getConstantPool()) {
  30.                                         if (constant instanceof ConstantClass) {
  31.                                                 String className = ((ConstantUtf8) mainClass
  32.                                                                 .getConstantPool().getConstant(
  33.                                                                                 ((ConstantClass) constant)
  34.                                                                                                 .getNameIndex())).getBytes();
  35.                                                 if (className.startsWith("[")) {
  36.                                                         className = className.substring(className
  37.                                                                         .lastIndexOf('[') + 1);
  38.                                                         if (className.startsWith("L"))
  39.                                                                 className = className.substring(1,
  40.                                                                                 className.length() - 1);
  41.                                                         else
  42.                                                                 continue;
  43.                                                 }
  44.                                                 className = className.replaceAll("/", "\\.");
  45.                                                 if (!foundClasses.contains(className)) {
  46.                                                         foundClasses.add(className);
  47.                                                         classesQueue.offer(className);
  48.                                                 }
  49.                                         }
  50.                                 }
  51.                         } catch (ClassNotFoundException e) {
  52.                                 System.err.println(classToScan);
  53.                                 notFound.add(classToScan);
  54.                         }
  55.                 }
  56.  
  57.                 System.out.println("-----------------------------------------------\n");
  58.                 System.out.println("Total Classes Referenced : " + foundClasses.size());
  59.                 System.out
  60.                                 .println("Your application is missing the following classes : \n");
  61.                 for (String classNotFound : notFound) {
  62.                         System.out.println(classNotFound);
  63.                 }
  64.         }
  65. }

Now Let's explain the code,

  1. As stated by the JVM specs http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html, any referenced class will be saved in a place called the "Constant Pool", the JVM specs says: "Java virtual machine instructions do not rely on the runtime layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table",
  2. Our tool loads and parse the entry class using BCEL and then
    Extract the Constant Pool from inside the class, after that
    The tool looks for all constant pool items of type "CONSTANT_Class_info" which represent a class type, each item will contains an attribute called "Name Index" which refere to a "CONSTANT_Utf8_info" constant pool item, the CONSTANT_Utf8_info structure is used to represent constant string values, which in our case will be the class full name (i.e. java/lang/Object, org/richfaces/component/html/HtmlMessage, [Ljava/lang/StackTraceElement;). The CONSTANT_Class_info has 3 cases, to represent a single class type (i.e java/lang/Object), an array of class type (i.e. [Ljava/lang/StackTraceElement;) or array/single primitive (int, long...etc) type (i.e. [I -array of integers-).
  3. In case the CONSTANT_Class_info represents a single/array of class type, we fetch the CONSTANT_Utf8_info entry that holds the class name and remove unwanted characters (i.e. array symbol '[')
  4. In case the CONSTANT_Class_info represents an array of primitive types (int, long) the item will be ignored
  5. The fetched class will be saved to be scanned too in the classesQueue
  6. All found classes are saved in foundClasses and all missing classes are saved in notFound list

Now Let's test our code, suppose we have the following application:

  1. package com.myapp;
  2.  
  3. import org.richfaces.component.html.HtmlMessage;
  4.  
  5. public class Test {
  6.         public static void main(String[] args) {
  7.                 try {
  8.                         org.richfaces.component.html.HtmlMessage msg = new HtmlMessage();
  9.                 } catch (Throwable e) { }
  10.         }
  11. }

add richfaces 4.1 libraries to the classpath, execute the code, you will find that everything went OK and no exceptions were thrown and the application did not complain about any missing classes, but Let's check that with our tool, add the following class to the Test application:

  1. package com.myapp;
  2.  
  3. public class FindMissingJarsTest {
  4.         public static void main(String[] args) throws Throwable {
  5.                 MissingJarsScanner.scanClass("com.myapp.Test");
  6.         }
  7. }

with the same classpath entries (which holds the richfaces libraries) execute the FindMissingJarsTest class main method, you will find the following output:

  1. Scaning class : com.myapp.Test
  2. Scaning class : java.lang.Object
  3. Scaning class : org.richfaces.component.html.HtmlMessage
  4. Scaning class : java.lang.Throwable
  5. Scaning class : java.lang.StringBuilder
  6. Scaning class : java.lang.IllegalArgumentException
  7. Scaning class : java.lang.CloneNotSupportedException
  8. Scaning class : java.lang.InterruptedException
  9. Scaning class : java.lang.Class
  10. Scaning class : java.lang.Integer
  11. Scaning class : java.lang.String
  12. Scaning class : java.util.List
  13. Scaning class : java.util.ArrayList
  14. Scaning class : org.richfaces.component.UIRichMessage
  15. Scaning class : javax.faces.component.behavior.ClientBehaviorHolder
  16. Scaning class : org.richfaces.component.html.HtmlMessage$Properties
  17. javax.faces.component.behavior.ClientBehaviorHolder
  18. Scaning class : javax.faces.component.StateHelper
  19. javax.faces.component.StateHelper
  20. Scaning class : java.util.Map
  21. Scaning class : java.util.Arrays
  22. Scaning class : java.util.Collections
  23. Scaning class : java.lang.IllegalStateException
  24. Scaning class : java.lang.StackTraceElement
  25. Scaning class : java.lang.NullPointerException
  26. Scaning class : java.io.Serializable
  27. Scaning class : java.io.IOException
  28. Scaning class : java.lang.System
  29.  
  30. ...
  31.  
  32. Scaning class : java.util.prefs.NodeChangeEvent
  33. Scaning class : java.security.cert.CRLSelector
  34. Scaning class : sun.security.provider.certpath.OCSPRequest
  35. -----------------------------------------------
  36.  
  37. Total Classes Referenced : 2126
  38. Your application is missing the following classes :
  39.  
  40. javax.faces.component.behavior.ClientBehaviorHolder
  41. javax.faces.component.StateHelper
  42. javax.faces.component.UIMessage

Our tool scanning started from class "com.myapp.Test" and scanned all referenced classes, and found 2126 classes referenced and here is the required, our Test application is missing 3 classes, javax.faces.component.behavior.ClientBehaviorHolder, javax.faces.component.StateHelper and javax.faces.component.UIMessage, if you digged google for these classes you will find that the 3 are located in jsf2 Jars, so our Test application is missing the JSF api and JSF implementation libraries :)

This is just a simple use for apache BCEL, many other ideas - crazy ideas - can be implemented using this library

Comments

That really ctparues the spirit of it. Thanks for posting.

Add new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.
By submitting this form, you accept the Mollom privacy policy.