Solution Pattern: Migrating from Red Hat Fuse to Red build of Apache Camel

See the Solution in Action

1. Demonstration

1.1. Watch a demonstration

You can see a migration based on the templates in action here.

1.2. Overview

The templates are available in the following repository: https://github.com/mthirion/fuse-to-camel3-camelk/tree/4.0

  • The repository has a branch specific to the current Red Hat Build of Apache Camel 4.

    repo branch

  • In each branch, the templates can be found in the "/templates" sub-directory.

  • The "templates" directory itself contains multiple subdirectories.

  • The breakdown is done on a per-component and per-runtime basis.

So, there is a subdirectory for each of the most used Camel components (REST API, SOAP, JMS…​) further divided per runtime (Quarkus and Spring Boot).

repo templates

Next, you can learn how to walkthrough this demo.

2. Run the demonstration

2.1. Before getting started

To try out, you’ll need Maven, and it’s best to have a Java 17 runtime.
Maven needs to have access to the repository "https://maven.repository.redhat.com/ga/" for the dependencies.
To make things easier, it’s better to use a visual Java IDE, such as Eclipse, VS Code…​

2.2. Setup

First, clone the repository:

$ git clone https://github.com/mthirion/fuse-to-camel3-camelk

Enter the repository and switch to the 4.0 branch.

$ cd fuse-to-camel3-camelk
$ git checkout 4.0

2.2.1. Getting the source application

The demoed example is a migration of a CXF-based REST API implemented using FUSE 6
The source code of that legacy application in the /fuse6-apps directory, under /rest/claimdemo
In the rest of the document, this location will be referred to as $SOURCE

$ SOURCE=./fuse6-apps/rest/claimdemo

You’ll need a Fuse 6 Fabric to deploy that application.
You can run one locally as follows:

$ docker pull weimeilin/fusefabric:naenablement
$ docker run -it -p 8181:8181 -p 8182:8182 -p 8184:8184 weimeilin/fusefabric:naenablement

This application listens to API calls at http://localhost:8182/cxf/status/status/custId/123

2.2.2. Identifying the target for the migration

The present document will describe how to migrate the application to the Camel Extension for Quarkus runtime.
We’ll therefore start with the template located in the templates/rest/ceq-xml-rest-app directory.
For the rest of the document, this location will be referred to as $TARGET

$ TARGET=./templates/rest/ceq-xml-rest-app

2.2.3. Preparing the OpenShift cluster

For th deployment to OpenShift, you’ll just need access to a namespace on an OpenShift cluster.
Here below, we’ll call this namespace 'claimdemo-migration'.

$ oc new-project claimdemo-migration

2.2.4. Preparing Camel K (optional)

If you want to test the migrated application as a serverless component, you’ll need an OpenShift server with Camel K installed.
Install the Red Hat Camel K Operator to your OpenShift cluster. Optionaly you can also deploy the OpenShift Serverless (Knative Serving and Knative Eventing) operators.

Make sure you also have the kamel CLI on your local machine, and of the same version as the Camel K Operator.
For clarity, we’ll use a separate namespace for Camel K-related artefacts. Let’s call it camel-migration.

$ oc new-project camel-migration

Custom beans such as custom Camel processors as considered by Camel K as external dependencies.
Those dependencies need to be made available to Camel K at deployment/build time.
The best way to do that is to use an external Maven repository, such as Nexus.
It can be deployed on or outside of OpenShift but needs to be reachable from it.

Find the prepared Maven settings file:

$ vim ./templates/camelk/script/settings.xml

Edit it, replacing the URL of th 2 repositories (nexus-camel and nexus-camel-snapshots) by the appropriate URL of your own Nexus server.

Create a ConfigMap to hold this settings.xml on OpenShift:

$ oc create cm camel-k-maven-settings --from-file ./templates/camelk/script/settings.xml -n camel-migration

Then run:

$ kamel install --force --maven-settings configmap:camel-k-maven-settings/settings.xml

2.3. Walkthrough guide

