Chapter 9. Customizing Fuse Online
Fuse Online provides many connectors that you can use to connect to common applications and services. There are also a number of built-in steps for processing data in common ways. However, if Fuse Online does not provide a feature that you need, talk with a developer about your requirements. An experienced developer can help you customize integrations by providing any of the following:
An OpenAPI document that Fuse Online can use to create a connector for a REST API client.
You upload this schema to Fuse Online and Fuse Online creates a connector according to the schema. You then use the connector to create a connection that you can add to an integration. For example, many retail web sites provide a REST API client interface that a developer can capture in an OpenAPI document.
An OpenAPI document that defines a REST API service.
You upload this schema to Fuse Online. Fuse Online makes the API service available and provides the URL for API calls. This lets you trigger integration execution with an API call.
- A WSDL file that Fuse Online can use to create a connector for a SOAP client.
A
JAR
file that implements a Fuse Online extension. An extension can be any one of the following:- One or more steps that operate on integration data between connections
- A connector for an application or service
A library resource such as a JDBC driver for a proprietary SQL database
You upload this
JAR
file to Fuse Online and Fuse Online makes the custom feature provided by the extension available.
See the following topics for details:
9.1. Developing REST API client connectors
Fuse Online can create connectors for Representational State Transfer Application Programming Interfaces (REST APIs) that support Hypertext Transfer Protocol (HTTP). To do this, Fuse Online requires a valid OpenAPI 3 (or 2) document that describes a REST API that you want to connect to. If the API service provider does not make an OpenAPI document available then an experienced developer must create the OpenAPI document.
The following topics provide information and instructions for developing REST API connectors:
9.1.1. Requirements for REST API client connectors
After you upload an OpenAPI schema to Fuse Online, a connector to the REST API becomes available. You can select the connector to create a REST API client connection. You can then create a new integration and add the REST API client connection, or you can edit an existing integration to add the REST API client connection.
Fuse Online connectors support OAuth 2.0, HTTP basic authorization, and API keys. If access to the REST API requires Transport Layer Security (TLS) then the API needs to use a valid certificate that is issued by a recognized Certificate Authority (CA).
A REST API that uses OAuth must have an authorization URL that takes a client callback URL as input. After Fuse Online creates the connector and before you use the connector to create a connection, you must visit that URL to register your Fuse Online environment as a client of the REST API. This authorizes your Fuse Online environment to access the REST API. As part of registration, you provide the Fuse Online callback URL. The details for doing this are described in Connecting Fuse Online to Applications and Services, Registering Fuse Online as a REST API client.
For a REST API that uses OAuth, Fuse Online supports only the Authorization Code grant flow for obtaining authorization. In the OpenAPI document that you upload to create the connector, in the OAuth securityDefinitions
object, you must set the flow
attribute to accessCode
, for example:
securityDefinitions: OauthSecurity: type: oauth2 flow: accessCode authorizationUrl: 'https://oauth.simple.api/authorization' tokenUrl: 'https://oauth.simple.api/token'
You must not set flow
to implicit
, password
, or application
.
The OpenAPI schema for a REST API client connector cannot have cyclic schema references. For example, a JSON schema that specifies a request or response body cannot reference itself as a whole nor reference any part of itself through any number of intermediate schemas.
Fuse Online cannot create connectors for REST APIs that support the HTTP 2.0 protocol.
9.1.2. Guidelines for OpenAPI schemas for REST API client connectors
When Fuse Online creates a REST API client connector, it maps each resource operation in the OpenAPI document to a connection action. The action name and action description come from documentation in the OpenAPI document.
The more detail that the OpenAPI document provides, the more support Fuse Online can offer when connecting to the API. For example, the API definition is not required to declare data types for requests and responses. Without type declarations, Fuse Online defines the corresponding connection action as typeless. However, in an integration, you cannot add a data mapping step immediately before or immediately after an API connection that performs a typeless action.
One remedy for this is to add more information to the OpenAPI document. Identify the OpenAPI resource operations that will map to the actions you want the API connection to perform. In the OpenAPI document, ensure that there is a YAML or JSON schema that specifies each operation’s request and response types.
After you upload the schema, Fuse Online gives you an opportunity to review and edit it in API Designer, which is a visual editor for designing APIs based on the OpenAPI document. You can add more detail, save your updates, and Fuse Online creates an API client connector that incorporates your updates. After Fuse Online creates the client connector, you can no longer edit the OpenAPI document. To implement a change, you must create a new client connector.
If the OpenAPI document for the API declares support for application/json
content type and also application/xml
content type then the connector uses the JSON format. If the OpenAPI document specifies consumes
or produces
parameters that define both application/json
and application/xml
, then the connector uses the JSON format.
9.1.3. Provide client credentials in parameters
When Fuse Online tries to obtain authorization to access an OAuth2 application, it uses HTTP basic authentication to provide client credentials. If you need to, you can change this default behavior so that Fuse Online passes client credentials to the provider as parameters instead of using HTTP basic authentication. This affects the use of the tokenUrl
endpoint that is used to obtain an OAuth access token.
This is a Technology Preview feature.
To specify that Fuse Online should pass client credentials as parameters, in the securityDefinitions
section of the OpenAPI document, add the x-authorize-using-parameters
vendor extension with a setting of true
. In the example below, the last line specifies x-authorize-using-parameters
:
securityDefinitions: concur_oauth2: type: 'oauth2' flow: 'accessCode' authorizationUrl: 'https://example.com/oauth/authorize' tokenUrl: 'https://example.com/oauth/token' scopes: LIST: Access List API x-authorize-using-parameters: true
The setting of the x-authorize-using-parameters
vendor extension is true
or false
:
-
true
indicates that client credentials are in parameters. -
false
, the default, indicates that Fuse Online uses HTTP basic authentication to provide client credentials.
9.1.4. Automatically refresh access tokens
If an access token has an expiration date, then Fuse Online integrations that use that token to connect to an application stop running successfully when the token expires. To obtain a new access token, you must either reconnect to the application or re-register with the application.
If you need to, you can change this default behavior so that Fuse Online automatically requests a new access token in the following situations:
- When the expiration date has been reached.
- When HTTP response status codes that you specify are received.
This is a Technology Preview feature.
To specify that Fuse Online should automatically try to obtain a new access token in the situations described, in the securityDefinitions
section of the OpenAPI document, add the x-refresh-token-retry-statuses
vendor extension. The setting of this extension is a comma separated list that specifies HTTP response status codes. When an access token’s expiration date is reached or when Fuse Online receives a message from an OAuth2 provider and the message has one of these response status codes, then Fuse Online automatically tries to obtain a new access token. In the example below, the last line specifies x-refresh-token-retry-statuses
:
securityDefinitions: concur_oauth2: type: 'oauth2' flow: 'accessCode' authorizationUrl: 'https://example.com/oauth/authorize' tokenUrl: 'https://example.com/oauth/token' scopes: LIST: Access List API x-refresh-token-retry-statuses: 401,402,403
Sometimes, an API operation fails and a side effect of that failure is that the access token is refreshed. In this situation, even when obtaining a new access token is successful, the API operation still fails. In other words, Fuse Online does not retry the failed API operation after it receives the new access token.
9.2. Adding and managing API client connectors
Fuse Online can create these API client connectors:
- A REST API client connector from an OpenAPI document. For information about the content of the OpenAPI document, see Developing REST API client connectors.
- A SOAP API client connector from a WSDL file.
The following topics provide information and instructions for adding and managing REST API client connectors:
After you create an API client connector, for details about using that connector, see Connecting Fuse Online to Applications and Services, Connecting to API clients.
9.2.1. Creating REST API client connectors
Upload an OpenAPI document to enable Fuse Online to create a REST API client connector.
Prerequisite
You have an OpenAPI document for the connector that you want Fuse Online to create.
Procedure
- In the Fuse Online navigation panel, click Customizations > API Client Connectors. Any API client connectors that are already available are listed here.
- Click Create API Connector.
On the Create API Connector page, do one of the following:
- Click in the dotted-line box and select the OpenAPI file that you want to upload.
- Select Use a URL and paste the URL for the OpenAPI document in the input field.
Click Next. If there is invalid or missing content, Fuse Online displays information about what needs to be corrected. Select a different OpenAPI file to upload or click Cancel, revise the OpenAPI file, and upload the updated file.
If the schema is valid, Fuse Online displays a summary of the operations that the connector provides. This might include errors and warnings about the operation definitions.
If you are satisfied with the summary, click Next.
Or, to revise the OpenAPI document, click Review/Edit to open the API Designer editor. Update the schema as needed. For details about using the API editor, see Design and develop an API definition with API Designer. When you are done, Save your changes to incorporate your updates into the new API client connector. Then click Next to continue creating the API client connector.
Sometimes, if you provide a URL for the OpenAPI document, Fuse Online can upload it but cannot open it for editing. Typically, this is caused by settings on the file’s host. To open the schema for editing, Fuse Online requires that the file host has:
-
An
https
URL. (Anhttp
URL does not work.) - Enabled CORS.
-
An
Indicate the API’s security requirements. Fuse Online reads the OpenAPI definition to determine the information needed to configure the connector to meet the API’s security requirements. Fuse Online can display any of the following:
- No Security
- HTTP Basic Authorization — If the API service uses HTTP basic authorization, select this checkbox. Later, when you use this connector to create a connection, Fuse Online prompts you to enter a user name and password.
OAuth 2.0 — Fuse Online prompts you to enter:
- Authorization URL is the location for registering Fuse Online as a client of the API. Registration authorizes Fuse Online to access the API. See Connecting Fuse Online to Applications and Services, Registering Fuse Online as a REST API client. The OpenAPI document or other documentation for the API should specify this URL. If it does not then you must contact the service provider to obtain this URL.
- Access Token URL is required for OAuth authorization. Again, the OpenAPI document or other documentation for the API should provide this URL. If it does not then you must contact the service provider.
- API Key — If the API service requires an API key, Fuse Online prompts for any information that it needs to create the connector. Prompts are based on the OpenAPI definition. For example, you might need to indicate if the API key is in message headers or in query parameters. If the OpenAPI definition specifies API key security, as well as another security type, select the checkbox to indicate that you want to use API key security in connections based on this connector. Later, when you use this connector to create a connection, Fuse Online prompts you to enter the value of an API key.
Click Next. Fuse Online displays the connector’s name, description, host, and base URL as indicated by the OpenAPI document. For connections that you create from this connector,
-
Fuse Online concatenates the host and base URL values to define the endpoint for the connection. For example, if the host is
https://example.com
and the base URL is/api/v1
then the connection endpoint ishttps://example.com/api/v1
. - Fuse Online applies the OpenAPI document to data mapping steps. If the OpenAPI document supports more than one schema then Fuse Online uses the TLS (HTTPS) schema.
-
Fuse Online concatenates the host and base URL values to define the endpoint for the connection. For example, if the host is
- Review the connector details and optionally upload an icon for the connector. If you do not upload an icon, Fuse Online generates one. You can upload an icon at a later time. When Fuse Online displays the flow of an integration, it displays a connector’s icon to represent connections that are created from that connector.
To override a value obtained from the OpenAPI file, edit the field value that you want to change.
ImportantAfter Fuse Online creates a connector, you cannot change it. To effect a change, you need to upload an updated OpenAPI document so that Fuse Online can create a new connector or you can upload the same schema and then edit it in the API editor. You then continue the process for creating a new API client connector.
- When you are satisfied with the connector details, click Save. Fuse Online displays the new connector in the list of API Client Connectors.
Next step
For details about using your new API connector, see Connecting Fuse Online to Applications and Services, Connecting to API clients.
9.2.2. Creating SOAP API client connectors
Upload a WSDL file to enable Fuse Online to create a SOAP API client connector.
Both inline and external (WSDL URLs) support multiple schemas with unique namespaces.
Prerequisite
You have a WSDL file for the SOAP client connector that you want Fuse Online to create.
Procedure
- In the Fuse Online navigation panel, click Customizations > API Client Connectors. Any API client connectors that are already available are listed here.
- Click Create API Connector.
On the Create API Connector page, do one of the following:
Click in the dotted-line box and select the WSDL (
.wsdl
) file that you want to upload.Note that disk-based external schemas referenced in WSDL files that you directly import into the connector (by using the File Upload form) are not supported. Uploaded WSDL files must use inline schemas.
Select Use a URL and paste the URL for the WSDL (
.wsdl
) file in the input field.Note that URL-based WSDLs support external schemas hosted with the WSDLs. Also, only URL-based WSDLs support external schemas that are based on the relative URLs from the WSDL’s base path. The WSDL URL must be available to the SOAP connector at runtime for parsing and validation. Therefore, make sure that the WSDL and schemas are hosted at a permanent URL.
- Click Next.
- On the Specify service and port page, verify the service and port.
- Click Next. If there is invalid or missing content, Fuse Online displays information about what needs to be corrected. Select a different WSDL file to upload or click Cancel, revise the WSDL file, and then upload the updated file. If the schema is valid, Fuse Online displays a summary of the API definition (name and description) and a list of imported elements, such as the number of operations.
- Click Next.
Indicate the security requirements to use when invoking the WSDL endpoint. Fuse Online reads the API definition to determine the information needed to configure the connector to meet the API’s security requirements. Fuse Online can display any of the following:
- None (no security)
- HTTP Basic Authorization — If the API service uses HTTP basic authorization, select this checkbox. Later, when you use this connector to create a connection, Fuse Online prompts you to enter a user name and password.
WS-Security Username Token — Fuse Online prompts you for the following information:
- Timestamp - Select this option if you want Fuse Online to add a timestamp to the WS-Security header.
Password Type - Select
Digest
,Text
, orNone
.If you select
Text
orDigest
:- Specify your username and password.
- Select Username Token Nonce if you want Fuse Online to add a Nonce element to the WS-Security Username Token header.
- Select Username Token Created if you want Fuse Online to add a "Created" timestamp element to the WS-Security Username Token header.
Click Next. Fuse Online displays the connector’s name, description, and WSDL endpoint address.
Optionally, upload an icon for the connector. You can also upload an icon at a later time.
Note: For this release, If you do not upload an icon, Fuse Online does not generate one for you.
When Fuse Online displays the flow of an integration, it displays a connector’s icon to represent connections that are created from that connector.
- For Name, enter your choice of a name that helps you distinguish this connection from any other connections.
- Optionally, for Description, enter information that is helpful to know about this connection.
Review the connector details and to override a value obtained from the WSDL file, edit the field value that you want to change.
ImportantAfter Fuse Online creates a connector, you cannot change it. To effect a change, you need to upload an updated OpenAPI document so that Fuse Online can create a new connector or you can upload the same schema and then edit it in the API editor. You then continue the process for creating a new API client connector.
- When you are satisfied with the connector details, click Save. Fuse Online displays the new connector in the list of API Client Connectors.
Next step
For details about using your new API connector, see Connecting Fuse Online to Applications and Services, Connecting to API clients.
9.2.3. Updating API client connectors by creating new ones
When there is an update to an OpenAPI document or WSDL file from which you created an API client connector, and you want your API client connector to use those updates, you must create a new API client connector. You cannot directly update an API client connector. After you create the new API client connector, you use it to create a new connection and then you edit each integration that uses a connection that was created from the out-of-date connector.
Prerequisites
Be prepared to do one of the following:
For a REST API client connector:
- Upload the updated OpenAPI document.
- Upload the out-of-date schema again and update it in API Designer.
- For a SOAP API client connector, upload the updated WSDL file.
Procedure
Create a new API client connector based on the updated OpenAPI document or WSDL file. To easily distinguish between the old connector and the new connector, you might want to specify a version number in the connector name or the connector description.
- Create a new connection from the new connector. Again, you want to be able to easily distinguish between connections created from the old connector and connections created from the new connector. A version number in the connection name or connection description is helpful.
- Edit each integration that uses a connection that was created from the old connector by removing the old connection and adding the new connection.
- Publish each updated integration.
- Recommended, but not required: delete the old connector and the old connections.
9.2.4. Deleting API client connectors
You cannot delete a connector when there is a connection that was created from that connector and this connection is being used in an integration. After you delete an API client connector, you cannot use a connection that was created from that connector.
Procedure
- In the left panel, click Customizations > API Client Connectors.
- To the right of the name of the connector that you want to delete, click Delete.
- In the confirmation popup, if you sure that you want to delete the connector, click Delete.
9.3. Developing Fuse Online extensions
If Fuse Online does not provide a feature that is needed to create an integration, then an expert developer can code an extension that provides the needed behavior. The Syndesis extension repository, https://github.com/syndesisio/syndesis-extensions, contains examples of extensions.
A business integrator shares requirements with a developer who codes the extension. The developer provides a .jar
file that contains the extension. The business integrator uploads the .jar
file in Fuse Online to make the custom connector, custom step(s), or library resource available for use in Fuse Online.
The Fuse Tooling plugin to Red Hat Developer Studio provides a wizard that helps you develop a step extension or a connector extension. It is a matter of personal preference whether you choose to develop a step extension or a connector extension in Developer Studio or in some other IDE. For information about using the Developer Studio plugin, see Developing extensions for Fuse Online integrations.
In this document, the following topics outline the procedure, describe the requirements, and provide additional examples for developing extensions in an IDE that you choose.
- Section 9.3.1, “General procedure for developing extensions”
- Section 9.3.2, “Description of the kinds of extensions”
- Section 9.3.3, “Overview of extension content and structure”
- Section 9.3.4, “Requirements in an extension definition JSON file”
- Section 9.3.5, “Descriptions of user interface properties”
- Section 9.3.6, “Description of Maven plugin that supports extensions”
- Section 9.3.7, “How to specify data shapes in extensions”
- Section 9.3.8, “Examples of developing step extensions”
- Section 9.3.9, “Example of developing a connector extension”
- Section 9.3.10, “How to develop library extensions”
- Section 9.3.11, “Creating JDBC driver library extensions”
9.3.1. General procedure for developing extensions
Before you start to develop an extension, become familiar with the tasks that you will need to accomplish.
Prerequisites
An integration pod runs in a Java process with a flat classpath. To avoid version clashes, make sure that the dependencies that an extension uses are aligned with the imported bill of materials (BOM) from all of these sources:
-
org.springframework.boot:spring-boot-dependencies:$SPRING_BOOT_VERSION
-
org.apache.camel:camel-spring-boot-dependencies:$CAMEL_VERSION
-
io.syndesis.integration:integration-bom:$SYNDESIS_VERSION
If there are additional dependencies that are not part of the imported BOMs, you must:
-
Package them in the extension JAR file that is in the
lib
directory. -
Omit them from the
dependencies
property of the extension’s JSON descriptor file.
Procedure
- Obtain an understanding of what the extended feature must do. Talk to your business colleague to understand the feature requirements.
- Determine whether you need to develop a step extension, a connector extension, or a library extension.
- Set up the Maven project in which to develop the extension.
If you are developing a step extension:
-
Decide whether to implement it as a Camel route or implement it by using the Syndesis
Step
API. Information for the Syndesis API is at http://javadoc.io/doc/io.syndesis.extension/extension-api. -
If you choose to implement the extension as a Camel route, decide whether to implement XML fragments, a
RouteBuilder
class, or a bean. -
In your Maven project, specify the required metadata, such as the
schemaVersion
, extensionname
,extensionId
, and so on.
-
Decide whether to implement it as a Camel route or implement it by using the Syndesis
- Code the classes that implement the feature.
-
Add dependencies to the project’s
pom.xml
file. For connector and library extensions, and for step extensions that you implement in XML, create the JSON file that defines the extension.
For step extensions that you implement in Java, Maven can generate the JSON extension definition file for you when you specify corresponding data structure values in your Maven project.
- Run Maven to build the extension and create the extension’s JAR file.
- Test the extension by uploading the JAR file to a Fuse Online development environment.
- Provide the JAR file that packages the extension to your business colleague, who uploads it to a Fuse Online production environment. When you provide the JAR file, let your business colleague know about any configuration settings that require information beyond what appears in the Fuse Online web interface.
9.3.2. Description of the kinds of extensions
An extension defines one of the following:
- One or more custom steps that operate on integration data between connections. Each custom step performs one action. This is a step extension.
- A library resource that an integration runtime uses. For example, a library extension can provide a JDBC driver for connecting to a proprietary SQL database, such as Oracle.
A single custom connector for creating connections to a particular application or service that you want to integrate. This is a connector extension.
NoteFuse Online can use an OpenAPI document to create a connector for a REST API client. See Develop a REST API client connector.
A business integrator shares requirements with a developer who codes the extension. The developer provides a .jar
file that contains the extension. The business integrator uploads the .jar
file in Fuse Online to make the custom connector, custom step(s), or library resource available for use within Fuse Online.
An extension .jar
file that you upload to Fuse Online always contains exactly one extension.
For an example of uploading and using an extension that provides a step that operates on data between connections, see the AMQ to REST API sample integration tutorial.
9.3.3. Overview of extension content and structure
An extension is a collection of classes, dependencies, and resources that are packaged in a .jar
file.
Fuse Online uses Spring Boot to load an extension. Consequently, you must package an extension according to Spring Boot’s executable JAR format. For example, ensure that you use the ZipEntry.STORED()
method to save a nested JAR file.
The structure of a .jar
file that packages an extension is as follows:
extension.jar | +- META-INF | | | +- syndesis | | | +- syndesis-extension-definition.json 1 | +- mycompany | | | +-project | | | +-YourClasses.class 2 | +- lib 3 | +-dependency1.jar | +-dependency2.jar
- 1
- A JSON schema file that specifies the data structures that define the extension. This is referred to as the extension definition JSON file.
- 2
- The Java classes that implement the behavior that the extension provides.
- 3
- Additional dependencies that are required to build and execute the custom feature.
9.3.4. Requirements in an extension definition JSON file
Each extension must have a .json
file that defines the extension by specifying values for data structures such as name, description, supported actions, and dependencies. For each extension type, the following table indicates whether Maven can generate the extension definition JSON file and which data structures are required.
Extension Type | Maven Can Generate Extension Definition | Required Data Structures |
---|---|---|
Step extension in Java | Yes |
|
Step extension in XML | No |
|
Connector extension | No |
|
Library extension | No |
|
*While specification of dependencies
is not strictly required, in practice, there are almost always dependencies that you need to specify.
Typically, an extension definition file has the following layout:
{ "schemaVersion": "v1", "name": "", "description": "", "version": "", "extensionId": "", "extensionType": "", "properties": { }, "actions": [ ], "dependencies": [ ], }
- schemaVersion defines the version of the extension definition schema. Internally, Syndesis uses schemaVersion to determine how to map the extension definition to the internal model. This allows extensions that were developed against an old version of Syndesis to be deployed on newer versions of Syndesis.
- name is the name of the extension. When you upload an extension to Fuse Online, this name appears.
- description is any useful information that you want to specify. Fuse Online does not operate on this value.
- version is for your convenience to help you distinguish updates to an extension. Fuse Online does not operate on this value.
- extensionId defines a unique ID for the extension. This should be unique at least across a Syndesis environment.
extensionType indicates to Syndesis what the extension provides. As of Syndesis version 1.3, the following extension types are supported:
-
Steps
-
Connectors
-
Libraries
-
properties at the top level in a connector extension is required. It controls what Fuse Online displays when a Fuse Online user selects the connector to create a connection. This
properties
object contains a set of properties for each form control for creating a connection. For example:"myControlName": { "deprecated": true|false, "description": "", "displayName": "", "group": "", "kind": "", "label": "", "required": true|false, "secret": true|false, "javaType": "", "type": "", "defaultValue": "", "enum": { } }
In connector extensions, nested
properties
objects define HTML form controls for configuring connection actions. In step extensions, theactions
object contains aproperties
object. Theproperties
object defines a set of properties for each form control for configuring the step. See also: Descriptions of user interface properties.actions defines the operations that a connector can perform or the operation that a step between connections can perform. Only connector and step extensions use actions that you specify. The format for an action specification looks like this:
{ "id": "", "name": "", "description": "", "actionType": "step|connector", "descriptor": { } }
- id is a unique ID for the action. This should be unique at least within a Syndesis environment.
- name is the action name that appears in Fuse Online. An integrator sees this value as the name of a connection action or as the name of a step that operates on integration data between connections.
- description is the action description that appears in Fuse Online. Use this field to help the integrator understand what the action does.
- actionType indicates whether the action is performed by a connection or a step that is between connections.
-
descriptor specifies nested attributes such as
kind
,entrypoint
,inputDataType
,outputDatatype
and more.
dependencies defines the resources that this extension requires Fuse Online to provide.
Define a dependency as follows:
{ "type": "MAVEN", "id" : "org.apache.camel:camel-telegram:jar:2.21.0" }
- type indicates the type of the dependency. Specify MAVEN. (It is expected that other types will be supported in the future.)
- id is the ID of the Maven dependency, which is a Maven GAV.
9.3.5. Descriptions of user interface properties
In connector extensions and step extensions, specify user interface properties in the extension definition JSON file or in Java class files. The settings of these properties define the HTML form controls that Fuse Online displays when a Fuse Online user creates a connection, configures a connection action, or configures a step that is provided by the extension.
You must specify properties for each form control that you want to appear in the extension’s user interface in the Fuse Online console. For each form control, specify some or all properties in any order.
Example of user interface property specifications
In the JSON file that is part of the IRC connector, the top level properties
object defines the HTML form controls that appear after a Fuse Online user selects the IRC connector to create a connection. There are three sets of property definitions for three form controls: hostname
, password
, and port
:
"properties": { "hostname": { "description": "IRC Server hostname", "displayName": "Hostname", "labelHint": "Hostname of the IRC server to connect to", "order": "1", "required": true, "secret": false, "type": "string" }, "password": { "description": "IRC Server password", "displayName": "Password", "labelHint": "Required if IRC server requires it to join", "order": "3", "required": false, "secret": true, "type": "string" }, "port": { "description": "IRC Server port", "displayName": "Port", "labelHint": "Port of the IRC server to connect to", "order": "2", "required": true, "secret": false, "tags": [], "type": "int" } },
Based on these property specifications, when a Fuse Online user selects the IRC connector, Fuse Online displays the following dialog. After the user enters values in the two required fields and clicks Next, Fuse Online creates an IRC connection that is configured with the values that the Fuse Online user enters.
About properties
objects in extension definition JSON files
In a connector extension:
-
The toplevel
properties
object is required. It controls what Fuse Online displays when a Fuse Online user selects the connector to create a connection. Thisproperties
object contains a set of properties for each form control for creating a connection. -
In the
actions
object, there is aproperties
object for each action. In each of theseproperties
objects, there is a set of properties for each form control for configuring that action.
In a step extension, the actions
object contains a properties
object. The properties
object defines a set of properties for each form control for configuring the step. The JSON hierarchy looks like this:
"actions": [ { ... "propertyDefinitionSteps": [ { ... "properties": { "control-ONE": { "type": "string", "displayName": "Topic Name", "order": "2", ..., } "control-TWO": { "type": "boolean", "displayName": "Urgent", "order": "3", ... } "control-THREE": { "type": "textarea", "displayName": "Comment", "order": "1", ..., } } } ]
About user interface properties in Java files
To define user interface form controls in Java files, import io.syndesis.extension.api.annotations.ConfigurationProperty
in each class file that defines user configuration of a connection, action, or step. For each form control that you want the Fuse Online console to display, specify the @ConfigurationProperty
annotation, followed by a list of properties. For information about the properties that you can specify, see the user interface property reference table at the end of this section.
The following code shows property definitions for one form control. This code is in the example of developing a Camel route with RouteBuilder
:
public class LogAction extends RouteBuilder { @ConfigurationProperty( name = "prefix", description = "The Log body prefix message", displayName = "Log Prefix", type = "string")
The following code shows property definitions for two controls. This code is from the example of using the Syndesis Step API:
@Action(id = "split", name = "Split", description = "Split your exchange") public class SplitAction implements Step { @ConfigurationProperty( name = "language", displayName = "Language", description = "The language used for the expression") private String language; @ConfigurationProperty( name = "expression", displayName = "Expression", description = "The expression used to split the exchange private String language;
Descriptions of control form input types
In the set of properties for each HTML form control, the type
property defines the input type of the form control that Fuse Online displays. For details about HTML form input types, see https://www.w3schools.com/html/html_form_input_types.asp.
The following table lists the possible input types for Fuse Online form controls. In the set of properties for a control, if you specify a type
value that is unknown, Fuse Online displays an input field that accepts one line of text. That is, the default is "type": "text"
.
Value of type property | HTML | Fuse Online displays |
---|---|---|
|
| A checkbox that the user can select or not select. |
|
A custom control that lets the Fuse Online user select a unit of time: milliseconds, seconds, minutes, hours, or days. The user also enters a number and Fuse Online returns a number of milliseconds. For example: | |
|
| This field does not appear in the Fuse Online console. You can use other properties to specify data that is associated with this field, for example, textual data of some kind. While Fuse Online users cannot see or modify this data, if a user selects View Source for a Fuse Online page, hidden fields are visible in the source display. Therefore, do not use hidden fields for security purposes. |
|
| An input field that accepts a number. |
|
| An input field in which Fuse Online masks the characters that the user enters, typically with asterisks. |
|
A |
A drop-down list with an entry for each label/value pair that you specify in the form control’s |
|
| An input field that accepts one line of text. |
|
| A textarea element is used |
Descriptions of control form user interface properties
In a connector or step extension, for each HTML form control that appears in the Fuse Online console, you can specify one or more of the properties described in the following table. For details about HTML form input types, see https://www.w3schools.com/html/html_form_input_types.asp.
Property name | Type | Description |
---|---|---|
| string | Controls the kind of form control that Fuse Online displays. See the previous table for details. |
| number |
If set for a |
| string |
If set, the value is mapped to the HTML |
| array |
If the value of the |
|
Varies according to the value of the |
Fuse Online initially displays this value in the form field. The type of the setting of the |
| string | If set, Fuse Online displays this value below the form control. Typically, this is a short, useful message about the control. |
| string | Fuse Online displays this value. |
| array |
If set, Fuse Online overrides any setting for the |
| string |
If set, a |
| number |
If set for a |
| number |
If set for a |
| Boolean |
If set to |
| number |
Determines the order of controls in the Fuse Online console. Fuse Online applies ascending order, that is, the control that has |
| string | If set, Fuse Online displays this value in a hazed font in an input field to help the user understand the expected input. |
| Boolean |
Controls whether or not the |
| number |
If the value of the |
| Boolean |
If specified, Fuse Online changes the setting of the control’s |
9.3.6. Description of Maven plugin that supports extensions
The extension-maven-plugin
supports extension development by packaging the extension as a valid Spring Boot module. For step extensions that you implement in Java, this plugin can generate the extension definition JSON file.
In your Maven project’s pom.xml
file, add the following plugin declaration:
<plugin> <groupId>io.syndesis.extension</groupId> <artifactId>extension-maven-plugin</artifactId> <version>${syndesis.version}</version> <executions> <execution> <goals> <goal>generate-metadata</goal> <goal>repackage-extension</goal> </goals> </execution> </executions> </plugin>
The extension-maven-plugin
defines the following goals:
generate-metadata generates the JSON extension definition file that will be in the generated JAR file as follows:
Maven starts with the data structure specifications that are in the
META-INF/syndesis/syndesis-extension-definition.json
file, if there is one.If you are coding in XML, then you must define the extension definition JSON file yourself and it must specify all required data structures.
If you are developing a connector or library extension, then you must define the extension definition JSON file yourself and it must specify all required data structures.
If you are developing a step extension in Java, you can:
- Create the extension definition JSON file yourself.
- In your Java code, specify annotations that define all required data structures. You do not create an extension definition JSON file.
- Create an extension definition JSON file and specify some but not all data structures.
- For step extensions that you develop in Java, Maven obtains missing specifications from code annotations
-
Maven adds the dependencies list, which specifies dependencies that are provided with a scope of
provided
and that are managed through theextension-bom
.
repackage-extension packages the extension.
-
Dependencies and related transitive dependencies that are not managed through the
extension-bom
are in thelib
folder of the generated JAR. -
For library extensions, dependencies whose scope is
system
are in thelib
folder of the generated JAR.
-
Dependencies and related transitive dependencies that are not managed through the
For example, suppose your Maven project has the following pom.xml
file:
<?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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.company</groupId> <artifactId>my-extension</artifactId> <version>1.0.0</version> <name>MyExtension</name> <description>A Sample Extension</description> <packaging>jar</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>io.syndesis.extension</groupId> <artifactId>extension-bom</artifactId> <version>1.3.10</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>io.syndesis.extension</groupId> <artifactId>extension-api</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.github.lalyos</groupId> <artifactId>jfiglet</artifactId> <version>0.0.8</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.7.0</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>io.syndesis.extension</groupId> <artifactId>extension-maven-plugin</artifactId> <version>1.3.10</version> <executions> <execution> <goals> <goal>generate-metadata</goal> <goal>repackage-extension</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>
Based on this pom.xml
file, the generated extension definition JSON file looks like this:
{ "name": "MyExtension", "description": "A Sample Extension", "extensionId": "com.company:my-extension", "version": "1.0.0", "dependencies": [ { "type": "MAVEN", "id": "io.syndesis.extension:extension-api:jar:1.3.10" } ], "extensionType": "Libraries", "schemaVersion": "v1" }
The generated archive has this structure and content:
my-extension-1.0.0.jar | +- lib | | | + jfiglet-0.0.8.jar | +- META-INF | +- MANIFEST.MF | +- syndesis | +- syndesis-extension-definition.json
9.3.7. How to specify data shapes in extensions
A data shape holds data type metadata for use by the data mapper. The data mapper transforms this metadata into internal documents that it uses to display the source and target data fields in the data mapper user interface. In an extension definition JSON file for a connector or for a custom step, each action specification defines an input data shape (inputDataShape
) and an output data shape (outputDataShape
).
When you are developing an extension, it is important to specify data shape properties that allow the data mapper to correctly handle and display the source and target fields. The following data shape properties affect data mapper behavior:
-
kind
-
type
-
specification
-
name
-
description
About the kind
property
The data shape kind
property is represented by the DataShapeKinds
enum. The possible values for the kind
property are:
any
indicates that the data type is not structured. For example, it might be a byte array or free format text. The data mapper ignores a data shape when itskind
property is set toany
. In other words, the data does not appear in the data mapper and therefore you cannot map any fields to or from this data.However, for a custom connector, when its
kind
property is set toany
, Fuse Online prompts you to specify input and/or output data types when you configure a connection that you have created from the custom connector. This happens when you add a connection to an integration. You can specify the kind of the data shape’s schema, an appropriate document for the kind of schema that you specify, and a name for the data type.-
none
indicates that there is no data type. For an input data shape, this indicates that the connection or step does not read data. For an output data shape, this indicates that the connection or step does not modify data. For example, when an input message body is being transferred to an output message body, setting thekind
property tonone
indicates that the data is only passing through. The data mapper ignores data shapes whenkind
is set tonone
. In other words, the data does not appear in the data mapper and therefore you cannot map any fields to or from this data. java
indicates that the data type is represented by a Java class. Follow the"kind": "java"
declaration by specifying a fully qualified class name for thetype
property. For example:"outputDataShape": { "kind": "java", "type": "org.apache.camel.component.telegram.model.IncomingMessage" },
json-schema
indicates that the data type is represented by a JSON schema. Whenkind
is set tojson-schema
, specify a JSON schema as the value of the data shape’sspecification
property. For example:"inputDataShape": { "description": "Person data", "kind": "json-schema", "name": "Person", "specification": "{\"$schema\":\"http://json-schema.org/draft-04/schema#\",\"title\":\"Person\",\"type\":\"object\",\"properties\":{\"firstName\":{...}}}" }
The code for the SAP Concur connector contains examples of data shapes that are specified by JSON schemas.
json-instance
indicates that the data type is represented by a JSON instance. Whenkind
is set tojson-instance
, specify a JSON instance as the value of the data shape’sspecification
property. For example:"inputDataShape": { "description": "Person data", "kind": "json-instance", "name": "Person", "specification": "{\"firstName\":\"John\",...}" }
xml-schema
indicates that the data type is represented by an XML schema. Whenkind
is set toxml-schema
, specify an XML Schema as the value of the data shape’sspecification
property. For example:"inputDataShape": { "description": "Person data", "kind": "xml-schema", "name": "Person", "specification": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">...</xs:schema>" }
xml-instance
indicates that the data type is represented by an XML instance. Whenkind
is set toxml-instance
, specify an XML instance as the value of the data shape’sspecification
property. For example:"inputDataShape": { "description": "Person data", "kind": "xml-instance", "name": "Person", "specification": "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><Person><firstName>Jane</firstName></Person>" }
csv-instance
indicates that the data type is represented by a CSV instance.Note Data Mapper support for CSV data is a Technology Preview feature only.
When
kind
is set tocsv-instance
, specify a CSV instance as the value of the data shape’sspecification
property. For example:"inputDataShape": { "description": "Person data", "kind": "csv-instance", "name": "Person", "specification": "John,Doe,120 Jefferson Street,Riverside, NJ, 08075" }
When
kind
is set tocsv-instance
, you can specify the followingboolean
(true/false
) parameters:Label Name Description Allow Duplicate Header Names
allowDuplicateHeaderNames
Allows duplicate names in the header line of CSV data.
Allow Missing Column Names
allowMissingColumnNames
Allows missing column names when parsing the header line of CSV data.
Comment Marker
commentMarker
Specifies the character that indicates the start of a comment line in the CSV data.
Delimiter
delimiter
Specifies the character that delimits the values (typically ";", "," or "\t") in the CSV data.
Escape
escape
Specifies the escape character for the CSV data.
First Record As Header
firstRecordAsHeader
Uses the first record in the CSV data as the header line.
Ignore Empty Lines
ignoreEmptyLines
Ignores any empty lines in the CSV data.
Ignore Header Case
ignoreHeaderCase
Ignores the letter case in the header line of the CSV data.
Ignore Surrounding Spaces
ignoreSurroundingSpaces
Ignores any space characters surrounding the CSV data.
Null String
nullString
Specifies the string to use when converting to and from null in the CSV data.
Skip Header Record
skipHeaderRecord
Skip the header record in the CSV data.
About the type
property
When the value of the kind
property is java
, the "kind": "java"
declaration is followed by a type
declaration that specifies a fully qualified Java class name. For example:
"outputDataShape": { "kind": "java", "type": "org.apache.camel.component.telegram.model.IncomingMessage" },
When the kind
property is set to anything other than java
then any setting for the type
property is ignored.
About the specification
property
The setting of the kind
property determines the setting of the specification
property, as shown in the following table.
kind property setting | specification property setting |
---|---|
| Java inspection result.
For each extension that you write in Java, use
As a reminder, for step extensions written in Java, |
| An actual JSON schema document. The setting cannot be a reference to a document and the JSON schema cannot point to other JSON schema documents by means of references. |
| An actual JSON document that contains example data. The data mapper derives the data types from the example data. The setting cannot be a reference to a document. |
| An actual XML schema document. The setting cannot be a reference to a document and the XML schema cannot point to other XML schema documents by means of references. |
| An actual XML instance document. The setting cannot be a reference to a document. |
| An actual CSV instance document. The setting cannot be a reference to a document. |
|
The |
|
The |
About the name
property
The data shape name
property specifies a human readable name for the data type. The data mapper displays this name in its user interface as the label for the data fields. In the following image, Person is an example of where you would see the value of the name
property.
This name also appears in data type indicators in the Fuse Online flow visualization.
About the description
property
The data shape description
property specifies text that appears as a tooltip when the cursor hovers over the data type name in the data mapper user interface.
9.3.8. Examples of developing step extensions
A step extension implements one or more custom steps. Each custom step implements one action for processing integration data between connections. The following examples demonstrate the alternatives for developing step extensions:
- Section 9.3.8.1, “Example of developing a Camel route with XML fragments”
-
Section 9.3.8.2, “Example of developing a Camel route with
RouteBuilder
” -
Section 9.3.8.3, “Example of developing a Camel route with
RouteBuilder
and Spring Boot” - Section 9.3.8.4, “Example of using a Camel bean”
- Section 9.3.8.5, “Example of using the Syndesis Step API”
Syndesis provides custom Java annotations that you can use in conjunction with the syndesis-extension-plugin
. When you implement a step extension or a connector extension in Java, you can specify annotations that enable Maven to add action definitions to the extension definition JSON file. To enable annotation processing, add the following dependency to your Maven project:
<dependency> <groupId>io.syndesis.extension</groupId> <artifactId>extension-annotation-processor</artifactId> <optional>true</optional> </dependency>
Because Spring Boot is the integration runtime, to inject beans into a Camel context, be sure to follow standard Spring Boot practices. For example, create an auto-configuration class and create beans there. However, the default behavior is that extension code is not subject to package scanning. Consequently, you must create and populate the META-INF/spring.factories
file in a step extension.
9.3.8.1. Example of developing a Camel route with XML fragments
To develop a custom step, you can implement the action as an XML fragment that is a Camel route that has an input such as direct
. The Syndesis runtime invokes this route in the same way that it invokes any other Camel route.
For example, suppose that you want to create a step that logs the body of a message with an optional prefix. The following XML defines a Camel route that does this.
<?xml version="1.0" encoding="UTF-8"?> <routes xmlns="http://camel.apache.org/schema/spring" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd"> <route id="log-body-with-prefix"> <from uri="direct:log"/> <choice> <when> <simple>${header.prefix} != ''</simple> <log message="${header.prefix} ${body}"/> </when> <otherwise> <log message="Output ${body}"/> </otherwise> </choice> </route> </routes>
When you develop an extension in XML, you must create the extension definition JSON file yourself. For this XML fragment, the src/main/resources/META-INF/syndesis/syndesis-extension-definition.json
file could define the action as follows:
{ "actionType": "step", "id": "log-body-with-prefix", "name": "Log body with prefix", "description": "A simple body log with a prefix", "descriptor": { "kind": "ENDPOINT", 1 "entrypoint": "direct:log", 2 "resource": "classpath:log-body-action.xml", 3 "inputDataShape": { "kind": "none" }, "outputDataShape": { "kind": "none" }, "propertyDefinitionSteps": [ { "description": "extension-properties", "name": "extension-properties", "properties": { 4 "prefix": { "componentProperty": false, "deprecated": false, "description": "The Log body prefix message", "displayName": "Log Prefix", "javaType": "String", "kind": "parameter", "required": false, "secret": false, "type": "string" } } } ] } }
- 1
- The type of the action is set to
ENDPOINT
. The runtime invokes a Camel endpoint to execute the action provided by this custom step. - 2
- The Camel endpoint to invoke is
direct:log
. This is thefrom
specification in the route. - 3
- This is the location of the XML fragment.
- 4
- These are the properties that the action defined in this custom step exposes to the integrator who will be adding this step to an integration. In Fuse Online, each value that the integrator specifies in the user interface gets mapped to a message header that has the same name as the property. In this example, the integrator will see one input field, with the Log Prefix display name. For more details, see Descriptions of user interface properties.
Syndesis does not support full Camel XML configuration. Syndesis supports only the <routes> tag.
9.3.8.2. Example of developing a Camel route with RouteBuilder
You can implement a custom step by developing an action as a Camel route with the support of the RouteBuilder
class. Such a route has an input such as direct
. Syndesis invokes this route in the same way that it invokes any other Camel route.
To implement the example that creates a step that logs the body of a message with an optional prefix, you can write something like this:
import org.apache.camel.builder.RouteBuilder; import io.syndesis.extension.api.annotations.Action; import io.syndesis.extension.api.annotations.ConfigurationProperty; @Action( 1 id = "log-body-with-prefix", name = "Log body with prefix", description = "A simple body log with a prefix", entrypoint = "direct:log") public class LogAction extends RouteBuilder { @ConfigurationProperty( 2 name = "prefix", description = "The Log body prefix message", displayName = "Log Prefix", type = "string") private String prefix; @Override public void configure() throws Exception { from("direct::start") 3 .choice() .when(simple("${header.prefix} != ''")) .log("${header.prefix} ${body}") .otherwise() .log("Output ${body}") .endChoice(); } }
- 1
- The
@Action
annotation indicates the action definition. - 2
- The
@ConfigurationProperty
annotation indicates definitions of user interface form controls. For details, see Descriptions of user interface properties. - 3
- This is the action implementation.
This Java code uses Syndesis annotations, which means that the extension-maven-plugin
can automatically generate the action definition. In the extension definition JSON file, the action definition will look like this:
{ "id": "log-body-with-prefix", "name": "Log body with prefix", "description": "A simple body log with a prefix", "descriptor": { "kind": "ENDPOINT", 1 "entrypoint": "direct:log", 2 "resource": "class:io.syndesis.extension.log.LogAction", 3 "inputDataShape": { "kind": "none" }, "outputDataShape": { "kind": "none" }, "propertyDefinitionSteps": [ { "description": "extension-properties", "name": "extension-properties", "properties": { 4 "prefix": { "componentProperty": false, "deprecated": false, "description": "The Log body prefix message", "displayName": "Log Prefix", "javaType": "java.lang.String", "kind": "parameter", "required": false, "secret": false, "type": "string", "raw": false } } } ] }, "actionType": "step" }
- 1
- The type of action is
ENDPOINT
. The runtime invokes a Camel endpoint to execute the action that this step implements. - 2
- This is the Camel endpoint to invoke. It is the
from
specification in the route. - 3
- This is the class that implements
RoutesBuilder
. - 4
- These are the properties that the action defined in this custom step exposes to the integrator who will be adding this step to an integration. In Fuse Online, each value that the integrator specifies in the user interface gets mapped to a message header that has the same name as the property. In this example, the integrator will see one input field, with the Log Prefix display name. For more information, see Descriptions of user interface properties.
9.3.8.3. Example of developing a Camel route with RouteBuilder
and Spring Boot
You can implement a custom step by developing an action as a Camel route with the support of the RouteBuilder
class as well as Spring Boot. In this example, Spring Boot is the facility for registering a RouteBuilder
object in a Camel context. Syndesis invokes this route in the same way that it invokes any other Camel route.
To implement the example that creates a step that logs the body of a message with an optional prefix, you can write something like this:
import io.syndesis.extension.api.annotations.Action; import io.syndesis.extension.api.annotations.ConfigurationProperty; import org.apache.camel.builder.RouteBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ActionsConfiguration { @Action( 1 id = "log-body-with-prefix", name = "Log body with prefix", description = "A simple body log with a prefix", entrypoint = "direct:log") @ConfigurationProperty( 2 name = "prefix", description = "The Log body prefix message", displayName = "Log Prefix", type = "string") @Bean 3 public RouteBuilder logBodyWithprefix() { return new RouteBuilder() { @Override public void configure() throws Exception { from("direct::start") 4 .choice() .when(simple("${header.prefix} != ''")) .log("${header.prefix} ${body}") .otherwise() .log("Output ${body}") .endChoice(); } }; } }
- 1
- The
@Action
annotation indicates the action definition. - 2
- The
@ConfigurationProperty
annotation indicates definitions of user interface form controls. For details, see Descriptions of user interface properties. - 3
- Register the
RouteBuilder
object as a bean. - 4
- This is the action implementation.
This Java code uses Syndesis annotations, which means that the extension-maven-plugin
can automatically generate the action definition. In the extension definition JSON file, the action definition will look like this:
{ "id": "log-body-with-prefix", "name": "Log body with prefix", "description": "A simple body log with a prefix", "descriptor": { "kind": "ENDPOINT", 1 "entrypoint": "direct:log", 2 "inputDataShape": { "kind": "none" }, "outputDataShape": { "kind": "none" }, "propertyDefinitionSteps": [ { "description": "extension-properties", "name": "extension-properties", "properties": { 3 "prefix": { "componentProperty": false, "deprecated": false, "description": "The Log body prefix message", "displayName": "Log Prefix", "javaType": "java.lang.String", "kind": "parameter", "required": false, "secret": false, "type": "string", "raw": false } } } ] }, "actionType": "step" }
- 1
- The type of action is
ENDPOINT
. The runtime invokes a Camel endpoint to execute the action that this step implements. - 2
- This is the Camel endpoint to invoke. It is the
from
specification in the route. - 3
- These are the properties that the action defined in this custom step exposes to the integrator who will be adding this step to an integration. In Fuse Online, each value that the integrator specifies in the user interface gets mapped to a message header that has the same name as the property. In this example, the integrator will see one input field, with the Log Prefix display name. For more details, see Descriptions of user interface properties.
To make configuration classes discoverable by Spring Boot, you must list them in a file named META-INF/spring.factories
, for example:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.company.ActionsConfiguration
With Spring Boot, every bean that you eventually register in your configuration classes is available to the Camel context. For details, see the Spring Boot documentation for Creating your own auto-configuration.
9.3.8.4. Example of using a Camel bean
You can implement a custom step by developing an action as a Camel bean processor. To implement the example that creates a step that logs the body of a message with an optional prefix, you can write something like this:
import org.apache.camel.Body;
import org.apache.camel.Handler;
import org.apache.camel.Header;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.syndesis.extension.api.annotations.Action;
import io.syndesis.extension.api.annotations.ConfigurationProperty;
@Action(
id = "log-body-with-prefix",
name = "Log body with prefix",
description = "A simple body log with a prefix")
public class LogAction {
private static final Logger LOGGER = LoggerFactory.getLogger(LogAction.class);
@ConfigurationProperty(
name = "prefix",
description = "The Log body prefix message",
displayName = "Log Prefix",
type = "string")
private String prefix;
@Handler 1
public void process(@Header("prefix") String prefix, @Body Object body) {
if (prefix == null) {
LOGGER.info("Output {}", body);
} else {
LOGGER.info("{} {}", prefix, body);
}
}
}
- 1
- This is the function that implements the action.
This Java code uses Syndesis annotations, which means that the extension-maven-plugin
can automatically generate the action definition. In the extension definition JSON file, the action definition will look like this:
{ "id": "log-body-with-prefix", "name": "Log body with prefix", "description": "A simple body log with a prefix", "descriptor": { "kind": "BEAN", 1 "entrypoint": "io.syndesis.extension.log.LogAction::process", 2 "inputDataShape": { "kind": "none" }, "outputDataShape": { "kind": "none" }, "propertyDefinitionSteps": [ { "description": "extension-properties", "name": "extension-properties", "properties": { "prefix": { 3 "componentProperty": false, "deprecated": false, "description": "The Log body prefix message", "displayName": "Log Prefix", "javaType": "java.lang.String", "kind": "parameter", "required": false, "secret": false, "type": "string", "raw": false } } } ] }, "actionType": "step" }
- 1
- The type of the action is
BEAN
. The runtime invokes a Camel bean processor to execute the action in this custom step. - 2
- This is the Camel bean to invoke.
- 3
- These are the properties that the action defined in this custom step exposes to the integrator who will be adding this step to an integration. In Fuse Online, each value that the integrator specifies in the user interface gets mapped to a message header that has the same name as the property. In this example, the integrator will see one input field, with the Log Prefix display name. For more details, see Descriptions of user interface properties.
When you use beans, you might find it convenient to inject user properties into the bean instead of retrieving them from the exchange header. To do this, implement getter and setter methods for the properties that you want to get injected. The action implementation would look like this:
import org.apache.camel.Body; import org.apache.camel.Handler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.syndesis.extension.api.annotations.Action; import io.syndesis.extension.api.annotations.ConfigurationProperty; @Action( id = "log-body-with-prefix", name = "Log body with prefix", description = "A simple body log with a prefix") public class LogAction { private static final Logger LOGGER = LoggerFactory.getLogger(LogAction.class); @ConfigurationProperty( name = "prefix", description = "The Log body prefix message", displayName = "Log Prefix", type = "string") private String prefix; public void setPrefix(String prefix) { 1 this.prefix = prefix; } public String getPrefix() { 2 return prefix; } @Handler public void process(@Body Object body) { if (this.prefix == null) { LOGGER.info("Output {}", body); } else { LOGGER.info("{} {}", this.prefix, body); } } }
9.3.8.5. Example of using the Syndesis Step API
You can implement a custom step by using the Syndesis Step
API. This provides a way to interact with runtime route creation. You can use any method provided by the ProcessorDefinition
class and you can create more complex routes. Information for the Syndesis API is at http://javadoc.io/doc/io.syndesis.extension/extension-api.
Here is an example of a step extension that uses the Syndesis Step
API to implement a split action:
import java.util.Map;
import java.util.Optional;
import io.syndesis.extension.api.Step;
import io.syndesis.extension.api.annotations.Action;
import io.syndesis.extension.api.annotations.ConfigurationProperty;
import org.apache.camel.CamelContext;
import org.apache.camel.model.ProcessorDefinition;
import org.apache.camel.util.ObjectHelper;
import org.apache.camel.Expression;
import org.apache.camel.builder.Builder;
import org.apache.camel.processor.aggregate.AggregationStrategy;
import org.apache.camel.processor.aggregate.UseOriginalAggregationStrategy;
import org.apache.camel.spi.Language;
@Action(id = "split", name = "Split", description = "Split your exchange")
public class SplitAction implements Step {
@ConfigurationProperty(
name = "language",
displayName = "Language",
description = "The language used for the expression")
private String language;
@ConfigurationProperty(
name = "expression",
displayName = "Expression",
description = "The expression used to split the exchange")
private String expression;
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getExpression() {
return expression;
}
public void setExpression(String expression) {
this.expression = expression;
}
@Override
public Optional<ProcessorDefinition> configure(
CamelContext context,
ProcessorDefinition route,
Map<String, Object> parameters) { 1
String languageName = language;
String expressionDefinition = expression;
if (ObjectHelper.isEmpty(languageName) && ObjectHelper.isEmpty(expressionDefinition)) {
route = route.split(Builder.body());
} else if (ObjectHelper.isNotEmpty(expressionDefinition)) {
if (ObjectHelper.isEmpty(languageName)) {
languageName = "simple";
}
final Language splitLanguage = context.resolveLanguage(languageName);
final Expression splitExpression = splitLanguage.createExpression(expressionDefinition);
final AggregationStrategy aggreationStrategy = new UseOriginalAggregationStrategy(null, false);
route = route.split(splitExpression).aggregationStrategy(aggreationStrategy);
}
return Optional.of(route);
}
}
- 1
- This is the implementation of the action that the custom step performs.
This Java code uses Syndesis annotations, which means that the extension-maven-plugin
can automatically generate the action definition. In the extension definition JSON file, the action definition will look like this:
{ "id": "split", "name": "Split", "description": "Split your exchange", "descriptor": { "kind": "STEP", 1 "entrypoint": "io.syndesis.extension.split.SplitAction", 2 "inputDataShape": { "kind": "none" }, "outputDataShape": { "kind": "none" }, "propertyDefinitionSteps": [ { "description": "extension-properties", "name": "extension-properties", "properties": { "language": { "componentProperty": false, "deprecated": false, "description": "The language used for the expression", "displayName": "Language", "javaType": "java.lang.String", "kind": "parameter", "required": false, "secret": false, "type": "string", "raw": false }, "expression": { "componentProperty": false, "deprecated": false, "description": "The expression used to split the exchange", "displayName": "Expression", "javaType": "java.lang.String", "kind": "parameter", "required": false, "secret": false, "type": "string", "raw": false } } } ] }, "tags": [], "actionType": "step" }
Additional resource
For details about user interface properties, see Descriptions of user interface properties.
9.3.9. Example of developing a connector extension
If Fuse Online does not provide a connector for the application or service that you want to connect to in an integration, an experienced developer can code an extension that contributes a new connector to Fuse Online. This documentation provides an introduction to developing a connector extension. For details about developing a connector, see Developing Syndesis connectors on the Syndesis community site.
For connector extensions, it is not yet possible to automatically generate the extension definition JSON file from Java code.
A connector is essentially a proxy for a Camel component. A connector configures the underlying component and creates endpoints according to options that are defined in the extension definition and in user-supplied options that the Fuse Online web interface collects.
The connector extension definition extends the extension definition that is required for step extensions with the following additional data structures:
componentScheme
Defines the Camel component that the connector uses. You can set
componentScheme
for the connector or for actions. If you setcomponentScheme
for both the connector and an action, the setting for the action has precedence.connectorCustomizers
Specifies a list of classes that implement the ComponentProxyCustomizer class. Each class customizes the behavior of a connector. For example, a class might manipulate properties before they are applied to the underlying component/endpoint, or a class might add pre/post endpoint logic. For each class, specify the full class name of the implementation, for example,
com.mycomponent.MyCustomizer
. You can setconnectorCustomizers
on actions as well as connectors. According to what is set, Fuse Online applies customizers to the connector first and then to actions.connectorFactory
Defines the class that implements the ComponentProxyFactory class, which creates and/or configures the underlying component/endpoint. Specify the full class name of the implementation. You can set
connectorFactory
for the connector or for actions. Actions have precedence.
Customizer example
The following customizer example sets up a DataSource
from individual options:
public class DataSourceCustomizer implements ComponentProxyCustomizer, CamelContextAware { private final static Logger LOGGER = LoggerFactory.getLogger(DataSourceCustomizer.class); private CamelContext camelContext; @Override public void setCamelContext(CamelContext camelContext) { 1 this.camelContext = camelContext; } @Override public CamelContext getCamelContext() { 2 return this.camelContext; } @Override public void customize(ComponentProxyComponent component, Map<String, Object> options) { if (!options.containsKey("dataSource")) { if (options.containsKey("user") && options.containsKey("password") && options.containsKey("url")) { try { BasicDataSource ds = new BasicDataSource(); consumeOption(camelContext, options, "user", String.class, ds::setUsername); 3 consumeOption(camelContext, options, "password", String.class, ds::setPassword); 4 consumeOption(camelContext, options, "url", String.class, ds::setUrl); 5 options.put("dataSource", ds); } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) { throw new IllegalArgumentException(e); } } else { LOGGER.debug("Not enough information provided to set-up the DataSource"); } } } }
Example of injecting properties
If the customizer respects Java bean conventions, you can also inject the properties, as shown in this revision of the previous example:
public class DataSourceCustomizer implements ComponentProxyCustomizer, CamelContextAware { private final static Logger LOGGER = LoggerFactory.getLogger(DataSourceCustomizer.class); private CamelContext camelContext; private String userName; private String password; private String url; @Override public void setCamelContext(CamelContext camelContext) { 1 this.camelContext = camelContext; } @Override public CamelContext getCamelContext() { 2 return this.camelContext; } public void setUserName(String userName) { 3 this.userName = userName; } public String getUserName() { 4 return this.userName; } public void setPassword(String password) { 5 this.password = password; } public String getPassword() { 6 return this.password; } public void setUrl(String url) { 7 this.url = url; } public String getUrl() { 8 return this.url; } @Override public void customize(ComponentProxyComponent component, Map<String, Object> options) { if (!options.containsKey("dataSource")) { if (userName != null && password != null && url != null) { try { BasicDataSource ds = new BasicDataSource(); ds.setUserName(userName); ds.setPassword(password); ds.setUrl(url); options.put("dataSource", ds); } catch (@SuppressWarnings("PMD.AvoidCatchingGenericException") Exception e) { throw new IllegalArgumentException(e); } } else { LOGGER.debug("Not enough information provided to set-up the DataSource"); } } } }
- 1 2 3
- By implementing
CamelContextAware
, Syndesis injects the Camel context and then invokes the customize method. This sample code overrides thesetCamelContext()
andgetCamelContext()
methods, and sets the user name. - 4 5 6 7 8
- The sample code processes the injected options and automatically removes them from the options map.
Using a customizer to configure before/after logic
You can use a customizer to configure before/after logic as shown in this example:
public class AWSS3DeleteObjectCustomizer implements ComponentProxyCustomizer { private String filenameKey; public void setFilenameKey(String filenameKey) { this.filenameKey = filenameKey; } public String getFilenameKey() { return this.filenameKey; } @Override public void customize(ComponentProxyComponent component, Map<String, Object> options) { component.setBeforeProducer(this::beforeProducer); } public void beforeProducer(final Exchange exchange) throws IOException { exchange.getIn().setHeader(S3Constants.S3_OPERATION, S3Operations.deleteObject); if (filenameKey != null) { exchange.getIn().setHeader(S3Constants.KEY, filenameKey); } } }
Customizing behavior of ComponentProxyComponent
The ComponentProxyFactory class creates and/or configures the underlying component/endpoint. To customize the behavior of the ComponentProxyComponent object that ComponentProxyFactory
creates, you can override any of the following methods:
createDelegateComponent()
Syndesis invokes this method when the proxy starts and it is used to eventually create a dedicated instance of the component with the scheme defined by the
componentScheme
option.The default behavior of this method is to determine if any of the connector/action options applies at the component level. Only in the case that the same option cannot be applied at the endpoint, the method creates a custom component instance and configures it according to the applicable options.
configureDelegateComponent()
Syndesis invokes this method only if a custom component instance has been created to configure additional behavior of the delegated component instance.
createDelegateEndpoint()
Syndesis invokes this method when the proxy creates the endpoint and by default creates the endpoint by using Camel catalog facilities.
configureDelegateEndpoint()
After the delegated endpoint has been created, Syndesis invokes this method to configure additional behavior of the delegated endpoint instance, for example:
public class IrcComponentProxyFactory implements ComponentProxyFactory { @Override public ComponentProxyComponent newInstance(String componentId, String componentScheme) { return new ComponentProxyComponent(componentId, componentScheme) { @Override protected void configureDelegateEndpoint(ComponentDefinition definition, Endpoint endpoint, Map<String, Object> options) throws Exception { if (!(endpoint instanceof IrcEndpoint)) { throw new IllegalStateException("Endpoint should be of type IrcEndpoint"); } final IrcEndpoint ircEndpoint = (IrcEndpoint)endpoint; final String channels = (String)options.remove("channels"); if (ObjectHelper.isNotEmpty(channels)) { ircEndpoint.getConfiguration().setChannel( Arrays.asList(channels.split(",")) ); } } }; } }
9.3.10. How to develop library extensions
A library extension provides a resource that an integration requires at runtime. A library extension does not contribute steps or connectors to Fuse Online.
When you save an integration, you can optionally select one or more imported library extensions that you want to include with the integration.
A library extension does not define any actions. Here is a sample definition for a library extension:
{ "schemaVersion" : "v1", "name" : "Example Library Extension", "description" : "Syndesis Extension for adding a runtime library", "extensionId" : "io.syndesis.extensions:syndesis-library", "version" : "1.0.0", "tags" : [ "my-libraries-extension" ], "extensionType" : "Libraries" }
See also the sample library extension here: https://github.com/syndesisio/syndesis-extensions
Other than the lack of actions, the structure of a library extension is the same as the structure of a step or connector extension.
In a Maven project that creates a library extension, to add dependencies that are not available from a Maven repository, specify a system
dependency, for example:
<dependency> <groupId>com.company</groupId> <artifactId>my-library-extension</artifactId> <version>1.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/my-library-extension.jar</systemPath> </dependency>
9.3.11. Creating JDBC driver library extensions
To connect to a SQL database other than Apache Derby, MySQL, and PostgreSQL, you can create a library extension that wraps a JDBC driver for the database you want to connect to. After uploading this extension to Fuse Online, the Fuse Online-provided Database connector can access the driver to validate and create connections to the proprietary database. You do not create a new connector for your particular database.
The Syndesis open source community provides a project for creating an extension that wraps a JDBC driver.
Package one driver only in an extension. This makes it easier to manage the extension as part of managing your particular database. However, it is possible to create a library extension that wraps more than one driver.
Prerequisites
To use the Syndesis project, you must have a GitHub account.
Procedure
Ensure access to the JDBC driver for the database you want to connect to by doing one of the following:
- Confirm that the driver is in a Maven repository.
- Download the driver.
- In a browser tab, go to https://github.com/syndesisio/syndesis-extensions
-
Fork the
syndesis-extensions
repository to your GitHub account. - Create a local clone from your fork.
In your
syndesis-extensions
clone:-
If the driver is not in a Maven repository, copy the driver into the
syndesis-library-jdbc-driver/lib
folder. Edit the
syndesis-library-jdbc-driver/pom.xml
file:-
Update the value of the
Name
element to be a name that you choose for this extension. -
Update the value of the
Description
element to provide helpful information about this extension. -
If you have copied the driver into
syndesis-library-jdbc-driver/lib
ensure that thesystemPath
inpom.xml
points to that driver file. Optionally change thegroupId
,artifactId
andversion
to reflect proper values according to the driver. -
If the driver is in a Maven repository, ensure that a reference to that Maven dependency is in the
pom.xml
file. -
Examine the rest of the content of the
pom.xml
file and change any relevant metadata as needed.
-
Update the value of the
-
Execute
./mvnw -pl :syndesis-library-jdbc-driver clean package
to build the extension.
-
If the driver is not in a Maven repository, copy the driver into the
The generated .jar
file is in the syndesis-library-jdbc-driver/target
folder. Import this .jar
file as an extension in Fuse Online.
After you import a library extension, when you save an integration in Fuse Online you can optionally select the imported library extension and associate it with the integration.
9.4. Adding and managing extensions
Extensions let you add customizations to Fuse Online so you can integrate applications the way you want to. After you start using customizations provided in extensions, you can identify the integrations that use those customizations. This is helpful to do before you update or delete an extension.
The following topics provide details:
9.4.1. Making custom features available
To make a custom feature available for use in an integration, upload the extension to Fuse Online.
Prerequisites
-
A developer has provided a
.jar
file that contains a Fuse Online extension.
Procedure
- In the left Fuse Online panel, click Customizations > Extensions.
- Click Import Extension.
Drag and drop, or choose, the
.jar
file that contains the extension that you want to upload.Fuse Online immediately tries to validate that the file contains an extension. If there is a problem, Fuse Online displays a message about the error. You must coordinate with the extension developer to obtain an updated
.jar
file, which you can then try to upload.Review the extension details.
After Fuse Online validates the file, it extracts and displays the extension’s name, ID, description, and type. The type indicates whether the extension defines a custom connector, or one or more custom steps for operating on data between connections, or a runtime library extension (including a JDBC driver).
For a connector extension, Fuse Online displays the actions that are available to a connection that is created from this custom connector. In the extension, the developer might have provided an icon that Fuse Online can use to represent the application connections created from this connector. While you do not see this icon in the extension details page, it appears when you create connections from the custom connector. If the extension developer did not provide an icon in the extension, then Fuse Online generates an icon.
For a step extension, Fuse Online displays the name of each custom step that the extension defines.
For a library extension, Fuse Online includes the imported Maven dependencies in the integration runtime classpath. You must ensure that the imported Maven dependencies do not conflict with other dependencies that are already used in the integration (including any other library extension, such as a JDBC driver).
- Click Import Extension. Fuse Online makes the custom connector or custom step(s) available and displays the extension’s details page.
9.4.2. Identifying integrations that use extensions
Before you update or delete an extension, you should identify the integrations that use customizations that are provided by that extension.
Procedure
- In the left Fuse Online panel, click Customizations > Extensions.
- In the list of extensions, find the entry for the extension that you want to update or delete and click its Details button.
Result
Fuse Online displays details about the extension including a list of any integrations that use a customization provided by the extension.
9.4.3. Updating extensions
When a developer updates an extension, you can upload the updated .jar
file to implement the updates in your integrations.
Prerequisite
A developer has provided you with an updated .jar
file for an extension that you previously uploaded.
Procedure
- In Fuse Online, in the left panel, click Customizations > Extensions.
- At the right of the entry for the extension that you want to update, click Update.
-
Click in the dotted-line box to navigate to and select the updated
.jar
file, and click Open. - Confirm that the extension details are correct and click Import Extension.
- In the details page for the updated extension, determine which integrations use the connector or custom step(s) defined in the extension.
It is up to you to know exactly what is required to update each integration that uses a custom connector or a custom step from the updated extension. At the very least, you must republish each integration that uses a customization defined in the updated extension.
In some cases, you might need to edit the integration to change or add configuration details for a customization. You must communicate with the extension developer to understand how to update integrations.
9.4.4. Deleting extensions
You can delete an extension even if a running integration uses a step that is provided by that extension or uses a connection that was created from a connector that was provided by that extension. After you delete an extension, you cannot start an integration that uses a customization that was provided by that extension.
Procedure
- In the left Fuse Online panel, click Customizations > Extensions.
- In the list of extensions, find the entry for the extension that you want to delete and click Delete, which appears at the right of the entry.
Results
There might be stopped or draft integrations that use a customization provided by an extension that you delete. To run one of these integrations, you will need to edit the integration to remove the customization.
Additional resources