Skip to content

Commit

Permalink
First version of Flowable Camel component
Browse files Browse the repository at this point in the history
  • Loading branch information
tijsrademakers committed Oct 10, 2024
1 parent 9f87ee8 commit 8ebbe5d
Show file tree
Hide file tree
Showing 38 changed files with 1,559 additions and 1 deletion.
5 changes: 5 additions & 0 deletions bom/camel-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,11 @@
<artifactId>camel-flink</artifactId>
<version>4.9.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-flowable</artifactId>
<version>4.9.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-fop</artifactId>
Expand Down
5 changes: 5 additions & 0 deletions catalog/camel-allcomponents/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,11 @@
<artifactId>camel-flink</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-flowable</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-fop</artifactId>
Expand Down
110 changes: 110 additions & 0 deletions components/camel-flowable/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<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">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.apache.camel</groupId>
<artifactId>components</artifactId>
<version>4.9.0-SNAPSHOT</version>
</parent>

<artifactId>camel-flowable</artifactId>
<packaging>jar</packaging>
<name>Camel :: Flowable</name>
<description>Camel Flowable support</description>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-spring</artifactId>
</dependency>

<!-- testing -->
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-test-spring-junit5</artifactId>
<scope>test</scope>
</dependency>

<!-- Flowable JARs -->
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-engine</artifactId>
<version>${flowable-version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-spring</artifactId>
<version>${flowable-version}</version>
</dependency>
<dependency>
<groupId>org.flowable</groupId>
<artifactId>flowable-event-registry-spring</artifactId>
<version>${flowable-version}</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${log4j2-version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>provided</scope>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<scope>provided</scope>
<version>${spring-version}</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<scope>test</scope>
<version>${hikari-version}</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
<version>${h2-version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
<version>${junit-version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Generated by camel build tools - do NOT edit this file!
components=flowable
groupId=org.apache.camel
artifactId=camel-flowable
version=4.9.0-SNAPSHOT
projectName=Camel :: Flowable
projectDescription=Camel Flowable support
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Generated by camel build tools - do NOT edit this file!
class=org.apache.camel.component.flowable.FlowableComponent
71 changes: 71 additions & 0 deletions components/camel-flowable/src/main/docs/flowable-component.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
= Flowable Component
:doctitle: Flowable
:shortname: flowable
:artifactid: camel-flowable
:description: Communicate with the Flowable Event Registry for sending and receiving messages from the Flowable BPMN and CMMN engines.
:since: 4.9.0
:supportlevel: Stable
:tabs-sync-option:
:component-header: Both producer and consumer are supported
//Manually maintained attributes

*Since Camel {since}*

*{component-header}*

Communicate with the Flowable Event Registry for sending and receiving messages from the Flowable BPMN and CMMN engines.

== URI format

------------------------------
flowable-event://[channelKey]?[options]
------------------------------

Where *channelKey* is the unique key value of the channel model deployed in the Flowable Event Registry.

// component-configure options: START

// component-configure options: END

// component options: START
include::partial$component-configure-options.adoc[]
include::partial$component-endpoint-options.adoc[]
// component options: END

// endpoint options: START

// endpoint options: END

== Usage

=== Body content

The body content is defined by the event model that is deployed to the Flowable Event Registry.
The event message can then be mapped to process or case variables in the process BPMN or case CMMN model.

== Examples

The following example sends an event to the Flowable event registry for further processing:

[source,java]
----------------------------------------------------------------------
from("direct:start").to("flowable:exampleChannel");
----------------------------------------------------------------------

== Dependencies

To use Flowable in your Camel routes, you need to add a dependency on
*camel-flowable*, which implements the component.

If you use Maven, you can add the following to your pom.xml,
substituting the version number for the latest and greatest release (see
the download page for the latest versions).

[source,xml]
-------------------------------------
<dependency>
<groupId>org.apache.camel</groupId>
<artifactId>camel-flowable</artifactId>
<version>x.x.x</version>
</dependency>
-------------------------------------
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.flowable;

import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.commons.lang3.StringUtils;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.eventregistry.api.ChannelModelProcessor;
import org.flowable.eventregistry.api.EventRegistry;
import org.flowable.eventregistry.api.EventRepositoryService;
import org.flowable.eventregistry.api.OutboundEventChannelAdapter;
import org.flowable.eventregistry.impl.EventRegistryEngineConfiguration;
import org.flowable.eventregistry.model.CamelInboundChannelModel;
import org.flowable.eventregistry.model.CamelOutboundChannelModel;
import org.flowable.eventregistry.model.ChannelModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.EmbeddedValueResolver;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.util.StringValueResolver;

public class CamelChannelModelProcessor
implements BeanFactoryAware, ApplicationContextAware, ApplicationListener<ContextRefreshedEvent>,
ChannelModelProcessor {

protected final Logger logger = LoggerFactory.getLogger(getClass());

protected CamelContext camelContext;

protected BeanFactory beanFactory;
protected ApplicationContext applicationContext;
protected boolean contextRefreshed;

protected StringValueResolver embeddedValueResolver;

public CamelChannelModelProcessor(CamelContext camelContext) {
this.camelContext = camelContext;
}

@Override
public boolean canProcess(ChannelModel channelModel) {
return channelModel instanceof CamelInboundChannelModel || channelModel instanceof CamelOutboundChannelModel;
}

@Override
public boolean canProcessIfChannelModelAlreadyRegistered(ChannelModel channelModel) {
return channelModel instanceof CamelOutboundChannelModel;
}

@Override
public void registerChannelModel(
ChannelModel channelModel, String tenantId, EventRegistry eventRegistry,
EventRepositoryService eventRepositoryService,
boolean fallbackToDefaultTenant) {

if (channelModel instanceof CamelInboundChannelModel) {
CamelInboundChannelModel camelInboundChannelModel = (CamelInboundChannelModel) channelModel;
logger.info("Starting to register inbound channel {} in tenant {}", channelModel.getKey(), tenantId);
processInboundDefinition(camelInboundChannelModel, tenantId);
logger.info("Finished registering inbound channel {} in tenant {}", channelModel.getKey(), tenantId);

} else if (channelModel instanceof CamelOutboundChannelModel) {
logger.info("Starting to register outbound channel {} in tenant {}", channelModel.getKey(), tenantId);
processOutboundDefinition((CamelOutboundChannelModel) channelModel, tenantId);
logger.info("Finished registering outbound channel {} in tenant {}", channelModel.getKey(), tenantId);
}
}

protected void processInboundDefinition(CamelInboundChannelModel channelModel, String tenantId) {
try (FlowableEndpoint endpoint
= new FlowableEndpoint(channelModel, getEventRegistryEngineConfiguration(), camelContext)) {

camelContext.addEndpoint(endpoint.getEndpointUri(), endpoint);
if (StringUtils.isNotEmpty(channelModel.getSourceUri())) {
camelContext.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {
from(channelModel.getSourceUri()).routeId(channelModel.getKey()).to(endpoint.getEndpointUri());
}
});
}

} catch (Exception e) {
logger.error("Error creating producer for inbound channel {} in tenant {}", channelModel.getKey(), tenantId);
throw new FlowableException(
"Error creating producer for inbound channel " + channelModel.getKey() + " in tenant " + tenantId);
}
}

protected void processOutboundDefinition(CamelOutboundChannelModel channelModel, String tenantId) {
String destination = channelModel.getDestination();
if (channelModel.getOutboundEventChannelAdapter() == null && StringUtils.isNotEmpty(destination)) {
channelModel.setOutboundEventChannelAdapter(createOutboundEventChannelAdapter(channelModel, tenantId));
}
}

protected OutboundEventChannelAdapter<String> createOutboundEventChannelAdapter(
CamelOutboundChannelModel channelModel, String tenantId) {

String destination = resolve(channelModel.getDestination());
try (FlowableEndpoint endpoint
= new FlowableEndpoint(channelModel, getEventRegistryEngineConfiguration(), camelContext)) {
camelContext.addEndpoint(endpoint.getEndpointUri(), endpoint);
camelContext.addRoutes(new RouteBuilder() {

@Override
public void configure() throws Exception {
from(endpoint).routeId(channelModel.getKey()).to(destination);
}
});

return new CamelOperationsOutboundEventChannelAdapter(endpoint);

} catch (Exception e) {
logger.error("Error creating route for outbound channel {} in tenant {}", channelModel.getKey(), tenantId);
throw new FlowableException(
"Error creating route for outbound channel " + channelModel.getKey() + " in tenant " + tenantId);
}
}

@Override
public void unregisterChannelModel(
ChannelModel channelModel, String tenantId, EventRepositoryService eventRepositoryService) {

}

protected String resolve(String value) {
if (embeddedValueResolver != null) {
return embeddedValueResolver.resolveStringValue(value);
} else {
return value;
}
}

@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
if (beanFactory instanceof ConfigurableBeanFactory) {
this.embeddedValueResolver = new EmbeddedValueResolver((ConfigurableBeanFactory) beanFactory);
}
}

protected EventRegistryEngineConfiguration getEventRegistryEngineConfiguration() {
return applicationContext.getBean(EventRegistryEngineConfiguration.class);
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (event.getApplicationContext() == this.applicationContext) {
this.contextRefreshed = true;
}
}
}
Loading

0 comments on commit 8ebbe5d

Please sign in to comment.