54.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.NoteIf the API consists of just a single Java class, you can leave theapiName
element empty, so that theendpoint-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 aprovided
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.NoteThe 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
andsetOption
. 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 55.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 theExampleJavadocHello
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 requiredendpoint-prefix
ishello-javadoc
. - [endpoint]
- The
endpoint
maps to the method name, which isgreetMe
. - [Option1=Value1]
- The URI options specify method parameters. The
greetMe(String name)
method takes the single parameter,name
, which can be specified asname=Jane%20Doe
. If you want to define default values for options, you can do this by overriding theinterceptProperties
method (see Section 54.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.