11 Replies Latest reply on Mar 23, 2011 12:52 PM by joshwa

    Remoting not generating interface methods

    earnest.dyke

      Greetings all,


      I have the code listed below and for some reason I cannot determine it is NOT generating method stubs in javascript for the methods marked with @WebRemote. Any ideas why this would be happening?


      Earnie!




      <?xml version="1.0" encoding="UTF-8"?>
      <components xmlns="http://jboss.com/products/seam/components"
           xmlns:core="http://jboss.com/products/seam/core" xmlns:persistence="http://jboss.com/products/seam/persistence"
           xmlns:drools="http://jboss.com/products/seam/drools" xmlns:bpm="http://jboss.com/products/seam/bpm"
           xmlns:security="http://jboss.com/products/seam/security" xmlns:mail="http://jboss.com/products/seam/mail"
           xmlns:web="http://jboss.com/products/seam/web" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
                       http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.1.xsd
                       http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.1.xsd
                       http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.1.xsd
                       http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd
                       http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.1.xsd
                       http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.1.xsd
                       http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">
      
      
         <component name="org.jboss.seam.remoting.remoting">
            <property name="debug">true</property>
            <property name="pollTimeout">2</property>
            <property name="pollInterval">1</property>
          </component>
      
           <core:init debug="true" jndi-pattern="@jndiPattern@" />
      
           <core:manager concurrent-request-timeout="500"
                conversation-timeout="120000" conversation-id-parameter="cid"
                parent-conversation-id-parameter="pid" />
                
           
           <web:hot-deploy-filter url-pattern="*.seam" />
      
           <persistence:managed-persistence-context
                name="entityManager" auto-create="true"
                persistence-unit-jndi-name="java:/passTheTrashEntityManagerFactory" />
      
           <drools:rule-base name="securityRules">
                <drools:rule-files>
                     <value>/security.drl</value>
                </drools:rule-files>
           </drools:rule-base>
      
           <security:rule-based-permission-resolver
                security-rules="#{securityRules}" />
      
           <security:identity authenticate-method="#{authenticator.authenticate}"
                remember-me="true" />
      
           <event type="org.jboss.seam.security.notLoggedIn">
                <action execute="#{redirect.captureCurrentView}" />
           </event>
           <event type="org.jboss.seam.security.loginSuccessful">
                <action execute="#{redirect.returnToCapturedView}" />
           </event>
      
      </components>



      package com.x.y.session;
      
      import javax.ejb.Local;
      
      import com.x.y.entity.Game;
      import org.jboss.seam.annotations.remoting.WebRemote;
      
      @Local
      public interface GamePlay {
      
           @WebRemote
           String startNewGame();
      
           public void allDone();
      
           @WebRemote
           Game getGame();
      }
      



      package com.x.y.session;
      
      import static org.jboss.seam.ScopeType.CONVERSATION;
      
      import javax.ejb.Remove;
      import javax.ejb.Stateful;
      
      import org.jboss.seam.annotations.Destroy;
      import org.jboss.seam.annotations.Factory;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      
      import com.x.y.entity.Game;
      
      @Stateful
      @Name("gamePlayAction")
      @Scope(CONVERSATION)
      public class GamePlayAction implements GamePlay {
           private Game game;
      
           public Game getGame() {
                return game;
           }
      
           public String startNewGame() {
                game = new Game();
                return "gameBoard";
           }
      
           @Remove
           @Destroy
           public void allDone() {
      
           }
      
      
      }
      



      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <ui:composition xmlns="http://www.w3.org/1999/xhtml"
           xmlns:s="http://jboss.com/products/seam/taglib"
           xmlns:ui="http://java.sun.com/jsf/facelets"
           xmlns:f="http://java.sun.com/jsf/core"
           xmlns:h="http://java.sun.com/jsf/html"
           xmlns:rich="http://richfaces.org/rich" template="layout/template.xhtml">
      
           <ui:define name="body">
                <script type="text/javascript"
                     src="seam/resource/remoting/resource/remote.js" />
                <script type="text/javascript"
                     src="seam/resource/remoting/interface.js?gamePlayAction" />
                <script type="text/javascript" src="game.js" />
                <script type="text/javascript">
                startNewGame();
                </script>
                <rich:panel>
                     <h:panelGrid columns="3">
                          <s:div id="r1_c1">R1C1</s:div>
                          <s:div id="r1_c2">R1C2</s:div>
                          <s:div id="r1_c3">R1C3</s:div>
                          <s:div id="r2_c1">R2C1</s:div>
                          <s:div id="r2_c2">R2C2</s:div>
                          <s:div id="r2_c3">R2C3</s:div>
                          <s:div id="r3_c1">R3C1</s:div>
                          <s:div id="r3_c2">R3C2</s:div>
                          <s:div id="r3_c3">R3C3</s:div>
                     </h:panelGrid>
                </rich:panel>
      
           </ui:define>
      </ui:composition>




        • 1. Re: Remoting not generating interface methods
          sherkan777

          Try add myapp, where myapp is name of your project.



          <script type="text/javascript"
                         src="MYAPP/seam/resource/remoting/interface.js?gamePlayAction" />
          



          • 2. Re: Remoting not generating interface methods
            sherkan777

            sorry:



            <script type="text/javascript"
                           src="/MYAPP/seam/resource/remoting/interface.js?gamePlayAction" />
            





            • 3. Re: Remoting not generating interface methods
              sherkan777

              and also same for interface.

              • 4. Re: Remoting not generating interface methods
                earnest.dyke

                Thanks for the reply. It's not that I get an error from seam/resource/remoting/interface.js?gamePlayAction but I get only the following:


                Earnie!



                Seam.Remoting.type.gamePlayAction = function() {
                  this.__callback = new Object();
                }
                Seam.Remoting.type.gamePlayAction.__name = "gamePlayAction";
                
                Seam.Component.register(Seam.Remoting.type.gamePlayAction);




                • 5. Re: Remoting not generating interface methods
                  earnest.dyke

                  OK, I have tracked this down to the InterfanceGenerator.java does not see the @WebMethod on the methods on the interface and as such does not generate the necessary javascript code. Now the question is WHY does it not see the annotations. Using Java 1.6, Seam 2.1.1 and JBoss 5.0.1.


                  Earnie!

                  • 6. Re: Remoting not generating interface methods
                    daniele.ulrich

                    We had exactly the same problem (running on 4.2.3.GA/failing on 5.1.0.GA) and we found out that in the InterfaceGenerator.java the @WebMethod annotation was not recognized - in fact it was null. After having a closer look to our classpaths we found out that in the web application (.war) in WEB-INF/lib/ the jboss-seam-remoting.jar was deployed by error. As we put all seam libraries also in the .ear there was a class loading conflict: the remoting component resides in the ejb layer where as the InterfaceGenerator is called in the web layer (.war). A .war has another class loading policy and loads the classes first from /WEB-INF/lib and in java the same class loaded from different class loaders cannot be used correctly. It's a pity that the classloaders do not throw any exceptions in this case, the annotation array (getDeclaredAnnotations()) gets simply not set.
                    It seems to me that the classloader policies have changed in 4.2.3.GA and 5.1.0.GA, that's why it's behaving differently.


                    Cheers


                    bluebossa

                    • 7. Re: Remoting not generating interface methods
                      earnest.dyke

                      That was it! Works like a charm now. Thanks!


                      Earnie!

                      • 8. Re: Remoting not generating interface methods
                        joshwa
                        I am having the same issue with Seam 2.2.0.GA and JBoss-EAP 4.2. I have made sure there is only one copy of jboss-seam-remoting in my ear, but it occurs randomly every few restarts.

                        My structure looks like this:

                        + ear
                          + lib
                            - jboss-seam-remoting.jar
                            - ... etc
                          - jboss-seam.jar
                          + ejb.jar
                          + war
                            + WEB-INF
                              + lib
                                - jboss-seam-ui.jar
                                - jboss-seam-debug.jar
                                - jboss-seam-excel.jar
                                - jboss-seam-ioc.jar
                                - jboss-seam-jul.jar
                                - jboss-seam-mail.jar
                                - jboss-seam-pdf.jar
                                - jboss-seam-rss.jar

                        The only things that look remoting-related in my jboss/server/lib dir are:

                        jboss-monitoring.jar
                        jboss-remoting-int.jar
                        jboss-remoting.jar

                        The component in question has 5 methods annotated with @WebRemote, and none of them look abnormal.

                        It's very strange that restarting solves this issue-- and sometimes it takes a few restarts. It's almost as if JBoss is randomly loading classes in different orders?

                        I have gone through enabling TRACE logging on the JBoss UCL, but that wasn't particularly illuminating.

                        Does anyone have any additional thoughts on how I can figure out why this is happening?

                        • 9. Re: Remoting not generating interface methods
                          joshwa

                          For future reference, here's how I solved this problem:


                          First, I moved all the jboss-seam-* jars back into the ear. This had the effect of making the symptoms happen every time.


                          It turns out that the problem wasn't that InterfaceGenerator wasn't generating methods, but instead the problem was that it wasn't generating the required type registrations, making it impossible for the refs to be unmarshaled. This is because a previous developer had annotated all the Entity Beans with @Name annotations. InterfaceGenerator registers it as a component rather than a type. Our remoting methods were returning a Response containing several different packets POJOs rather than a single return type, so InterfaceGenerator wouldn't find the types there, either.


                          (It's my guess that prior to 2.2, this was handled slightly differently? Still not sure why it was happening intermittently with 2.2-- probably JBoss Classloader Hell.)


                          What I ended up doing was to create one big empty void method in each manager component that was needed for remoting, and included as parameters each of the types that would be returned in the Response and needed to be registered in order to be unmarshaled:



                          @WebRemote
                          public void generateTypeRegistrations(MyType1 a, MyType2 b, ...etc) {
                          }
                          



                          • 10. Re: Remoting not generating interface methods
                            joshwa

                            OK I know I said I solved this problem, but for reference I am still experiencing the problem.

                            • 11. Re: Remoting not generating interface methods
                              joshwa

                              (posted in response to a RH support ticket on this issue)


                              (funny list formatting since Seam Text doesn't support nested lists like textile or markdown does..)


                              A brief recap of how InterfaceGenerator works



                              InterfaceGenerator's purpose is to generate javascript methods and type definitions to facilitate remoting based on Seam components and entities. It includes generating proxy objects for each Seam Comnponent, as well as type definitions for the components, their properties, and their methods' return types and parameters so that the js can submit/respond to remoting calls using those types.


                              It is invoked by a html script tag that looks like this:


                              <script type="text/javascript"  src="#{contextPath}/seam/resource/remoting/interface.js?shipmentManager&amp;sampleManager"></script>
                              


                                     
                              or in JSF-land:


                              <s:remote include="shipmentManager,sampleManager" />
                              


                                     
                              Where the parameters are the names of Seam components.


                              InterfaceGenerator then inspects the components, and does a few different things in its generated javascript:



                              1. Creates a js object for each Component (component has a Name annotation - can be retrieved via Component.getInstance())

                              2. If the component is an ComponentType.ENTITY_BEAN, register a type definition so that the js can parse responses of that type

                              3. For each method in the component that has the WebRemote annotation, do the following:

                              4. - create a method on the Component's js object

                              5. - create a type definition for the method's return type

                              6. - create a type definition for each of the method's parameters



                              The InterfaceGenerator script tag above is in our application. In these two components, the relevant bits are listed below.


                              The Problem



                              When we deploy or bounce the application, InterfaceGenerator sometimes fails to generate the type definitions in these components. The lack of the Sample type definition (see the return types above) is the one that breaks our app. It often takes 8-15 bounces to get the interface to be generated. This is obviously a source of instability for us.


                              The Bug!



                              We have traced the problem to a bug in InterfaceGenerator.java.


                              InterfaceGenerator's main generateComponentInterface() method utilizes a cache:


                              private Map<String,byte[]> interfaceCache = new HashMap<String,byte[]>();
                              



                              This is to prevent InterfaceGenerator from generating the same interface repeatedly.


                              Further down inside generateComponentInterface() is:


                              Set<Type> types = new HashSet<Type>();
                              


                               
                              This contains a list of the types generated in the current run through generateComponentInterface(). Since InterfaceGenerator creates type/component interfaces for all fields, parameter types, and return types, we wouldn't want to return the same code twice if the type is referenced more than once in the same component or by multiple components. The problem lies in the interaction between these two caches.


                              Here is an example from our code: we have two components/types: SampleList, and Sample. SampleList contains a return type of Sample for one of its methods. In my html I've requested both components.


                              The code path that InterfaceGenerator uses looks roughly like this:



                              1. Look at the type Sample.

                              2. - Generate a type definition for Sample.

                              3. - Add sample to the types set.

                              4. - Insert the generated type definition into the interfaceCache under the key sample.

                              5. - Append the definition to the OutputStream.

                              6. Look at the type SampleList

                              7. - Generate a type definition for SampleList.

                              8. - Notice the return type Sample on a method

                              9. - Notice that Sample is already in the types set. Don't generate a new interface for it

                              10. - Add sampleList to the types set.

                              11. - Insert the generated type definition into the interfaceCache under the key sampleList.

                              12. - Append the definition to the OutputStream.

                              13. On any subsequent page, I can now request the Sample type and get the cached definition.



                              HOWEVER.


                              The order of loading the components when generating these interfaces is RANDOM.


                              Let's reverse the order and see what happens!



                              1. Look at the type SampleList

                              2. -  Generate a type definition for SampleList.

                              3. -  Add sampleList to the types set.

                              4. -  Notice the return type Sample on a method

                              5. -  Generate a type definition for Sample

                              6. -  Add sample to the types set.

                              7. -  Insert the generated type definition into the interfaceCache under the key sampleList.

                              8. -  Append the definition to the OutputStream.

                              9. Look at the type Sample.

                              10. -  Notice that Sample is already in the types set. Don't generate a new interface for it. Instead return null.

                              11. -  Insert the generated type definition (null) into the interfaceCache under the key sample.




                              1. On a subsequent page, request the definition for Sample, but NOT sampleList!

                              2. - Get the cached entry from interfaceCache for the key Sample.

                              3. - It's null! We get nothing back.

                              4. - Remoting can't parse Sample responses since the definitions aren't there.



                              Workaround:



                              In order to make sure the types are loaded before any 'enclosing' types are loaded, I put them on the login page. I bundled them all up into one class to simplify things. However, calls to Seam components don't work when the user hasn't logged in yet. To circumvent this, I made my proxy object a POJO, and used its fully qualified class name in the s:remote tag (com.mycompany.entity.response). Since this forces InterfaceGenerator to use the appendClassSource() method, which doesn't look at method parameter or return types, I made them public fields on the Response class.


                              When the user hits the login page right after the application is started, the interfaceCache will be populated with all the type definitions, under the com.mycompany.entity.response key. I then included the Response interface to the global layout template, so that all the type registrations will be present on every page, regardless of what other components are requested.



                              (an additional aside-- if you use the seam <s:remote /> tag twice in one page, it will return two links to remote.js, which resets all the component and type registrations! So in order to load just the components I needed on a particular page, I used a <ui:param /> tag on each indivudual page to dynamically append the required component names to the <s:remote> in the global layout. )



                              What Seam should do



                              In addition to including the enclosed type definitions in the interfaceCache entry for the enclosing type, it should create separate entries in the HashSet for each type, and return those definitions if they are explictly referenced (it would have to keep track of the enclosed types for each enclosing type in the HashSet so it knows what to include).