You’re ready to perform the migration.
To complete it, proceed to follow the below steps.

  1. Update Application properties

    1. Append the properties from $SOURCE/src/main/fabric8/com.redhat.demo.result.properties to the end of $TARGET/src/main/resources/application.properties

  2. Update Camel route

    1. Move the Camel <route> section from $SOURCE/src/main/resources/OSGI-INF/blueprint/blueprint.xml to $TARGET/src/main/resources/camel/MyQuarkusRoute.xml

    2. Replace the existing empty <route> section of the target file, and pay attention to only copy the <route> section and not the <CamelContext> nor anything else.

  3. Changes to Java and the Camel processors

    1. Copy the java packages from $SOURCE/src/main/java` to $TARGET/src/main/java

      $ cp -r $SOURCE/src/main/java $TARGET/src/main/
    2. You can optionally remove the Service interface, which is a class used specifically by the CXFRS framework, which is no longer the framework supporting REST API in Camel 3.

      $ rm $TARGET/src/main/java/org/blogdemo/claimdemo/StatusService.java
    3. Add @Named and @ApplicationScoped annotations to the Java classes that are used by Camel as custom beans or custom processors.

    4. In our example, there is one custom processor in the Camel route.
      It’s referenced by name as "claimProcessor", and correspond to the class org.blogdemo.claimdemo.ClaimProcessor.java.
      Therefore, add the below annotation to that Java class:

      import jakara.enterprise.context.ApplicationScoped;
      import jakarta.inject.Named;
      @Named("claimProcessor")
      @ApplicationScoped
      public class ClaimProcessor {...
  4. Camel REST DSL changes

    The Camel CXFRS component has been removed from Camel since v3. Camel now relies on the Camel REST component for the implementation of REST API endpoints. This provides separation of concerns between the REST interface and the REST implementation. It’s recommended to generate the REST interface from an OpenAPI specification.
    Camel ships a Maven plugin to automate the creation of the required code from the openAPI document.

    1. Copy the OpenAPI spec to the Maven project and run the Maven plugin.
      $ mkdir -p $TARGET/src/spec $ cp ./fuse6-apps/rest/openapi.yaml $TARGET/src/spec

      $ cd $TARGET
      $ mvn camel-restdsl-openapi:generate-xml
      $ cd -
    2. The XML code will be generated in the target/generated-rest-sources/restdsl-openapi/ folder, and needs to be copied in the "resources" folder. $ cp -f $TARGET/target/generated-rest-sources/restdsl-openapi/camel-rest.xml $TARGET/src/main/resources/camel-rest/rests.xml

  5. Switch from CXFRS to Camel REST

    1. The final stage is to adjust the <from> entry of the Camel route, replacing the 'cxfrs://' prefix with one that links the Camel route to the generated, separate Camel REST interface, which is actually done via the operationID (found in the openAPI specification document).

      # $TARGET/src/main/resources/camel/MyQuarkusRoute.xml
      <from id="_from4" uri="cxfrs:bean:statusEndpoint"/>
      >>
      <from id="_from4" uri="direct://getCustById"/>
  6. Final consideration upon CXFRS

    1. The CXFRS library, based on the CXF framework initially designed for SOAP, made use of the soap-related "operationName" header to identify the target Java method to call.
      This is not needed anymore, as the implementation now relies on the Camel REST library.

    2. To makes the application forward compatible to new clients, it’s best to get rid of that header constraints.
      An easy (quick & dirty) way to do that is by replacing the condition in the camel route as follows:

      <simple>${header.operationName} == "status"</simple>
      >>
      <simple> "status" == "status"</simple>

That’s it !
The application has already been migrated and is now a Camel 3 Quarkus application.
You can run it locally for validation with:

$ cd $TARGET
$ mvn clean package
$ mvn quarkus:dev
$ curl http://localhost:8182/cxf/status/custId/789
$ cd -

2.3.1. Running it on OpenShift

Thanks to the templates, the migrated application is already fully compatible with OpenShift and can safely run immediately in containers.
To run it and test it on OpenShift:

$ oc project claimdemo-migration
$ cd $TARGET
$ mvn clean package -Popenshift -Dquarkus.kubernetes.deploy=true -Dquarkus.kubernetes-client.trust-certs=true -Dquarkus.openshift.route.expose=true
$ ROUTE=`oc get route ceq-xml-rest-app --no-headers=true -n claimdemo-migration | awk '{print $2}'`
$ curl http://$ROUTE/cxf/status/custId/789
$ cd -

2.3.2. Turning the migrated application into a Camel K serverless function

The template makes use of the new IO XML format, which makes the migrated application immediately compatible with Camel K.

As mentioned, with Camel K, the Java dependencies (custom Camel processor) need to be made externally available, for example thanks to a Nexus repository
To do that, you can use the helpers found in the Camel K template directory, which will be referred to as $CAMELK

$ CAMELK=./templates/camelk

There are 3 elements to modify in the helper:

  1. Import of the Java library

    Copy the org.blogdemo.claimdemo.ClaimProcessor java class to the Camel K "javadependency" directory.

    $ mkdir -p $CAMELK/javadependency/src
    $ mkdir -p $CAMELK/javadependency/src/main
    $ mkdir -p $CAMELK/javadependency/src/main/java
    $ cp -r $TARGET/src/main/java $CAMELK/javadependency/src/main/java
  2. Upload the Java library to your Nexus

    To do that, you first need to edit the pom.xml and correct the URL of the Nexus repository from the <distributionManagement> section

    # $CAMELK/pom.xml
    <distributionManagement>
         <repository>
             <id>nexus-camel</id>
             <url>URL OF YOUR NEXUS REPOSITORY</url>
         </repository>
    </distributionManagement>

    You’ll have to make sure that you have permission to write to the Nexus repository.
    This means you will need to make an authenticated call to the Nexus server.
    Credentials information are located in the settings.xml linked to your local Nexus (not the one we used to create a ConfigMap).
    Your local file should contain a <server> entry with the exact same "id" as the one listed in the pom.xml

    <server>
      <id>nexus-camel</id>
      <username>admin</username>
      <password>****</password>
    </server>

    Once all set, run:

    $ cd $CAMELK/javadependency
    $ mvn deploy
    $ cd -
  3. Configure the Camel K beans registry

    The Camel processor bean must be referenced in the Camel registry. Edit the CamelBeans.java file in the following way:

    # $CAMELK/BeansBinding.java
    import org.blogdemo.claimdemo.*;
    @BindToRegistry("claimProcessor")
    public static ClaimProcessor camelbean() {
           return new ClaimProcessor();
       }

    You are ready to deploy the application as a Camel K Integration
    Simply run:

    $ oc project camel-migration
    $ kamel run --name camelk-migration \
       -d mvn:com.redhat.appfoundation.camelk.dependency:java-dependency:1.0.0 \
       -d camel-jackson -d camel-servlet \
       --open-api file:$TARGET/src/spec/openapi.yaml \
       --config file:$TARGET/src/main/resources/application.properties \
       --build-property file:$TARGET/src/main/resources/application.properties \
      $TARGET/src/main/resources/camel/MyQuarkusRoute.xml $CAMELK/BeansBinding.java
Entry configuration prefixes such as "%prod.", "%dev." etc might be misinterpreted by Camel K and lead to a deployment error.
Feel free to comment out those lines, that are not required by the Camel K framework.