[rules-users] Sending requests to the Drools Execution Server from an external source

JeffMax jeffmax at gmail.com
Tue Oct 26 16:31:44 EDT 2010


I am just posting this to share my experience setting up the Drools Execution
Server for making requests from a non-Java based service (from the
perspective of a programmer without much Spring, CXF, or Camel experience).

The Drools Execution Server is packaged as a WAR file that can be run in any
Java servlet container (Tomcat/Jetty/JBOSS.) One important thing to know
about it right now is that as of version 5.1, most tutorials out there
except on the Drools Documentation are no longer correct. It used to use a
.properties file and a URL based on the name of the knowledge engine, this
is no longer true. 
I will try to explain how (with quite a bit of hand-waving and wild
guessing) how it works in this tutorial. I would welcome anyone that
understands how Camel and this integration truly works to correct or provide
more useful details on anything I mention here. 

First, to get it running on tomcat:

Unzip the war file and place it in the webapps directory of Tomcat. There is
one thing you have to check (at least for Tomcat):
1) In camel-server.xml, change the line that says "address="/kservice/rest""
to just "address="/rest"". The reason for this is that in the web.xml for
the server, kservice is used as the url pattern for  CXF servlet.:

<servlet>
		<servlet-name>CXFServlet</servlet-name>
		<display-name>CXF Servlet</display-name>
		<servlet-class>
			org.apache.cxf.transport.servlet.CXFServlet
		</servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>CXFServlet</servlet-name>
		<url-pattern>/kservice/*</url-pattern>
	</servlet-mapping>

If you leave this the way it is, to get to your services, you will need to
go to kservice/kservice/, which is a little redundant. 

With this being done, you should be able to go to
http://localhost:8080/drools-server/kservice/ in your browser and see a list
of services that are available.

The new version of Drools Server uses Apache CXF (a web framework that is an
implementation of JAX-RS), Spring, and Apache Camel. I do not fully
understand how it works, but the basics are that you use the Spring
Container which allows you to declaratively create your objects (or beans)
via xml configuration files (Spring is a huge framework, that was just an
over simplification of how it is being used in this context).  From there
you use CXF to expose a URL which will be used as the endpoint for your
service. Camel grabs the data from this endpoint, and forwards it your
drools session, and then returns the response back out.

Lets look at how this happens in the config (camel-server.xml):

The CXF tag looks like this:

  <cxf:rsServer id="rsServer"  
                address="/rest"
                serviceClass="org.drools.jax.rs.CommandExecutorImpl">
       <cxf:providers>
           <bean class="org.drools.jax.rs.CommandMessageBodyReader"/>
       </cxf:providers>
  </cxf:rsServer>  

We see here, it is exposing at /rest a web service that is serviced by the
class CommandExecuctorImpl (there is also that CommandMessageBodyReader, I
do not know exactly what it does)
A quick look into the source for CommandExecutorImpl, we see that it is
class that uses the CXF annotations (that is part of CXF, using annotations
to expose methods as web services) to expose 
the execute method at /execute, to consume "text/plain" (which was the
source of some of my trouble, see below) through an HTTP POST.  You can also
see that this method is not actually meant to be called because the only
thing that it does is raise an exception stating that fact.


public class CommandExecutorImpl implements CommandExecutor {
    
    public CommandExecutorImpl() {
        
    }
    
    public CommandExecutorImpl(boolean restFlag) {
        
    }       

    @POST()
    @Path("/execute")
    @Consumes("text/plain")
    @Produces("text/plain")       
    public <T> T execute(Command<T> command) {
        throw new UnsupportedOperationException( "This should never be
called, as it's handled by camel" );
    }
}

I believe this is where Camel comes in. Again in the Spring configuration
(camel-server.xml) we have:

  <!-- Leave this, as it's needed to make Camel "drools" aware -->
  <bean id="droolsPolicy" class="org.drools.camel.component.DroolsPolicy" />  
    
  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">    
    <!-- 
     ! Routes incoming messages from end point id="rsServer".
     ! Example route unmarshals the messages with xstream and executes
against ksession1.
     ! Copy and paste this element, changing marshallers and the 'to' uri,
to target different sessions, as needed.
     !-->
     
    <route>
       <from uri="cxfrs://bean://rsServer"/>
       <policy ref="droolsPolicy">
	       <unmarshal ref="xstream" />       
	       <to uri="drools:node1/ksession1" />
	       <marshal ref="xstream" />
       </policy>
    </route>    
        
  </camelContext>

We can see here that we are declaring a "route". Camel's job is to translate
and route data between things, and I believe here, it is somehow
intercepting the execute method, receiving the data it is supposed to
receive (the data you post to the server) and forwarding it to drools (note
the droolsPolicy bean declared right before the route). So here we are
declaring a route to take data from the CXF end point we just created, and
forward it to your drools knowledge session, which is declared in
knowledge-services.xml.

The sample that comes with the Execution Server creates a jsp that you call
from your browser that sends the command to the Execution Server (running
within the same conatainer.) The jsp uses a file called camel-client.xml to
do this. Camel-client.xml creates a bean that the jsp grabs (actually
Test.java, called from the jsp) that takes care of sending a POST to the CXF
endpoint described above.

  <camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
      
    <route>
       <from uri="direct://kservice"/>
       <policy ref="droolsPolicy"> 
               <to
uri="cxfrs://http://localhost:3333/drools-server/kservice/rest"/>
       </policy>
    </route>     
        
  </camelContext>

So test.jsp grabs this bean and passes it some xml it created and somehow
this sends a post to the CXF endpoint.

Here is the code from Test.java

public String send(String msg) {
        ClassPathXmlApplicationContext springContext = new
ClassPathXmlApplicationContext("classpath:camel-client.xml");
        String batch = "";
        batch += "<batch-execution lookup=\"ksession1\">\n";
        batch += "  <insert out-identifier=\"message\">\n";
        batch += "      <org.test.Message>\n";
        batch += "         <text>" + msg + "</text>\n";
        batch += "      </org.test.Message>\n";
        batch += "   </insert>\n";
        batch += "</batch-execution>\n";                
        
        
        Test test = new Test();
        String response = test.execute( batch, 
                                        ( CamelContext )
springContext.getBean( "camel" ) );
        
        return response;
    }
    
    public String execute(String msg, CamelContext camelContext) {                
               
        String response = camelContext.createProducerTemplate().requestBody(
"direct://kservice", msg, String.class );
  
        return response;
    }
}

It basically grabs the camel context, and uses that to send the xml to the
"direct//kservice route" declared in camel-client.xml, which sends the data
to the specified URL.
This is camel-client.xml creates what is called a "Camel Endpoint"

None of this camel-client.xml really matters for my task however. You can
delete test.jsp and camel-client.xml from the code, because they are only
used for sending requests from Java to Java (or at the very least, from two
languages that support using Camel). I want to send xml requests from
another server into the drools execution server. This only uses the
knowledge-services.xml and camel-server.xml configuration. The requests that
are sent in have to be commands as described at this link:

http://downloads.jboss.com/drools/docs/5.1.1.34858.FINAL/drools-integration/html/ch04.html#d0e702

So, if we look at the command that the test server was actually sending, it
is just:
<batch-execution lookup="ksession1"><insert
out-identifier="message"><org.test.Message><text>Hello
World</text></org.test.Message></insert></batch-execution>
 
This is the other place I ran into a problem. Following an online tutorial
(which as mentioned, is no longer completely accurate) at
http://www.lunatech-research.com/archives/2010/01/04/how-build-decision-service-using-jboss-rules-execution-server,
I was trying to use curl to send in the request. There are two important
things that are not immediately obvious
1) Camel knows it has to append /execute to the url describing the endpoint.
2) As I pointed out, the CommandExecutorImpl states it consumes and produces
"text/plain".

By default, curl --data will send a Content-Type header of
"application/x-www-form-urlencoded". This will not work. Also by default,
curl will not show the text of HTTP errors returned by the server. You need
to use the -v flag to get that. So, to do the exact same thing that the
test.jsp servlet does, but from an external source, you would do the
following:


curl  -v -H "Content-Type: text/plain" --data "<batch-execution
lookup=\"ksession1\"><insert
out-identifier=\"message\"><org.test.Message><text>HI</text></org.test.Message></insert></batch-execution>\n"
http://localhost:8080/drools-server/kservice/rest/execute 

Note, you have to add /execute to the URL.

More on this later as I try to do something more complicated.
-- 
View this message in context: http://drools-java-rules-engine.46999.n3.nabble.com/Sending-requests-to-the-Drools-Execution-Server-from-an-external-source-tp1776466p1776466.html
Sent from the Drools - User mailing list archive at Nabble.com.



More information about the rules-users mailing list