Red Hat Training

A Red Hat training course is available for Red Hat Fuse

55.3. Generated Component Sub-Project

Overview

The Maven sub-project for building the new component is located under the camel-api-example/camel-api-example-component project directory. In this section, we take a closer look at the generated example code and describe how it works.

Providing the Java API in the component POM

The Java API must be provided as a dependency in the component POM. For example, the sample Java API is defined as a dependency in the component POM file, camel-api-example-component/pom.xml, as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <dependencies>
    ...
    <dependency>
      <groupId>org.jboss.fuse.example</groupId>
      <artifactId>camel-api-example-api</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    ...
  </dependencies>
  ...
</project>

Providing the Javadoc metadata in the component POM

If you are using Javadoc metadata for all or part of the Java API, you must provide the Javadoc as a dependency in the component POM. There are two things to note about this dependency:
  • The Maven coordinates for the Javadoc are almost the same as for the Java API, except that you must also specify a classifier element, as follows:
    <classifier>javadoc</classifier>
  • You must declare the Javadoc to have provided scope, as follows:
    <scope>provided</scope>
For example, in the component POM, the Javadoc dependency is defined as follows:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <dependencies>
    ...
    <!-- Component API javadoc in provided scope to read API signatures -->
    <dependency>
      <groupId>org.jboss.fuse.example</groupId>
      <artifactId>camel-api-example-api</artifactId>
      <version>1.0-SNAPSHOT</version>
      <classifier>javadoc</classifier>
      <scope>provided</scope>
    </dependency>
    ...
  </dependencies>
  ...
</project>

Defining the file metadata for Example File Hello

The metadata for ExampleFileHello is provided in a signature file. In general, this file must be created manually, but it has quite a simple format, which consists of a list of method signatures (one on each line). The example code provides the signature file, file-sig-api.txt, in the directory, camel-api-example-component/signatures, which has the following contents:
public String sayHi();
public String greetMe(String name);
public String greetUs(String name1, String name2);
For more details about the signature file format, see the section called “Signature file metadata”.

Configuring the API mapping

One of the key features of the API component framework is that it automatically generates the code to perform API mapping. That is, generating stub code that maps endpoint URIs to method invocations on the Java API. The basic inputs to the API mapping are: the Java API, the Javadoc metadata, and/or the signature file metadata.
The component that performs the API mapping is the camel-api-component-maven-plugin Maven plug-in, which is configured in the component POM. The following extract from the component POM shows how the camel-api-component-maven-plugin plug-in is configured:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  ...
  <build>
    <defaultGoal>install</defaultGoal>

    <plugins>
      ...
      <!-- generate Component source and test source -->
      <plugin>
        <groupId>org.apache.camel</groupId>
        <artifactId>camel-api-component-maven-plugin</artifactId>
        <executions>
          <execution>
            <id>generate-test-component-classes</id>
            <goals>
              <goal>fromApis</goal>
            </goals>
            <configuration>
              <apis>
                <api>
                  <apiName>hello-file</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleFileHello</proxyClass>
                  <fromSignatureFile>signatures/file-sig-api.txt</fromSignatureFile>
                </api>
                <api>
                  <apiName>hello-javadoc</apiName>
                  <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
                  <fromJavadoc/>
                </api>
              </apis>
            </configuration>
          </execution>
        </executions>
      </plugin>
      ...
    </plugins>
    ...
  </build>
  ...
</project>
The plug-in is configured by the configuration element, which contains a single apis child element to configure the classes of the Java API. Each API class is configured by an api element, as follows:
apiName
The API name is a short name for the API class and is used as the endpoint-prefix part of an endpoint URI.
Note
If the API consists of just a single Java class, you can leave the apiName element empty, so that the endpoint-prefix becomes redundant, and you can then specify the endpoint URI using the format shown in the section called “URI format for a single API class”.
proxyClass
The proxy class element specifies the fully-qualified name of the API class.
fromJavadoc
If the API class is accompanied by Javadoc metadata, you must indicate this by including the fromJavadoc element and the Javadoc itself must also be specified in the Maven file, as a provided dependency (see the section called “Providing the Javadoc metadata in the component POM”).
fromSignatureFile
If the API class is accompanied by signature file metadata, you must indicate this by including the fromSignatureFile element, where the content of this element specifies the location of the signature file.
Note
The signature files do not get included in the final package built by Maven, because these files are needed only at build time, not at run time.

Generated component implementation

