How to implement a worker thread that will process Kinesis records and update GUI in javaFx? - user-interface

I'm working on a micro-services monitoring app.
My app supposes to update a GUI accordingly when receiving a new consumed
record, meaning:
When I receive a new record:
1)I check if the request it represents is a part of a legal flow, and
if that flow already has representation in the GUI.
By representation, I mean a set of circles that represent the full flow.
For example, if I get a transaction (MS1 received request) a legal flow num 1: that is MS1 to MS2 to MS3, so my GUI will add a table column with 2 grey circles: MS1 to MS2 and MS2 to MS3. Next, when a record: MS2 received from
MS1 is consumed I will paint the first circle green and so on.
My problem is:
I don't understand how to "tap into" Amazon's KCL code (presented here).
meaning, I don't know how to make that a consumed record will trigger event in my JavaFX GUI that will update the GUI accordingly.
Help would be much appreciated!
package com.kinesisdataconsumer;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import com.DATA_STATUS;
import com.DataBase;
import com.MonitoringLogicImpl;
import com.kinesisdataproducer.Producer;
import com.Transaction;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessor;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorCheckpointer;
import com.amazonaws.services.kinesis.clientlibrary.interfaces.IRecordProcessorFactory;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.InitialPositionInStream;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.KinesisClientLibConfiguration;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.Worker;
import com.amazonaws.services.kinesis.clientlibrary.lib.worker.ShutdownReason;
import com.amazonaws.services.kinesis.model.Record;
public class Consumer implements IRecordProcessorFactory {
private static final Logger log = LoggerFactory.getLogger(Consumer.class);
public DataBase dataBase;
public ArrayList<Transaction> transactionList;
public MonitoringLogicImpl monitoringLogic;
private final AtomicLong largestTimestamp = new AtomicLong(0);
private final List<Long> sequenceNumbers = new ArrayList<>();
private final Object lock = new Object();
public Consumer(DataBase database, ArrayList<Transaction> transactions, MonitoringLogicImpl monitoringLogicImplementation){
dataBase = database;
transactionList = transactions;
monitoringLogic = monitoringLogicImplementation;
}
private class RecordProcessor implements IRecordProcessor {
#Override
public void initialize(String shardId) {}
#Override
public void processRecords(List<Record> records, IRecordProcessorCheckpointer checkpointer) {
long timestamp = 0;
List<Long> seqNos = new ArrayList<>();
for (Record r : records) {
timestamp = Math.max(timestamp, Long.parseLong(r.getPartitionKey()));
try {
byte[] b = new byte[r.getData().remaining()];
r.getData().get(b);
seqNos.add(Long.parseLong(new String(b, "UTF-8").split("#")[0]));
//this thread adds the transaction to the DB
Thread addTransactionToDBThread = new Thread() {
public void run() {
try {
JSONObject jsonObj = new JSONObject(new String(b, "UTF-8").split("#")[1]);
Transaction transaction = Transaction.convertJsonToTransaction(jsonObj);
//add the transaction to the database
dataBase.addTransactionToDB(transaction);
//update the user-interface about the last transaction in the system
DATA_STATUS transactionStatus = monitoringLogic.getStatus(transaction);
monitoringLogic.updateUI(transaction.getUuid(), transaction.getSender(), transaction.getReceiver(), transactionStatus);
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
};
addTransactionToDBThread.start();
} catch (Exception e) {
log.error("Error parsing record", e);
System.exit(1);
}
}
synchronized (lock) {
if (largestTimestamp.get() < timestamp) {
log.info(String.format(
"Found new larger timestamp: %d (was %d), clearing state",
timestamp, largestTimestamp.get()));
largestTimestamp.set(timestamp);
sequenceNumbers.clear();
}
// Only add to the shared list if our data is from the latest run.
if (largestTimestamp.get() == timestamp) {
sequenceNumbers.addAll(seqNos);
Collections.sort(sequenceNumbers);
}
}
try {
checkpointer.checkpoint();
} catch (Exception e) {
log.error("Error while trying to checkpoint during ProcessRecords", e);
}
}
#Override
public void shutdown(IRecordProcessorCheckpointer checkpointer, ShutdownReason reason) {
log.info("Shutting down, reason: " + reason);
try {
checkpointer.checkpoint();
} catch (Exception e) {
log.error("Error while trying to checkpoint during Shutdown", e);
}
}
}
/**
* Log a message indicating the current state.
*/
public void logResults() {
synchronized (lock) {
if (largestTimestamp.get() == 0) {
return;
}
if (sequenceNumbers.size() == 0) {
log.info("No sequence numbers found for current run.");
return;
}
// The producer assigns sequence numbers starting from 1, so we
// start counting from one before that, i.e. 0.
long last = 0;
long gaps = 0;
for (long sn : sequenceNumbers) {
if (sn - last > 1) {
gaps++;
}
last = sn;
}
log.info(String.format(
"Found %d gaps in the sequence numbers. Lowest seen so far is %d, highest is %d",
gaps, sequenceNumbers.get(0), sequenceNumbers.get(sequenceNumbers.size() - 1)));
}
}
#Override
public IRecordProcessor createProcessor() {
return this.new RecordProcessor();
}
public void consumeData() {
KinesisClientLibConfiguration config =
new KinesisClientLibConfiguration(
"KinesisProducerLibSampleConsumer",
Producer.STREAM_NAME,
new DefaultAWSCredentialsProviderChain(),
"KinesisProducerLibSampleConsumer")
.withRegionName(Producer.REGION)
.withInitialPositionInStream(InitialPositionInStream.LATEST);
final Consumer consumer = new Consumer(dataBase, transactionList, monitoringLogic);
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
#Override
public void run() {
consumer.logResults();
}
}, 10, 1, TimeUnit.SECONDS);
new Worker.Builder()
.recordProcessorFactory(consumer)
.config(config)
.build()
.run();
}
}

