Announcement Announcement Module
Collapse
No announcement yet.
Upgrading M6 to R2 Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Upgrading M6 to R2

    Hi, I'm back again to SI.

    Since my last post here I had "stabilized" my work with SI M6, until I found what it seems a bug when calling methods thru the gateway that have no parameters.

    So I decided to upgrade to R2, to what my boss graciously give me one hour to do it. That's why I'm working on a Sunday on my own time...

    So apart from the classes that changed packages and schema configuration changes here are my *BIG* problems so far:

    1) As I said in other posts, I'm using SI and Jersey (JAX-RS) at the same time. Now unfortunately it seems that SI forgot other annotations other than theirs, so it gets confused when it finds Jersey annotations. I solve that problem by changing MethodParameterMessageMapper (note the *******)

    Code:
    		boolean hasHeadersAnnotation() {
    			if (this._hasHeadersAnnotation) {
    				return true;
    			}
    			if (this.getParameterAnnotations() == null) {
    				return false;
    			}
    			for (Object o : this.getParameterAnnotations()) {
    				if (Headers.class.isInstance(o)) {
    					Assert.isAssignable(Map.class, this.getParameterType(),
    							"parameter with the @Headers annotation must be assignable to java.util.Map");
    					this._hasHeadersAnnotation = true; *******
    					return true; *******
    				}
    				
    			}
    			return false;
    		}
    Now this seems to me like a bug that can cause problems in other situations besides mine.

    2) Since I'm using the Gateway to map in-messages to receiver-side methods, I need to invoke methods with several params. But SI doesen't allow me to do it. I had to change again MethodParameterMessageMapper:

    Code:
    	private void initializeParameterMetadata() {
    		boolean foundMessageOrPayload = false;
    		Class<?>[] paramTypes = this.method.getParameterTypes();			
    		this.parameterMetadata = new MethodParameterMetadata[paramTypes.length];
    		for (int i = 0; i < parameterMetadata.length; i++) {
    			MethodParameterMetadata metadata = new MethodParameterMetadata(this.method, i);
    			metadata.initParameterNameDiscovery(this.parameterNameDiscoverer);
    			GenericTypeResolver.resolveParameterType(metadata, this.method.getDeclaringClass());
    			if (metadata.getHeaderAnnotation() == null && !metadata.hasHeadersAnnotation()) {
    				// this is either a Message or the Object to be used as a Message payload
    		*******	//Assert.isTrue(!foundMessageOrPayload, "only one Message or payload parameter is allowed");
    				foundMessageOrPayload = true;
    			}
    			parameterMetadata[i] = metadata;
    		}
    	}
    3) Now in my M6 Gateway I was relying on my own MessageCreator that was being injected in it. But now it seems that the MessageCreator is gone, and I didn't find a way to change the default InboundMessageMapper on, again, MethodParameterMessageMapper. So I've made a very ugly hack:

    Code:
    	public Message<?> toMessage(Object[] parameters) {
    		return new MyOldMessageCreator().createMessage(parameters);
           }
    Finally, after all this I found that my original problem still happens, and again it looks to me like a bug. Looking at this code from GatewayProxyFactoryBean.invokeGatewayMethod
    Code:
    		if (paramCount == 0 ) {
    			if (shouldReply) {
    				if (isReturnTypeMessage) {
    					return gateway.receive();
    				}
    				response = gateway.receive();
    			}
    		}
    		else {
    it seems that when a method has no params SI tries to *receive* the response method withou never *send* the request message, and thus it keeps waiting forever for the response. Since I have no time for more, I just made another very ugly hack
    Code:
    if (paramCount == 0 && false) {
    and like that it works.

    Now this solve my problems and allows me to have the latest R2, but of course it's nothing but bad workarounds. However I hope you guys can pick on this and make decent changes.

    I still have to test another of my M6 problems, the propagation of a receiving-side exception to the requester-side, but I'll leave that for tomorrow.

    I hope this findings are useful.

    Cheers.
    Last edited by amsmota; Dec 5th, 2008, 10:36 AM.

  • #2
    Thanks for posting all of these detailed explanations. I will go through each of these.

    For starters, on #1, can you explain what you've changed? I'm not understanding the problem there (the code with ***** looks the same).

    Thanks,
    Mark

    Comment


    • #3
      Hi Mark.

      It's the location of those two lines. In the original they are *outside* the if{} block, so they are executed every time even when the annotation is not a Header.

      Thanks for your quick review.

      Cheers.

      Comment


      • #4
        Ah, I see now. That is definitely a bug. I just added the issue here: http://jira.springframework.org/browse/INT-492

        ...and I just committed the change as well: https://fisheye.springframework.org/...ration?cs=1760

        Thank you for pointing this out.

        I will likely post a couple questions about your other two points later. Just one quick comment - the no-arg methods for a Gateway proxy are intended to be for receiving a Message. Can you describe what you are doing there?

        Thanks again,
        Mark

        Comment


        • #5
          Well, as I said elsewhere my use of SI is to provide a transparent service infrastructure that allow developers of services to write code "as usual" and them just extract and annotate a interface.

          So they are free to write the code as they want, even code like

          Code:
          class DataAcessor(){
             public List getAllEmployees(){
                getHibernateTemplate().getDao().readEmployees();
             }
          }
          and (using Jersey annotations)
          Code:
          @Path("/dataservice")
          interface DataAcessor(){
          
             @Path("/dataservice/everybody")
             @GET
             List getAllEmployees();
          }
          so that the code can be accessed remotely, using a REST-like service, by send a GET request to a resource identified by

          Code:
          http://somehost/services/dataservice/everybody
          thru a connector that in this situation will be a HttpConnector (but can be other protocols as well).


          That's why I can have calls (requests) without parameters, I can't put any constraint on the "server" side implementation.

          Feel free to ask what you want, I'm glad I can help, I wish I could contribute more .

          Cheers.
          Last edited by amsmota; Nov 23rd, 2008, 04:58 PM.

          Comment


          • #6
            I think I understand what you are building there, but it might be better to provide a gateway layer of your own rather than relying on the GatewayProxyFactoryBean.

            The GatewayProxyFactoryBean is clearly focused on mapping a *single parameter* to a Message payload. The proxy methods may also accept additional parameters *if* annotated with @Header or @Headers. Those annotated parameters map to header values of the Message that is being created. And as you've discovered, any no-arg methods are intended for *receiving* Messages from a channel.

            It sounds like your use case might be better implemented with your own custom gateway layer that either builds upon the MessagingGateway hierarchy in Spring Integration or uses the MessageChannelTemplate directly.

            That said, I'm still interested in your thoughts - even if you completely disagree with my assessment here.

            Comment


            • #7
              Well, first let me stress this point here:

              3) Now in my M6 Gateway I was relying on my own MessageCreator that was being injected in it. But now it seems that the MessageCreator is gone, and I didn't find a way to change the default InboundMessageMapper on, again, MethodParameterMessageMapper. So I've made a very ugly hack:
              because this is a thing I really need and my hack is indeed a very ugly one.

              Now for your considerations, I really don't think it's a question of me "agreeing" or not, those are architectural decisions that is up to the "owners" of the project to make. I have my requirements and use cases, that are most probably different from other people, that's the way its is and it's a normal thing...

              But from a "higher" point of view, I don't see no reason to impose architectural constraints like the ones you mention. Even more when is so simple to overcome those limitations. Here are some thoughts:

              - Just create a @Response annotation so you can identify methods that are intended for *receiving* Messages from a channel. That way you can have even methods *with* parameters (like a correlation Id, for instance) going directly to peek the response channel.

              - I don't see any problem on passing a Object or a Object[] as a payload. You have to write very few lines of code to allow that. You can even create a explicit annotation to make it more explicit, like @MultiParam or something. That way one can easily proxy methods with several parameters, that I think is a very usual thing to do.

              - Or actually allow the injection of a custom InboundMessageMapper (like the old MessageCreator) that simply leaves to the developer the decision of using or not multi-params. Just remove the current constraints that makes the single-param compulsory.

              Now I've done all this myself (and it took me half a hour to do it once I understood what was made) but just for my restricted use-case, so it's not a general solution. And again, the architectural decisions of your project are not mine to make ...
              Last edited by amsmota; Nov 24th, 2008, 07:30 AM.

              Comment


              • #8
                Can you describe what you are doing with the no-param method that does create a Message? In general, I would expect the Message to contain something that maps from the inbound parameter. I guess in this case you have static Message content? Is there really a need to use Messaging for this use-case at all?

                One thing that is interesting about this project (or any framework for that matter) is that what one user finds very intuitive for their use-case might be very confusing to someone else. Also, we are trying to stress that there is a difference between messaging-based architectures (with loose coupling and a single "document" object) as opposed to more tightly-coupled, difficult to maintain RPC solutions (e.g. an RMI method with multiple parameters).

                To keep things simple, the general rule we have followed with the gateway interface is that a single parameter can be a payload or Message - any other parameters must map to the MessageHeaders with annotations indicating how to map them. If any further Message-creation is needed for a certain use case, then extending SimpleMessagingGateway is still pretty simple, and it accepts custom mappers in its constructor:
                Code:
                SimpleMessagingGateway(InboundMessageMapper<?> inboundMapper, OutboundMessageMapper<?> outboundMapper) { ... }
                However, I'm interested in hearing your answer to my first question above (no-arg method). I'm concerned to some degree that it may be a solution looking for a problem. Of course, I'm often proven wrong

                Regards,
                Mark

                Comment


                • #9
                  I think I already point my use. As a example I used a Class that works over Hibernate to access a DB.

                  Code:
                  public class EmployeesHibernateDAO{
                     Employee get EmployeeByName(String name){
                        ...
                     {
                     Employee get EmployeeById(Long id){
                        ...
                     {
                     List<Employee> getAllEmployees(){
                       ...
                     }
                  }
                  From here I extract a Interface that I annotate with Jersey (but could be any other kind of annotations, including my own)


                  Code:
                  public interface EmployeesHibernateDAO{
                     @GET
                     @Path("/data/employees/{name"})
                     Employee get EmployeeByName(String name);
                  
                     @GET
                     @Path("/data/employees/{id"})
                     Employee get EmployeeById(Long id);
                  
                     @GET
                     @Path("/data/employees)
                     List<Employee> getAllEmployees();
                  }
                  Now I just feed the Gateway with this interface and that is all I have to do to have a remote service requesting those paths to invoke the service. Now this has nothing to do with RMI, it's pure REST style, being the advantage the services being written as if it were to be invoked locally. No need to be concerned about REST or Messaging at all.

                  Now if you want to know what I'm doing internally on my SI based platform, is something like this:

                  On the requester side:

                  I have a Resource that has the Gateway (and thus the Interface) injected, parses the parameters and find which method of the interface should be called. Then it simply invoke

                  Code:
                  method.invoke(gateway, parms.toArray());
                  Since this is a method of the Interface that is wrapped within the Gateway, all the messaging stuff is done behind the scene and I finally have a Message with a array of parms (so, any number of parms, even if it's a empty array) sent thru SI to the InputChannel defined in the Gateway. With 0, 1 or more Objects as a payload.

                  On the receiving side:

                  I have a MessageHandler accepting Messages from the InputChannel. This one has the Implementation of the Interface that was injected in the Gateway injected into it. It simply takes the method to be invoked on the injected implementation (that was put in the MessageHeader by my old MessageCreator), get's the payload and simply calls the method and wraps the response in a Message before sent it to the Response channel.

                  Code:
                  	Object response = method.invoke(serviceImpl, objs);
                  	return new GenericMessage<Object>(response);
                  So this a solution that already found it's problem, my problem, and has I said it' already done with the changes I've made, so if you think these ideas don't apply to the overall design of SI, that's fine by me, I just use my implementation over the existing SI.

                  I have other questions that I'll write in another post, this one is already too long ...

                  Comment


                  • #10
                    My question is really why you are using a Spring Integration Message to invoke the no-arg method. It seems like the messaging layer is being injected unnecessarily. Isn't this just a REST service that invokes a DAO?

                    Comment


                    • #11
                      Originally posted by Mark Fisher View Post
                      (...)If any further Message-creation is needed for a certain use case, then extending SimpleMessagingGateway is still pretty simple, and it accepts custom mappers in its constructor:
                      Code:
                      SimpleMessagingGateway(InboundMessageMapper<?> inboundMapper, OutboundMessageMapper<?> outboundMapper) { ... }
                      Regards,
                      Mark
                      Now I don't know if this answers my question

                      3) Now in my M6 Gateway I was relying on my own MessageCreator that was being injected in it. But now it seems that the MessageCreator is gone, and I didn't find a way to change the default InboundMessageMapper on, again, MethodParameterMessageMapper. So I've made a very ugly hack:
                      but if it does, and assuming that the Gateway tag has a GatewayProxyFactoryBean behind (and that's what I want so I can have my interfaces wrapped within SI), how can I make the Gateway use that extended SimpleMessagingGateway ?

                      On another note, for what I could test until now the question of the Exceptions being propagated until the requester side is working correctly now.

                      Comment


                      • #12
                        Thanks for providing such a detailed explanation. I have two additional questions:

                        1) You said you create a Message "With 0, 1 or more Objects as a payload". Does this mean that the actual payload instance is an Object array?

                        2) If you are creating an "invoker" object for these send and receive calls...
                        Code:
                        method.invoke(gateway, parms.toArray());
                        
                        Object response = method.invoke(serviceImpl, objs);
                        return new GenericMessage<Object>(response);
                        ...I'm unclear what advantage the intermediate interface generated by GatewayProxyFactoryBean is providing. My first impression is that it's an extra layer. Why not just extend SimpleMessagingGateway?

                        If you can answer these two questions, then I think I would have a much better understanding of this problem. And, don't get me wrong, I am definitely trying to understand this so that I can make changes if necessary to the way the GatewayProxyFactoryBean works. Or, if we need to change something at a different layer we will do that. The fact that your "method.invoke" call looks very generic already just makes me wonder if there are 2 generic layers (yours and GatewayProxyFactoryBean) where 1 would suffice.

                        Thanks,
                        Mark

                        Comment


                        • #13
                          Originally posted by Mark Fisher View Post
                          My question is really why you are using a Spring Integration Message to invoke the no-arg method. It seems like the messaging layer is being injected unnecessarily. Isn't this just a REST service that invokes a DAO?
                          Because the correlation between request/service-to-be-invoked is bound by the correlation between Gateway/MessageHandler, and the respectively injected Interface/Implementation. And I need that to the methods that *do* have args. And also because the no-arg (can) return a Object that has to be returned.

                          Why would I want to implement different infrastructure for a arg and no-arg methods anyway, when in fact a no-arg method is just a specific case of multi-arg method that happens to have number-of-args = 0?

                          It's just like I learned somewhere, a Square is just a specific case of a Rectangle that happens to have both dimensions at the same value. You wouldn't calculate its area in a different way just because it's a square, it's still w*h .

                          Comment


                          • #14
                            I don't think the analogy of square/rectangle fits this example. Since we're talking about something vs. nothing, I would have expected you to argue that "square" is the same as "no square" with the only difference being that the 2nd one does not exist

                            Seriously, on that same note, I really do not understand this assertion:
                            in fact a no-arg method is just a specific case of multi-arg method that happens to have number-of-args = 0
                            From my perspective, Spring Integration is a documented-oriented Messaging framework where a Message cannot be empty. Therefore, if a method is going to generate a Message, it must have parameters. For that reason, if a method has a return value and no parameters, then that is treated as a "receive" call.

                            What is the *payload* of the Message you create for the no-arg method invocation?

                            Comment


                            • #15
                              Let me try to answer, but it's not always easy to make my point (and please remember I'm not a English native speaker).

                              Originally posted by Mark Fisher View Post
                              Thanks for providing such a detailed explanation. I have two additional questions:

                              1) You said you create a Message "With 0, 1 or more Objects as a payload". Does this mean that the actual payload instance is an Object array?
                              Yes. (uff, that was easy...)


                              Originally posted by Mark Fisher View Post

                              2) If you are creating an "invoker" object for these send and receive calls...
                              Code:
                              method.invoke(gateway, parms.toArray());
                              
                              Object response = method.invoke(serviceImpl, objs);
                              return new GenericMessage<Object>(response);
                              ...I'm unclear what advantage the intermediate interface generated by GatewayProxyFactoryBean is providing. My first impression is that it's an extra layer. Why not just extend SimpleMessagingGateway?
                              Well, maybe I'm wrong but it seems that is the GatewayProxyFactoryBean that actually "wraps" the interface with the SI stuff, and that is what I want so I don't have to explicitly send the messages myself.

                              Please note that

                              Code:
                              method.invoke(gateway, parms.toArray());
                              and

                              Code:
                              Object response = method.invoke(serviceImpl, objs);
                              return new GenericMessage<Object>(response);
                              are in different objects, the first in the requester(or sender) and the other on the receiver. When a "method.invoke(gateway, parms.toArray());" occurs, what is really happening is that SI does it's magic, with a little help from my MessageCreator that only adds the "method" to the Message Header so it can be invoked on the "handling" side. My Resource actually don't deal with SI at all, it's all done behind the scenes by the injected Gateway.

                              On the other side, since the Message Handler has been configured with a input and output channel, very little is done directly with SI, I just extract the payload Object[] from the Message, extract the method to be invoked from the MessageHeader, invoke it with the params, and wrap the result in a GenericMessage that follows it's course to the output channel.

                              So to answer your question, the GatewayProxyFactoryBean is giving me a way of connecting to the SI infrastructure without me having to do it explicitly.

                              I hope I've made myself clear, but I doubt...

                              Comment

                              Working...
                              X