org.imixs.workflow.kafka.ProducerService Maven / Gradle / Ivy
Show all versions of imixs-adapters-kafka Show documentation
package org.imixs.workflow.kafka;
import java.io.IOException;
import java.io.Serializable;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
import javax.enterprise.event.Observes;
import javax.xml.bind.JAXBException;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.LongSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.WorkflowKernel;
import org.imixs.workflow.engine.ProcessingEvent;
import org.imixs.workflow.exceptions.AdapterException;
import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.xml.XMLDocument;
import org.imixs.workflow.xml.XMLDocumentAdapter;
/**
* The ProducerService is a Kafka client that publishes workflow events to the
* Kafka cluster.
*
* The kafka producer is thread safe and sharing a single producer instance
* across threads will generally be faster than having multiple instances. For
* that reason we use a @Singleton with @ConcurrencyManagement
*
*
* The producer consists of a pool of buffer space that holds records that
* haven't yet been transmitted to the server as well as a background I/O thread
* that is responsible for turning these records into requests and transmitting
* them to the cluster. Failure to close the producer after use will leak these
* resources.
*
* @version 1.0
* @author rsoika
*
*/
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class ProducerService implements Serializable {
private static final long serialVersionUID = 1L;
private static Logger logger = Logger.getLogger(ProducerService.class.getName());
Producer producer;
/**
* This method creates a Kafka producer with some properties during
* initalization.
*
* BOOTSTRAP_SERVERS_CONFIG: The Kafka broker's address. If Kafka is running in
* a cluster then you can provide comma (,) seperated addresses. For
* example:localhost:9091,localhost:9092
*
* CLIENT_ID_CONFIG: Id of the producer so that the broker can determine the
* source of the request.
*
* KEY_SERIALIZER_CLASS_CONFIG: The class that will be used to serialize the key
* object. In our example, our key is Long, so we can use the LongSerializer
* class to serialize the key. If in your use case you are using some other
* object as the key then you can create your custom serializer class by
* implementing the Serializer interface of Kafka and overriding the serialize
* method.
*
* VALUE_SERIALIZER_CLASS_CONFIG: The class that will be used to serialize the
* value object. In our example, our value is String, so we can use the
* StringSerializer class to serialize the key. If your value is some other
* object then you create your custom serializer class.
*
*/
@PostConstruct
void init() {
logger.info("...init KafkaProducer...");
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,
ConfigService.getEnv(ConfigService.ENV_KAFKA_BROKERS, "kafka:9092"));
props.put(ProducerConfig.CLIENT_ID_CONFIG,
ConfigService.getEnv(ConfigService.ENV_KAFKA_CLIENTID, "Imixs-Workflow-1"));
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, LongSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,
// CustomPartitioner.class.getName());
producer = new KafkaProducer<>(props);
}
/**
* Autowire:
*
* On each workflow process event a new message is generated if the workflow
* model version matches the setup.
*/
public void onProcess(@Observes ProcessingEvent documentEvent) {
if (ProcessingEvent.AFTER_PROCESS == documentEvent.getEventType()) {
// test autowire / model version
String modelPattern = ConfigService.getEnv(ConfigService.ENV_KAFKA_AUTOWIRE, null);
if (modelPattern != null && !modelPattern.isEmpty()) {
String modelVersion = documentEvent.getDocument().getModelVersion();
Pattern regexPattern = Pattern.compile(modelPattern);
if (!regexPattern.matcher(modelVersion).find()) {
// no match
return;
}
}
logger.info("...consuming ProcssingEvent (model:" + modelPattern + ") -> send new kafka event...");
try {
sendWorkitem(documentEvent.getDocument());
} catch (AdapterException e) {
// convert Adapter Exception into runtime Exception!
throw new ProcessingErrorException(e.getErrorContext() , e.getErrorCode(), e.getMessage(),e);
}
}
}
/**
* This method sends a kafka message based on a given workitem.
*
* The topic ofi the message is the model version
*
* The value is a serialized version of the workitem.
*
* @param workitem
* @throws AdapterException
*/
public void sendWorkitem(ItemCollection workitem) throws AdapterException {
String uid = workitem.getUniqueID();
// we use the model version as the topic name
String topic = workitem.getModelVersion();
// String value = workitem.getWorkflowGroup() + ":" + uid;
try {
byte[] value = XMLDocumentAdapter.writeItemCollection(workitem);
ProducerRecord record = new ProducerRecord(topic, new String(value));
RecordMetadata metadata = producer.send(record).get();
logger.info("...Imixs-Workflow Event sent with key " + uid + " to partition " + metadata.partition()
+ " with offset " + metadata.offset());
}
catch (ExecutionException e) {
throw new AdapterException(ProducerService.class.getSimpleName(), "EXECUTION-EXCEPTION",
e.getMessage(), e);
} catch (InterruptedException e) {
throw new AdapterException(ProducerService.class.getSimpleName(), "INTERUPTED-EXCEPTION",
e.getMessage(), e);
} catch (JAXBException e) {
throw new AdapterException(ProducerService.class.getSimpleName(), "JAXB-EXCEPTION", e.getMessage(),
e);
} catch (IOException e) {
throw new AdapterException(ProducerService.class.getSimpleName(), "IO-EXCEPTION", e.getMessage(),
e);
}
}
}