Related

Oracle : Send latest insert/update to JMS

I have an insert/update trigger for a Oracle table.
Is there a way to send the details of the affected row(all columns) as a message to JMS?
I can write a Java Program, 'loadjava' that and call from the trigger.
Does this way affect performance?
Is there any native way of achieving this?
There is indeed a native way: use AQ JMS from PL/SQL, see https://docs.oracle.com/database/121/ADQUE/jm_exmpl.htm#ADQUE1600. In short you create an AQ queue with a JMS payload type; then you can post messages with PL/SQL from the trigger. An external Java client can connect to the database and read the messages with JMS.
I don't know how much a call into Java would affect performance, but I try to avoid it. It was a nice idea but it never really caught on, so it remains a fringe case and at least early on there were always issues. PL/SQL on the other hand works.
If you need to send data to another message queue product (tags activemq and mq) you can read the messages in Java and forward them. It adds an extra step, but it is straightforward.
loadjava have many problems and not stable if there is many classes loaded and many business, take a look Calling Java from Oracle, PLSQL causing oracle.aurora.vm.ReadOnlyObjectException
Oracle AQ as i know is not free.
I have implemented the same need after trying many possibilities by creating only 1 class loaded to oracle with loadjava which is called as a procedure by a trigger and have the responsability to call an external java program with all needed parameters and log external process output to a table, as below.
i have encoded text mesage to BASE64 because i used JSON format and some specials caracters can causes problems as a parameters to external java program.
i have used "#*#jms_separator#*#" as a separator in the sent parameter string to parse the content if i need to send many parameters to the external program.
the whole duration of ShellExecutor.shellExec is around 500ms and running since 1 year without any problem.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.util.Arrays;
import java.util.concurrent.FutureTask;
import sun.misc.BASE64Encoder;
public class ShellExecutor {
static {
System.setProperty("file.encoding", "UTF-8");
}
private static final String INSERT_LOGS_SQL = "INSERT INTO JMS_LOG (TEXT_LOG) VALUES (?) ";
private static final String DEFAULT_CONNECTION = "jdbc:default:connection:";
public static String SQLshellExec(String command) throws Exception {
long start = System.currentTimeMillis();
StringBuffer result = new StringBuffer();
ShellExecutor worker = new ShellExecutor();
try {
worker.shellExec(command, result);
} finally {
result.append("exe duration : " + (System.currentTimeMillis() - start + "\n"));
Connection dbConnection = null;
PreparedStatement logsStatement = null;
try {
dbConnection = DriverManager.getConnection(DEFAULT_CONNECTION);
logsStatement = dbConnection.prepareStatement(INSERT_LOGS_SQL);
logsStatement.clearParameters();
Clob clob = dbConnection.createClob();
clob.setString(1, result.toString());
logsStatement.setClob(1, clob);
logsStatement.executeUpdate();
} finally {
if (logsStatement != null) {
try {
logsStatement.close();
} catch (Exception e) {
}
}
}
}
return result.substring(result.length() - 3090);
}
public void shellExec(String command, StringBuffer result) throws Exception {
Process process = null;
int exit = -10;
try {
InputStream stdout = null;
String[] params = command.split("#*#jms_separator#*#");
BASE64Encoder benc = new BASE64Encoder();
for (int i = 0; i < params.length; i++) {
if (params[i].contains("{") || params[i].contains("}") || params[i].contains("<")
|| params[i].contains("/>")) {
params[i] = benc.encodeBuffer(params[i].getBytes("UTF-8"));
}
}
result.append("Using separator : " + "#*#jms_separator#*#").append("\n")
.append("Calling : " + Arrays.toString(params)).append("\n");
ProcessBuilder pb = new ProcessBuilder(params);
pb.redirectErrorStream(true);
process = pb.start();
stdout = process.getInputStream();
LogStreamReader lsr = new LogStreamReader(stdout, result);
FutureTask<String> stdoutFuture = new FutureTask<String>(lsr, null);
Thread thread = new Thread(stdoutFuture, "LogStreamReader");
thread.start();
try {
exit = process.waitFor();
} catch (InterruptedException e) {
try {
exit = process.waitFor();
} catch (Exception e1) {
}
}
stdoutFuture.get();
result.append("\n").append("exit code :").append(exit).append("\n");
if (exit != 0) {
throw new RuntimeException(result.toString());
}
} catch (Exception e) {
result.append("\nException(").append(e.toString()).append("):").append(e.getCause()).append("\n\n");
e.printStackTrace(System.err);
throw e;
} finally {
if (process != null) {
process.destroy();
}
}
}
}
class LogStreamReader implements Runnable {
private BufferedReader reader;
private StringBuffer result;
public LogStreamReader(InputStream is, StringBuffer result) {
this.reader = new BufferedReader(new InputStreamReader(is));
this.result = result;
}
public void run() {
try {
String line = null;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}
} catch (Exception e) {
result.append("\nException(").append(e.toString()).append("):").append(e.getCause()).append("\n\n");
e.printStackTrace(System.err);
} finally {
try {
reader.close();
} catch (IOException e) {
}
}
}
}
The class of the external Java program packaged as an executable with all needed librairies, a simple JMS sender :
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import javax.jms.Destination;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import org.apache.commons.codec.binary.Base64;
import org.json.JSONObject;
import progress.message.jclient.ConnectionFactory;
import progress.message.jimpl.Connection;
public class JMSSender {
private static SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss.SSS");
public static void main(String[] args) throws Throwable {
doSend(args[0]);
}
public static void doSend(String text)
throws Throwable {
if (Base64.isBase64(text)) {
text = new String(Base64.decodeBase64(text));
}
String content = "\n\nsending message :" + text;
Connection con = null;
Session session = null;
try {
ConnectionFactory cf = new ConnectionFactory();
session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination dest = session.createTopic(destination) ;
MessageProducer producer = session.createProducer(dest);
con.start();
JSONObject json = new JSONObject();
json.put("content", text);
json.put("date", sdf.format(new Date()));
TextMessage tm = session.createTextMessage(json.toString());
producer.send(tm);
content += " \n\n" + "sent message :" + json.toString();
} catch (Throwable e) {
content += " \n\n" + e.toString() + " \n\n" + Arrays.toString(e.getStackTrace());
if (e.getCause() != null) {
content += " \n\nCause : " + e.getCause().toString() + " \n\n"
+ Arrays.toString(e.getCause().getStackTrace());
}
e.printStackTrace(System.err);
throw e;
} finally {
write("steps on sending message : " + content);
if (session != null) {
try {
session.commit();
session.close();
} catch (Exception e) {
}
session = null;
}
if (con != null) {
try {
con.stop();
con.close();
} catch (Exception e) {
}
}
}
}
private static void write(String log) {
try {
if (System.out != null) {
System.out.println(log);
}
} catch (Exception e2) {
}
}
}

