LinkageError caused by org.omg.CORBA package in OSGi environment
Recently my colleague observed an unusual class loading error while experimenting in GlassFish V3. I really enjoyed analysing the issue and getting to the root of the problem. I will share my experience here. JDK has a portion of OMG CORBA APIs belonging to org.omg.CORBA and similar package names. The bad thing is org.omg.CORBA package is an extensible package in the sense that different OMG CORBA services contribute classes to this package. I guess this is because they share the same IDL namespace and hence IDL compiler generates the same package name for all of them. Given that JDK does not have all the OMG CORBA services, not all org.omg.CORBA classes are part of JDK. e.g., CORBA Object Service - Transaction is one example. It is not there in JDK, yet it is required in an Java EE environment, as Java Transaction Service depends on it. So, our CORBA engineer produced an OSGi bundle called glassfish-corba-omgapi which contained all the org.omg.CORBA apis needed by GlassFish v3. The system.bundle also exports org.omg.CORBA package. Both the bundles exported with same version, which in itself is a mistake. If you are following me so far, then you can see that org.omg.CORBA package is exported by two bundles with identical version: glassfish-corba-omgapi and system.bundle Our JTS engineer realized that her JTS module is always importing org.omg.CORBA from system.bundle (this is a correct behavior) and hence she is not able to locate some COSTransaction class. To work around it, she removed org.omg.CORBA from org.osgi.framework.system.packages list, as a result now only glassfish-corba-omgapi bundle exports org.omg.CORBA package. Although this made the COSTransaction classes available, it resulted in the following LinkageError:
java.lang.VerifyError: (class: org/glassfish/enterprise/iiop/impl/GlassFishORBManager, method: initORB signature: (Ljava/util/Properties;)V) Bad type in putfield/putstatic at org.glassfish.enterprise.iiop.impl.GlassFishORBFactoryImpl.postConstruct(GlassFishORBFactoryImpl.java:29) at com.sun.hk2.component.AbstractWombImpl.inject(AbstractWombImpl.java:170)
Now the investigation begins. javap -classpath orb-iiop.jar -p -c org.glassfish.enterprise.iiop.impl.GlassFishORBManager shows the following bytecodes:
495: invokestatic #114; //Method com/sun/corba/ee/spi/osgi/ORBFactory.create:([Ljava/lang/String;Ljava/util/Properties;Z)Lcom/sun/corba/ee/spi/orb/ORB; 498: putfield #2; //Field orb:Lorg/omg/CORBA/ORB;
Basically, it translates to something like this: org.omg.CORBA.ORB this.something = com.sun.corba.ee.spi.osgi.ORBFactory.create(properties); As you can see com.sun.corba.ee.spi.osgi.ORBFactory.create(Properties) returns com.sun.corba.ee.spi.orb.ORB, which definitely extends org.omg.CORBA.ORB for the code to compile. So, let's inspect the hierarchy of this class: (-> means extends and within bracket the jar that supplies the class is mentioned as well. Each jar has a separate class loader)
com.sun.corba.ee.spi.orb.ORB (glassfish-corba-orb.jar) -> com.sun.corba.ee.org.omg.CORBA.ORB (glassfish-corba-omgapi.jar) -> org.omg.CORBA_2_3.ORB (JDK's rt.jar) -> org.omg.CORBA.ORB (JDK's rt.jar)
Note a *duplication* of class in the runtime: glassfish-corba-omgapi.jar exports org.omg.CORBA package and has org.omg.CORBA.ORB.class in it. JDK's rt.jar also has org.omg.CORBA.ORB.class. Since org.omg.CORBA is no longer exported by system.bundle, GlassFishORBManager, which is loaded from org-iiop.jar module, gets org.omg.CORBA.ORB.class from glassfish-corba-omgapi.jar. As a result, the simple assignment is now looking like this: org.omg.CORBA.ORB(glassfish-corba-omgapi.jar) = org.omg.CORBA.ORB (JDK's rt.jar) As you can see, they are two different types and hence the VerificationError. There are several ways to solve it. 1. If we add a *bootdelegation* property for org.omg.CORBA package, then everyone will see org.omg.CORBA.ORB from JDK's rt.jar and that will address the issue. Although this solution looks simple, it is not preferred, as it is a violation of modularity principle. More over, it leads to split packages which can lead to other issues at runtime like access violation of package scoped objects. 2. If we remove all the org.omg CORBA related packages (including org.omg.CORBA_2_3) from system packages list and ensure that they are exported by glassfish-corba-omgapi bundle, then also every one will see one version of classes and the problem will be solved. There is an assumption here that no other JDK class other than the CORBA implementation has direct reference to these packages. This assumption may not be true. 3. If we can put glassfish-corba-omgapi.jar in one of the java.endorsed.dirs and add all the new packages to system.packages list, then also everyone see one consistent version of classes. Since we can't assume we are using jre/lib/endorsed, this has the disadvantage of requiring user to specify -Djava.endorsed.dirs in command line, something which does not fly with "java -jar glassfish.jar" style invocation. For now, I think we can start with option #2 and if we face any issues, we can go to #1. Now let's see why the error was so cryptic in an OSGi environment. After all, OSGi is supposed to give us more helpful errors. The problem is our system packages are exported without any "uses" clause. "uses" clause plays a vital role in package resolution and correct specification of uses attribute might have given us a better error stating the class space was not consistent. Having said that, I still don't understand why we didn't get a "loader constraint violation" when GlassFishORBManager was loaded. Is this because ORBFactory returns com.sun.corba.ee.spi.orb.ORB, where as GlassFishORBManager has a field of type org.omg.CORBA.ORB? Needs further investigation. Interested readers should look at the excellent classloading discussion by Liang and Bracha.