6 Replies Latest reply on Mar 20, 2007 9:19 AM by Alexander Sack

    Bundle jars within EAR?

    Oskar Carlstedt Novice

      Hello!!

      I have tried to find a solution on my problem by reading this forum, googling for a day or two, but I can't find a good solution. My problem is EAR files containing other jar files. Deploying this file give an error telling me "EJBLocal is not visible..."

      A std. EAR file can contain a jar file that are used by the EJB- and web modules packed within the EAR file. Doing this with an EAR containing a war file having JAX-WS web service and a jar file containing a simple stateless Session bean. I run JBoss 4.0.5.GA in isolated mode.

      My EAR file looks like:

      my-test-ear
       beans.jar (having a MANIFEST.MF file with classpath pointing to lib/...)
       jaxws-web.war (having a MANIFEST.MF with classpath pointing to lib/...)
       /lib
       xbean.jar
       xmlpublic.jar
       saxon.jar
       ...
      
       /META-INF
       application.xml
       MANIFEST.MF
       jboss-app.xml (containing a loader-repository-tag with valie my-ear:app=ejb3)
      
      
      
      ************ application.xml ****************
      <?xml version="1.0" encoding="UTF-8"?>
      <application xmlns="http://java.sun.com/xml/ns/j2ee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
       http://java.sun.com/xml/ns/j2ee/application_1_4.xsd" version="1.4">
       <description>my-test</description>
       <display-name>my-test</display-name>
       <module>
       <ejb>my-test-beans.jar</ejb>
       </module>
       <module>
       <web>
       <web-uri>my-test-jaxws-web.war</web-uri>
       <context-root>/my-test-jaxws-web</context-root>
       </web>
       </module>
      </application>
      
      
      
      ************** jboss-app.xml *****************
      <!DOCTYPE jboss-app
       PUBLIC "-//JBoss//DTD J2EE Application 1.4//EN"
       "http://www.jboss.org/j2ee/dtd/jboss-app_4_0.dtd">
      <jboss-app>
       <loader-repository>my-test:app=ejb3</loader-repository>
      </jboss-app>
      
      

      Here is the contents of my beans.jar:
      my-test-beans.jar
       META-INF
       MANIFEST.MF
       maven
       my-test
       my-test-beans
       pom.xml
       pom.properties
       my
       beans
       TestBean.class
      
      
      
      ***************** TestBean.java ****************
      package my.test;
      
      import javax.ejb.Stateless;
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      import org.apache.xmlbeans.XmlObject;
      
      
      @Stateless
      public class TestBean implements Test {
      
       /**
       * Declare a logger to use in this class
       */
       private final static Log log = LogFactory.getLog(TestBean.class);
      
       public XmlObject doSomething(XmlObject theRequestXmlObject) {
       return theRequestXmlObject;
       }
      }
      
      
      
      ******************* Test.java *********************
      package my.test;
      
      public interface Test {
      
       public org.apache.xmlbeans.XmlObject doSomething(org.apache.xmlbeans.XmlObject theRequestXmlObject);
      }
      
      
      
      
      

      And here is what my war fil looks like
      my-test-jaxws-web.war
       META-INF
       MANIFEST.MF
       maven
       my-test
       my-test-jaxws-web
       pom.xml
       pom.properties
       WEB-INF
       wsdl
       test.wsdl
       test.xsd
       classes
       my
       test
       TestServiceEnpoint.class
       web.xml
      
      ********************** TestServiceEndpoint.java ******************
      package my.test;
      
      import java.io.ByteArrayOutputStream;
      import java.lang.reflect.Method;
      import java.util.List;
      import java.util.Map;
      
      import javax.annotation.Resource;
      import javax.ejb.Stateless;
      import javax.naming.InitialContext;
      import javax.xml.transform.OutputKeys;
      import javax.xml.transform.Source;
      import javax.xml.transform.Transformer;
      import javax.xml.transform.TransformerFactory;
      import javax.xml.transform.dom.DOMResult;
      import javax.xml.transform.dom.DOMSource;
      import javax.xml.transform.stream.StreamResult;
      import javax.xml.ws.Provider;
      import javax.xml.ws.Service;
      import javax.xml.ws.ServiceMode;
      import javax.xml.ws.WebServiceContext;
      import javax.xml.ws.WebServiceException;
      import javax.xml.ws.WebServiceProvider;
      import javax.xml.ws.handler.MessageContext;
      
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      import org.apache.xmlbeans.XmlObject;
      import org.apache.xmlbeans.XmlOptions;
      import org.w3c.dom.Node;
      
      @Stateless
      @WebServiceProvider(
       serviceName = "Test",
       portName = "TestSoap11Port",
       targetNamespace = "http://my-test/service",
       wsdlLocation = "WEB-INF/wsdl/test.wsdl")
      @ServiceMode(value = Service.Mode.PAYLOAD)
      public class TestServiceEndpoint implements Provider<Source> {
      
       /**
       * Declare a logger to use
       */
       private final static Log log = LogFactory.getLog(TestServiceEndpoint.class);
      
       /**
       * The sei to use
       */
       private Object sei = null;
      
       /**
       * The soapaction http header. Just a string with the value 'soapaction'
       */
       private static final String HTTP_HEADER_SOAP_ACTION = "soapaction";
      
       @Resource
       protected WebServiceContext webServiceContext;
      
       /**
       * Does everything that must be done. This method is a kind of dispatcher
       * that finds the business method to invoke. Invokes the method and
       * serializes the response.
       */
       public Source invoke(Source requestSource) {
      
       try {
      
       // get transformer and set output to xml
       Transformer transformer = TransformerFactory.newInstance()
       .newTransformer();
       transformer.setOutputProperty(OutputKeys.METHOD, "xml");
      
       // holder of encoding, used in debug
       String encoding = null;
      
       // get everything as a dom result
       DOMResult domResult = new DOMResult();
       transformer.transform(requestSource, domResult);
      
       // get dom node from dom result
       Node domNode = domResult.getNode();
      
       // let xml beans parse the dom node
       XmlObject xmlRequestObject = XmlObject.Factory.parse(domNode);
      
       if (log.isDebugEnabled()) {
      
       // get everything as a stream result
       ByteArrayOutputStream outTmpStream = new ByteArrayOutputStream();
       StreamResult streamResult = new StreamResult();
       streamResult.setOutputStream(outTmpStream);
      
       // transform everything into the out stream
       transformer.transform(requestSource, streamResult);
      
       // get encoding of incomming request, default to utf-8
       String tmpContent = new String(outTmpStream.toByteArray());
       String encodingStartToken = "encoding=\"";
       String encodingEndToken = "\"";
      
       int encodingStart = tmpContent.indexOf(encodingStartToken);
       if(encodingStart != -1) {
      
       // get end index of encoding
       encodingStart = encodingStart + encodingStartToken.length();
       int encodingEnd = tmpContent.indexOf(encodingEndToken, encodingStart);
      
       // set new encoding
       encoding = tmpContent.substring(encodingStart, encodingEnd);
       }
       else {
      
       // default encoding is UTF-8
       encoding = "UTF-8";
       }
      
       // get a string reppresentation of incomming request
       String xmlRequestString = new String(outTmpStream.toByteArray(), encoding);
      
       log.debug("Incomming request:\n" + xmlRequestString);
       }
      
       // instantiate the sei
       InitialContext initialContext = new InitialContext();
       sei = initialContext.lookup("my-test/TestBean/local");
      
       // get business method
       Method wsdlJavaMethod = getWsdlJavaMethod();
      
       // invoke method
       XmlObject xmlResponseObject = (XmlObject) wsdlJavaMethod.invoke(sei, xmlRequestObject);
      
       // if debug, debug response
       if(log.isDebugEnabled()) {
      
       // create xml options
       XmlOptions xmlOptions = new XmlOptions();
       xmlOptions.setSavePrettyPrint();
       xmlOptions.setSavePrettyPrintIndent(4);
       xmlOptions.setUseDefaultNamespace();
      
       // creat a byte arry stream where to print result
       ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
       xmlResponseObject.save(tmpStream, xmlOptions);
      
       // debug result
       log.debug("Outgoing response:\n" + new String(tmpStream.toByteArray(), encoding));
       }
      
       // return the object
       return new DOMSource(xmlResponseObject.newDomNode());
      
       } catch (RuntimeException re) {
      
       throw re;
       } catch (Exception e) {
      
       throw new WebServiceException(e);
       }
       }
      
       private Method getWsdlJavaMethod() {
      
       // get message context
       MessageContext messageContext = webServiceContext.getMessageContext();
      
       // get request headers from message context
       Map<String, ?> requestHeaders = (Map<String, ?>) messageContext
       .get(MessageContext.HTTP_REQUEST_HEADERS);
      
       // get soap action header
       List<String> soapactions = (List<String>) requestHeaders
       .get(HTTP_HEADER_SOAP_ACTION);
      
       // ceck soapaction assertions
       if (soapactions == null || soapactions.size() != 1) {
      
       // one soapaction must be given per each request
       throw new WebServiceException(
       "...");
       }
      
       // get soapaction
       String soapaction = soapactions.get(0);
      
       log.debug("Got soapaction httpheader: " + soapaction);
      
       // extract method name
       String methodName = extractJavaMethodName(soapaction);
      
       log.debug("Extracted method name from soapaction: " + methodName);
      
       // get the method to invoke
       Method wsdlJavaMethod;
       try {
       wsdlJavaMethod = sei.getClass().getMethod(methodName, new Class<?>[]{XmlObject.class});
      
       // return the found method
       return wsdlJavaMethod;
      
       } catch (SecurityException e) {
      
       throw new RuntimeException("...", e);
       } catch (NoSuchMethodException e) {
      
       throw new RuntimeException("..." xmlObject)", e);
       }
       }
      
       /**
       * Returns the java method name extracted from a given soap action string.
       * This method does not validate the returned value, it just extracts a
       * possible java method name.
       *
       * @param soapaction
       * the soap action to extract method from
       * @return a java method name
       */
       private String extractJavaMethodName(String soapaction) {
      
       String methodName = null;
      
       if(soapaction.indexOf(":") != -1) {
      
       // get part of soapaction after the ':'-character
       methodName = soapaction.substring(soapaction.lastIndexOf(":") + 1);
       }
       else {
      
       // no ':'-character. method name is same as soapaction
       methodName = soapaction;
       }
      
       // remove trailing cite character and then return
       return methodName.substring(0, methodName.length() - 1);
       }
      }
      
      
      


      When I deploy the war file itself (but then having all the dependencies in the WEB-INF/lib folder, that part works. But putting EJB3s and WARs together in an EAR file seems to be a problem. Has anyone done tis before?

      Can anyone help me with this. I lack in finding more ideas of my trial an error now.

      Thanks
      Oskar

        • 1. Re: Bundle jars within EAR?
          Oskar Carlstedt Novice

          Deploying the same file again, but without the jars log4j and commons-logging. Wolla!!, It seems to work.

          Isn't this a classloader bug/error? What I mean is. The files I put in my ear are those I want to use, especially in isolated mode. It shall not matter if a put a log4j jar in my ear, it shall override the one in the server/default/lib folder.

          I think this is very important when delivering ready to install archives (an archive I can send to a customer just telling him "drop the ear in the deploy folder and that's it").

          //Oskar

          • 2. Re: Bundle jars within EAR?
            Oskar Carlstedt Novice

            I will report this in JIRA. This is even worse. If I deploy my EAR file with the log4j and commons-logging jars bundled, then I must restart JBoss to be able to deploy the new ear without my logging jars.

            Strange, but good for you to know.


            Best
            Oskar

            • 3. Re: Bundle jars within EAR?
              Gary Cuozzo Newbie

              I don't know if this is related to your issues, but recently I had an issue with using /lib inside my ear/sar/jar files as JBoss was trying to load everything from the "master" lib directory for the app server. I moved all my libraries into the root and adjusted my deployment descriptors accordingly and my issues went away. Maybe worth a try for you. I don't know if this is a bug (sounds like one) or not.

              HTH,
              gary.

              • 4. Re: Bundle jars within EAR?
                Alexander Sack Novice

                 

                "oskar.carlstedt" wrote:
                Deploying the same file again, but without the jars log4j and commons-logging. Wolla!!, It seems to work.

                Isn't this a classloader bug/error? What I mean is. The files I put in my ear are those I want to use, especially in isolated mode. It shall not matter if a put a log4j jar in my ear, it shall override the one in the server/default/lib folder.

                I think this is very important when delivering ready to install archives (an archive I can send to a customer just telling him "drop the ear in the deploy folder and that's it").

                //Oskar


                No I don't its a bug per se but a JBoss behavior/choice. Java works on class loader parent delegation so if the jar is already in server/default/lib, it will pick that one up FIRST before it loads yours. Your manifest entries just put these classes on the path for the loader to find them. However, again, classloader is going to look at the parent loader which will contain log4j and pick that one first before it even attempts to load the one you package. Now, I TOTALLY agree with you that there should be a way to get around this on a per EAR level but I don't think thats covered by the spec at this point (and yes I voiced this opinion to the group at one point a while ago).

                I noticed that unlike 4.0.4.GA, 4.0.5 finally puts back jaxen which was something we had to manually do to get access to XPath.

                • 5. Re: Bundle jars within EAR?
                  Oskar Carlstedt Novice

                  Hi All!!

                  Thanks for all replies. I think it is very important to "solve" this classloader inheritance issue, especially in isolated mode. AFAIK in Apache Tomcat it is possible to "override" jars in the libs folder when deploying a war file. Why can't I do this in JBoss?

                  Of course, this is an implementation task, but in my eyes, the leaf most classes shall always be taken care before those to be found closer to the root. It is quite easy/obvious thinking, but might be hard to implement.

                  Thanks again all for your replies
                  //Oskar

                  • 6. Re: Bundle jars within EAR?
                    Alexander Sack Novice

                     

                    "oskar.carlstedt" wrote:
                    Hi All!!

                    Thanks for all replies. I think it is very important to "solve" this classloader inheritance issue, especially in isolated mode. AFAIK in Apache Tomcat it is possible to "override" jars in the libs folder when deploying a war file. Why can't I do this in JBoss?


                    Because the servlet spec covers this exact behavior. In fact, in the spec, the classloader MUST look at WEB-INF/lib FIRST. I really don't understand why this is so dubiously defined in the Java EE spec.