Confluent Kafka Avro deserializer for spring boot kafka listener

Does somebody implemented confluent-kafka messages deserializer to consume kafka messages by spring "#KafkaListener"-s ?
Here is my answer, which I've implemented based on: "io.confluent.kafka.serializers.AbstractKafkaAvroDeserializer"
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import javax.xml.bind.DatatypeConverter;
import org.apache.avro.Schema;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DecoderFactory;
import org.apache.avro.specific.SpecificDatumReader;
import org.apache.avro.specific.SpecificRecordBase;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.serialization.Deserializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AvroConfluentDeserializer<T extends SpecificRecordBase> implements Deserializer<T> {
private static final Logger LOG = LoggerFactory.getLogger(AvroConfluentDeserializer.class);
protected static final byte MAGIC_BYTE = 0x0;
protected static final int idSize = 4;
private final DecoderFactory decoderFactory = DecoderFactory.get();
protected final Class<T> targetType;
public AvroConfluentDeserializer(Class<T> targetType) {
this.targetType = targetType;
}
#Override
public void close() {
// No-op
}
#Override
public void configure(Map<String, ?> arg0, boolean arg1) {
// No-op
}
#Override
public T deserialize(String topic, byte[] data) {
try {
T result = null;
if (data != null) {
LOG.info("data='{}'", DatatypeConverter.printHexBinary(data));
result = (T) deserializePayload(data, targetType.newInstance().getSchema());
LOG.info("deserialized data='{}'", result);
}
return result;
} catch (Exception ex) {
throw new SerializationException(
"Can't deserialize data '" + Arrays.toString(data) + "' from topic '" + topic + "'", ex);
}
}
protected T deserializePayload(byte[] payload, Schema schema) throws SerializationException {
int id = -1;
try {
ByteBuffer buffer = getByteBuffer(payload);
id = buffer.getInt();
int length = buffer.limit() - 1 - idSize;
int start = buffer.position() + buffer.arrayOffset();
DatumReader<T> reader = new SpecificDatumReader<T>(schema);
return reader.read(null, decoderFactory.binaryDecoder(buffer.array(), start, length, null));
} catch (IOException | RuntimeException e) {
throw new SerializationException("Error deserializing Avro message for id " + id, e);
}
}
private ByteBuffer getByteBuffer(byte[] payload) {
ByteBuffer buffer = ByteBuffer.wrap(payload);
if (buffer.get() != MAGIC_BYTE) {
throw new SerializationException("Unknown magic byte!");
}
return buffer;
}
}