The API component consists of the following core classes (which must be implemented for every Camel component), under the camel-api-example-component/src/main/java directory:
ExampleComponent
Represents the component itself. This class acts as a factory for endpoint instances (for example, instances of ExampleEndpoint).
ExampleEndpoint
Represents an endpoint URI. This class acts as a factory for consumer endpoints (for example, ExampleConsumer) and as a factory for producer endpoints (for example, ExampleProducer).
ExampleConsumer
Represents a concrete instance of a consumer endpoint, which is capable of consuming messages from the location specified in the endpoint URI.
ExampleProducer
Represents a concrete instance of a producer endpoint, which is capable of sending messages to the location specified in the endpoint URI.
ExampleConfiguration
Can be used to define endpoint URI options. The URI options defined by this configuration class are not tied to any specific API class. That is, you can combine these URI options with any of the API classes or methods. This can be useful, for example, if you need to declare username and password credentials in order to connect to the remote service. The primary purpose of the ExampleConfiguration class is to provide values for parameters required to instantiate API classes, or classes that implement API interfaces. For example, these could be constructor parameters, or parameter values for a factory method or class.
To implement a URI option, option, in this class, all that you need to do is implement the pair of accessor methods, getOption and setOption. The component framework automatically parses the endpoint URI and injects the option values at run time.

ExampleComponent class

The generated ExampleComponent class is defined as follows:
// Java
package org.jboss.fuse.example;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.util.component.AbstractApiComponent;

import org.jboss.fuse.example.internal.ExampleApiCollection;
import org.jboss.fuse.example.internal.ExampleApiName;

/**
 * Represents the component that manages {@link ExampleEndpoint}.
 */
@UriEndpoint(scheme = "example", consumerClass = ExampleConsumer.class, consumerPrefix = "consumer")
public class ExampleComponent extends AbstractApiComponent<ExampleApiName, ExampleConfiguration, ExampleApiCollection> {

    public ExampleComponent() {
        super(ExampleEndpoint.class, ExampleApiName.class, ExampleApiCollection.getCollection());
    }

    public ExampleComponent(CamelContext context) {
        super(context, ExampleEndpoint.class, ExampleApiName.class, ExampleApiCollection.getCollection());
    }

    @Override
    protected ExampleApiName getApiName(String apiNameStr) throws IllegalArgumentException {
        return ExampleApiName.fromValue(apiNameStr);
    }

    @Override
    protected Endpoint createEndpoint(String uri, String methodName, ExampleApiName apiName,
                                      ExampleConfiguration endpointConfiguration) {
        return new ExampleEndpoint(uri, this, apiName, methodName, endpointConfiguration);
    }
}
The important method in this class is createEndpoint, which creates new endpoint instances. Typically, you do not need to change any of the default code in the component class. If there are any other objects with the same life cycle as this component, however, you might want to make those objects available from the component class (for example, by adding a methods to create those objects or by injecting those objects into the component).

ExampleEndpoint class

The generated ExampleEndpoint class is defined as follows:
// Java
package org.jboss.fuse.example;

import java.util.Map;

import org.apache.camel.Consumer;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.spi.UriEndpoint;
import org.apache.camel.util.component.AbstractApiEndpoint;
import org.apache.camel.util.component.ApiMethod;
import org.apache.camel.util.component.ApiMethodPropertiesHelper;

import org.jboss.fuse.example.api.ExampleFileHello;
import org.jboss.fuse.example.api.ExampleJavadocHello;
import org.jboss.fuse.example.internal.ExampleApiCollection;
import org.jboss.fuse.example.internal.ExampleApiName;
import org.jboss.fuse.example.internal.ExampleConstants;
import org.jboss.fuse.example.internal.ExamplePropertiesHelper;

/**
 * Represents a Example endpoint.
 */
@UriEndpoint(scheme = "example", consumerClass = ExampleConsumer.class, consumerPrefix = "consumer")
public class ExampleEndpoint extends AbstractApiEndpoint<ExampleApiName, ExampleConfiguration> {

    // TODO create and manage API proxy
    private Object apiProxy;

    public ExampleEndpoint(String uri, ExampleComponent component,
                         ExampleApiName apiName, String methodName, ExampleConfiguration endpointConfiguration) {
        super(uri, component, apiName, methodName, ExampleApiCollection.getCollection().getHelper(apiName), endpointConfiguration);

    }

    public Producer createProducer() throws Exception {
        return new ExampleProducer(this);
    }

    public Consumer createConsumer(Processor processor) throws Exception {
        // make sure inBody is not set for consumers
        if (inBody != null) {
            throw new IllegalArgumentException("Option inBody is not supported for consumer endpoint");
        }
        final ExampleConsumer consumer = new ExampleConsumer(this, processor);
        // also set consumer.* properties
        configureConsumer(consumer);
        return consumer;
    }

    @Override
    protected ApiMethodPropertiesHelper<ExampleConfiguration> getPropertiesHelper() {
        return ExamplePropertiesHelper.getHelper();
    }

    protected String getThreadProfileName() {
        return ExampleConstants.THREAD_PROFILE_NAME;
    }

