Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Migrated to Confluence 5.3

Part 5

... Continued from Part 4

We continue from part 4 where we have the routing in place. However as you might have noticed we aren't quiet there yet with a nice solution, we are still coding to much. In this part we will look into to address these two concerns:

  • Starting Camel automatically
  • Using CXF

...

  • directly

Starting Camel automatically

Our current deployment model is as a war and we have the web.xml to help start things. Well in fact we will leverage Spring to start the world (wink). We use it's ContextListener

...

Code Block
xml
xml
    <camelContext id="camel" xmlns="http://activemq.apache.org/camel/schema/spring">
       ...
    </camelContext>

Adding route builder

Now we have Camel integrated but we still need to add our route bulder that we did manually from the javacode as:

...

There are two solutions to this

  • using spring bean
  • package scanning

Using a spring bean we just declare the route builder using a regular spring bean:

...

Code Block
xml
xml
    <camelContext id="camel" xmlns="http://activemq.apache.org/camel/schema/spring">
       <package>org.apache.camel.example.reportincident</package>
    </camelContext>

Using CXF directly

Now we have seen how you can leverage Spring to start Camel, in fact it handles the lifecycle of Camel, so you can say Camel is embedded with Spring in your application.

...

So now we have two spring XML files

  • cxf-config.xml
  • camel-config.xml

And since cxf-config.xml is dependent on camel-config.xml we need to have correct ordering in our web.xml where we have defined the XML files to load by Spring. So we set the camel-config.xml before the cxf-config.xml so Spring have created the SpringCamelContext and registered it in its registry with the id = camel.

...

Code Block
xml
xml
    <xs:element name="inputReportIncident">
        <xs:complexType name="inputReportIncident">

Using CXF endpoint

Okay now we are ready to turn our attention to using CXF directly in Camel routing. So we zap ReportIncidentEndpointImpl as we no longer need this code. So what's left is:

  • FilenameGenerator.java
  • ReportIncidentRoutes.java
    And that is all what's needed, well for now... (wink)

The idea now goal is to replace the endpoint the starts the ball going from previous staring endpoint ("direct:start") to the new starting CXF endpoint.

CXF endpoint can be configured in either or both CXF spring configuration file or/and in the route directly. It accepts one parameter and others are optional. The parameter it must have is the service class. The service class is the interface for the WSDL operation's. As we have the wsdl2java goal to generate this class for us, we have it already as org.apache.camel.example.reportincident.ReportIncidentEndpoint.

...

Code Block
java
java
    // return OK as response
    .transform(constant(OK));

Important issue regarding using CXF endpoints in Camel

Now we are nearly there, there is an important issue left with using CXF endpoints in Camel. In part 4 we started the route by sending the InputReportIncident object containing the webservice input. Now we are using CXF endpoints directly in our routing so its a CxfExchange that is created and passed in the routing. CxfExchange stores the payload in a CXF holder class org.apache.cxf.message.MessageContentsList. So to be able to get our InputReportIncident class we need to get this object from the holder class. For this we show how it's done in Java using a processor, then later we show a nicer solution.

...

Now the route is nice and simple.

Unit testing

...

Now lets turn our attention to unit testing it. From part 4 we have an unit test that is capable of exposing a webservice and send a test request and assert a mail is received. We will refactor this unit test to start up Camel, as it's Camel that should expose the webservice.

...

Code Block
java
java
/**
 * Unit test of our routes
 */
public class ReportIncidentRoutesTest extends TestCase {

    private CamelContext camel;

    // should be the same address as we have in our route
    private static String ADDRESS = "http://localhost:8080/part-five/webservices/incident";

    protected void startCamel() throws Exception {
    	camel = new DefaultCamelContext();
        camel.addRoutes(new ReportIncidentRoutes());
        camel.start();
    }

    protected static ReportIncidentEndpoint createCXFClient() {
        // we use CXF to create a client for us as its easier than JAXWS and works
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setServiceClass(ReportIncidentEndpoint.class);
        factory.setAddress(ADDRESS);
        return (ReportIncidentEndpoint) factory.create();
    }

    public void testRendportIncident() throws Exception {
        // start camel
        startCamel();

        // assert mailbox is empty before starting
        Mailbox inbox = Mailbox.get("incident@mycompany.com");
        assertEquals("Should not have mails", 0, inbox.size());

        // create input parameter
        InputReportIncident input = new InputReportIncident();
        input.setIncidentId("123");
        input.setIncidentDate("2008-08-18");
        input.setGivenName("Claus");
        input.setFamilyName("Ibsen");
        input.setSummary("Bla");
        input.setDetails("Bla bla");
        input.setEmail("davsclaus@apache.org");
        input.setPhone("0045 2962 7576");

        // create the webservice client and send the request
        ReportIncidentEndpoint client = createCXFClient();
        OutputReportIncident out = client.reportIncident(input);

        // assert we got a OK back
        assertEquals("0", out.getCode());

        // let some time pass to allow Camel to pickup the file and send it as an email
        Thread.sleep(3000);

        // assert mail box
        assertEquals("Should have got 1 mail", 1, inbox.size());

        // stop camel
        camel.stop();
    }
}

Conclusion

We have now seen how we have created a more much nicer solution leveraging Camel's powerful routing capabilities.

What we have here is routing logic with the help of the code comments could be understood by non developers. This is very powerful. In a later part we will look at some of the tools that Camel provides, for instance a tool to generate a nice diagrams of your routes (wink)

Code Block
java
java

    public void configure() throws Exception {
        // webservice response for OK
        OutputReportIncident OK = new OutputReportIncident();
        OK.setCode("0");

        // endpoint to our CXF webservice
        String cxfEndpoint = "cxf://http://localhost:8080/part-five/webservices/incident"
                + "?serviceClass=org.apache.camel.example.reportincident.ReportIncidentEndpoint"
                + "&wsdlURL=report_incident.wsdl";

        // first part from the webservice -> file backup
        from(cxfEndpoint)
            // we need to convert the CXF payload to InputReportIncident that FilenameGenerator and velocity expects
            .convertBodyTo(InputReportIncident.class)
            // then set the file name using the FilenameGenerator bean
            .setHeader(FileComponent.HEADER_FILE_NAME, BeanLanguage.bean(FilenameGenerator.class, "generateFilename"))
            // and create the mail body using velocity templating
            .to("velocity:MailBody.vm")
            // and store the file
            .to("file://target/subfolder")
            // return OK as response
            .transform(constant(OK));

        // second part from the file backup -> send email
        from("file://target/subfolder")
            // set the subject of the email
            .setHeader("subject", constant("new incident reported"))
            // send the email
            .to("smtp://someone@localhost?password=secret&to=incident@mycompany.com");
    }

In the next part's look at using XML to create the route instead of Java code. Then it might be even more readable by non developers.