CompletableFuture to make webservice calls and save when everything is done

I have a list of sessions that I have to call a webservice to set some property on each session.
I am trying to call webservice using async process and use completablefuture for it so that when it is all done, I can save them all in db.
How can I do this? So far, my code is as follows, it doesn't work.
sessions.stream()
.forEach(s -> CompletableFuture.runAsync(() -> webServiceCall(s), executor));
sessionService.saveAll(sessions);
EDIT:
I came up with this solution, not sure if this is the correct way of doing it.
List<CompletableFuture<Void>> futures = sessions.stream()
.map(s -> CompletableFuture.runAsync(() -> webServiceCall(s), executor))
.collect(Collectors.toList());
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
sessionService.saveAll(sessions);
I am using join to make sure it waits for response to return before saving sessions
In short - all you need something like this -
CompletableFuture.supplyAsync(this::supplySomething, ex).thenAccept(this::consumer);
You need a method that will call in a executor (threadpool). In my case my pool size is 100. Next you need to call your supplier as many times as you want.
Each call to 'supplier' will create one task. I'm creating 10000 tasks. Each of them will run in parallel and each of them, upon completion, will call my 'consumer'.
Your supplier should return some sort of object which holds response from webservice. This object will then become the parameter of your 'consumer' method.
You might want to kill the pool after (or in middle) everything is done.
See an example below -
package com.sanjeev.java8.thread;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Caller {
public static ExecutorService ex = Executors.newFixedThreadPool(100);
public static void main(String[] args) throws InterruptedException {
Caller caller = new Caller();
caller.start();
ex.shutdown();
ex.awaitTermination(10, TimeUnit.MINUTES);
}
private void start() {
for (int i = 0; i < 10000; i++) {
CompletableFuture.supplyAsync(this::supplySomething, ex).thenAccept(this::consumer);
}
}
private int supplySomething() {
try {
URL url = new URL("http://www.mywebservice.com");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setDoInput(true);
connection.connect();
try (DataOutputStream wr = new DataOutputStream(connection.getOutputStream())) {
wr.write("supply-some-data".getBytes());
}
Reader in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
for (int c; (c = in.read()) >= 0;) {
System.out.print((char) c);
}
in.close();
// return the response code. I'm return 'int', you should return some sort of object.
return 200;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public void consumer(Integer i) {
// This parameter should be of type 'your object' that supplier returned.
// I got the response; add it in the list or whatever....
}
}
Another example that might suits your need better -
public class Caller2 {
public static ExecutorService ex = Executors.newFixedThreadPool(2);
private static Iterator<String> addresses = Stream.of("www.google.com", "www.yahoo.com", "www.abc.com").collect(Collectors.toList()).iterator();
private static ArrayList<String> results = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
Caller2 caller = new Caller2();
caller.start();
ex.shutdown();
ex.awaitTermination(1, TimeUnit.HOURS);
System.out.println(results);
}
private void start() {
while (addresses.hasNext()) {
CompletableFuture.supplyAsync(this::supplyURL, ex).thenAccept(this::consumer);
}
}
private String supplyURL() {
String url = addresses.next();
// call this URL and return response;
return "Success";
}
public void consumer(String result) {
results.add(result);
}

Thread does not progress past ZMQ.context(1)

I am trying to implement a simple pub sub example where I have a server and am publishing periodic notifications about uptime to clients.
This is being run as a part of a Windows service - bundled with InnoSetup and launch4j and Apache procrun/prunsrv.
The thread does not go beyond the creation of the context. What could be going wrong?
import java.io.IOException;
import java.util.Date;
import org.msgpack.MessagePack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.zeromq.ZMQ;
import org.ocpsoft.prettytime.*;
/**
* Notification service for updates to configuration in the logger
* #author Aalhad
*/
public class NotificationServer extends Thread {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private volatile boolean shouldRun;
private PrettyTime upTime;
private PreferenceManager prefMgr = PreferenceManager.getInstance();
public ZMQ.Context context;
public ZMQ.Socket pubSocket;
public NotificationServer() {
log.debug("Entered notification server constructor ......................");
context = ZMQ.context(1);
log.debug("THIS DOES NOT GET PRINTED ... it is as if we are blocking in ZMQ.context!!!");
pubSocket = context.socket(ZMQ.PUB);
pubSocket.bind("tcp://*:"+prefMgr.getNotificationPort());
pubSocket.bind("ipc://powerlogger");
log.debug("NotificationServer created");
}
#Override
public void run() {
log.debug("Entering run loop of Notification Server");
setStarting();
log.debug("Writing to tcp port: {}", prefMgr.getNotificationPort());
upTime = new PrettyTime();
ConfigMessage msg = prefMgr.getConfigMessage();
MessagePack msgPack = new MessagePack();
byte[] sendBytes;
try {
log.debug("Going ahead and sending: {}", msg);
sendBytes = msgPack.write(msg);
pubSocket.send(sendBytes);
log.debug("Finished sending msg");
} catch (IOException ex) {
log.error("Could not send first config notification",ex);
}
//On starts and restarts, we send the current configuration to our
//subscribers
String upSince;
while (shouldRun()) {
log.trace("In the notification loop");
upSince = upTime.format(new Date(0));
log.trace("============================================================== Started: {}", upSince );
ConfigMessage cfgMsg = new ConfigMessage();
cfgMsg.msgType = MessageType.UPSINCE;
cfgMsg.message = upSince;
try {
// ..... code here to write the time into a
// messagepack structure and publishing it
sleep(5000);
log.trace("After sleeping in notification loop");
} catch (InterruptedException ex) {
log.error("Notification thread disturbed when sleeping.");
}
}
}
public synchronized void shutDown() {
shouldRun = false;
log.trace("Set shouldRun to false in discovery server");
try {
if (pubSocket != null) {
pubSocket.close();
context.term();
}
}
catch(Exception e) {
log.error("Interesting situation when trying to close the discovery socket when shutting down",e);
}
}
public synchronized void setStarting() {
shouldRun = true;
}
private synchronized boolean shouldRun() {
return shouldRun;
}
}
Found the bug. It was a simple issue of forgetting to provide the jar for the service when creating the installer. The logs did not show that the class could not be found.
Fixed.

How to call this Comets application from a web page

I have implemented the two classes shown at http://tomcat.apache.org/tomcat-6.0-doc/aio.html which gives a messenger application using Tomcat's comet implementation.
How do I connect this to a web interface and get something to display.
I am thinking these are the basic steps (I don't know the details).
I should create some traditional event - a button click or AJAX event - that calls the ChatServlet and passes in a CometEvent (somehow) - perhaps BEGIN
From then I have my code call the event method every time I want to send something to the client using the READ event as the input parameter.
I have copied the two classes below:
package controller;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.catalina.CometEvent;
import org.apache.catalina.CometProcessor;
public class ChatServlet extends HttpServlet implements CometProcessor {
protected ArrayList<HttpServletResponse> connections = new ArrayList<HttpServletResponse>();
protected MessageSender messageSender = null;
public void init() throws ServletException {
messageSender = new MessageSender();
Thread messageSenderThread = new Thread(messageSender, "MessageSender["
+ getServletContext().getContextPath() + "]");
messageSenderThread.setDaemon(true);
messageSenderThread.start();
}
public void destroy() {
connections.clear();
messageSender.stop();
messageSender = null;
}
/**
* Process the given Comet event.
*
* #param event
* The Comet event that will be processed
* #throws IOException
* #throws ServletException
*/
public void event(CometEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getHttpServletRequest();
HttpServletResponse response = event.getHttpServletResponse();
if (event.getEventType() == CometEvent.EventType.BEGIN) {
log("Begin for session: " + request.getSession(true).getId());
PrintWriter writer = response.getWriter();
writer
.println("<!doctype html public \"-//w3c//dtd html 4.0 transitional//en\">");
writer
.println("<head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
writer.flush();
synchronized (connections) {
connections.add(response);
}
} else if (event.getEventType() == CometEvent.EventType.ERROR) {
log("Error for session: " + request.getSession(true).getId());
synchronized (connections) {
connections.remove(response);
}
event.close();
} else if (event.getEventType() == CometEvent.EventType.END) {
log("End for session: " + request.getSession(true).getId());
synchronized (connections) {
connections.remove(response);
}
PrintWriter writer = response.getWriter();
writer.println("</body></html>");
event.close();
} else if (event.getEventType() == CometEvent.EventType.READ) {
InputStream is = request.getInputStream();
byte[] buf = new byte[512];
do {
int n = is.read(buf); // can throw an IOException
if (n > 0) {
log("Read " + n + " bytes: " + new String(buf, 0, n)
+ " for session: "
+ request.getSession(true).getId());
} else if (n < 0) {
// error(event, request, response);
System.out.println("you have an error");
return;
}
} while (is.available() > 0);
}
}
}
package controller;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import javax.servlet.http.HttpServletResponse;
public class MessageSender implements Runnable {
protected boolean running = true;
protected ArrayList<String> messages = new ArrayList<String>();
protected ArrayList<HttpServletResponse> connections = new ArrayList<HttpServletResponse>();
public MessageSender() {
}
public void stop() {
running = false;
}
/**
* Add message for sending.
*/
public void send(String user, String message) {
synchronized (messages) {
messages.add("[" + user + "]: " + message);
messages.notify();
}
}
public void run() {
while (running) {
if (messages.size() == 0) {
try {
synchronized (messages) {
messages.wait();
}
} catch (InterruptedException e) {
// Ignore
}
}
synchronized (connections) {
String[] pendingMessages = null;
synchronized (messages) {
pendingMessages = messages.toArray(new String[0]);
messages.clear();
}
// Send any pending message on all the open connections
for (int i = 0; i < connections.size(); i++) {
try {
PrintWriter writer = connections.get(i).getWriter();
for (int j = 0; j < pendingMessages.length; j++) {
writer.println(pendingMessages[j] + "<br>");
}
writer.flush();
} catch (IOException e) {
System.out.println("IOExeption sending message" + e);
}
}
}
}
}
}
Here you have a complete example for Tomcat with source code to download at the bottom:Developing with Comet and Java

Resources