    @Override
    protected void afterConfigureProperties() {
        // TODO create API proxy, set connection properties, etc.
        switch (apiName) {
            case HELLO_FILE:
                apiProxy = new ExampleFileHello();
                break;
            case HELLO_JAVADOC:
                apiProxy = new ExampleJavadocHello();
                break;
            default:
                throw new IllegalArgumentException("Invalid API name " + apiName);
        }
    }

    @Override
    public Object getApiProxy(ApiMethod method, Map<String, Object> args) {
        return apiProxy;
    }
}
In the context of the API component framework, one of the key steps performed by the endpoint class is to create an API proxy. The API proxy is an instance from the target Java API, whose methods are invoked by the endpoint. Because a Java API typically consists of many classes, it is necessary to pick the appropriate API class, based on the endpoint-prefix appearing in the URI (recall that a URI has the general form, scheme://endpoint-prefix/endpoint).

ExampleConsumer class

The generated ExampleConsumer class is defined as follows:
// Java
package org.jboss.fuse.example;

import org.apache.camel.Processor;
import org.apache.camel.util.component.AbstractApiConsumer;

import org.jboss.fuse.example.internal.ExampleApiName;

/**
 * The Example consumer.
 */
public class ExampleConsumer extends AbstractApiConsumer<ExampleApiName, ExampleConfiguration> {

    public ExampleConsumer(ExampleEndpoint endpoint, Processor processor) {
        super(endpoint, processor);
    }

}

ExampleProducer class

The generated ExampleProducer class is defined as follows:
// Java
package org.jboss.fuse.example;

import org.apache.camel.util.component.AbstractApiProducer;

import org.jboss.fuse.example.internal.ExampleApiName;
import org.jboss.fuse.example.internal.ExamplePropertiesHelper;

/**
 * The Example producer.
 */
public class ExampleProducer extends AbstractApiProducer<ExampleApiName, ExampleConfiguration> {

    public ExampleProducer(ExampleEndpoint endpoint) {
        super(endpoint, ExamplePropertiesHelper.getHelper());
    }
}

ExampleConfiguration class

The generated ExampleConfiguration class is defined as follows:
// Java
package org.jboss.fuse.example;

import org.apache.camel.spi.UriParams;

/**
 * Component configuration for Example component.
 */
@UriParams
public class ExampleConfiguration {

    // TODO add component configuration properties
}
To add a URI option, option, to this class, define a field of the appropriate type, and implement a corresponding pair of accessor methods, getOption and setOption. The component framework automatically parses the endpoint URI and injects the option values at run time.
Note
This class is used to define general URI options, which can be combined with any API method. To define URI options tied to a specific API method, configure extra options in the API component Maven plug-in. See Section 56.7, “Extra Options” for details.

URI format

Recall the general format of an API component URI:
scheme://endpoint-prefix/endpoint?Option1=Value1&...&OptionN=ValueN
In general, a URI maps to a specific method invocation on the Java API. For example, suppose you want to invoke the API method, ExampleJavadocHello.greetMe("Jane Doe"), the URI would be constructed, as follows:
[scheme]
The API component scheme, as specified when you generated the code with the Maven archetype. In this case, the scheme is example.
[endpoint-prefix]
The API name, which maps to the API class defined by the camel-api-component-maven-plugin Maven plug-in configuration. For the ExampleJavadocHello class, the relevant configuration is:
<configuration>
  <apis>
    <api>
      <apiName>hello-javadoc</apiName>
      <proxyClass>org.jboss.fuse.example.api.ExampleJavadocHello</proxyClass>
      <fromJavadoc/>
    </api>
    ...
  </apis>
</configuration>
Which shows that the required endpoint-prefix is hello-javadoc.
[endpoint]
The endpoint maps to the method name, which is greetMe.
[Option1=Value1]
The URI options specify method parameters. The greetMe(String name) method takes the single parameter, name, which can be specified as name=Jane%20Doe. If you want to define default values for options, you can do this by overriding the interceptProperties method (see Section 55.4, “Programming Model”).
Putting together the pieces of the URI, we see that we can invoke ExampleJavadocHello.greetMe("Jane Doe") with the following URI:
example://hello-javadoc/greetMe?name=Jane%20Doe

Default component instance

In order to map the example URI scheme to the default component instance, the Maven archetype creates the following file under the camel-api-example-component sub-project:
src/main/resources/META-INF/services/org/apache/camel/component/example
This resource file is what enables the Camel core to identify the component associated with the example URI scheme. Whenever you use an example:// URI in a route, Camel searches the classpath to look for the corresponding example resource file. The example file has the following contents:
class=org.jboss.fuse.example.ExampleComponent
This enables the Camel core to create a default instance of the ExampleComponent component. The only time you would need to edit this file is if you refactor the name of the component class.