How to print the exception in custom log messages when throwing Retryable errors to DefaultErrorHandler DLT? - spring-kafka

Ive implemented a KafkaConsumerFactory in which for the errors (Retryable error) I throw the exception. I want these exceptions to be printed an a proper logging format like sflf4j(LOGGER.ERROR) and not print on the console.
The ask is I want to print the errors in a proper log format instead of throwing in console with a huge stacktrace.
Below is the code snippet for KakaConsumerConfigFactory.java that has DLT Implementation to process and push error records to DLT topic.
package org.abc;
import org.apache.kafka.common.TopicPartition;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.kafka.ConcurrentKafkaListenerContainerFactoryConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.ConcurrentKafkaListenerContainerFactory;
import org.springframework.kafka.core.ConsumerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.listener.DeadLetterPublishingRecoverer;
import org.springframework.kafka.listener.DefaultErrorHandler;
import org.springframework.util.backoff.FixedBackOff;
import com.azure.cosmos.CosmosException;
import com.azure.spring.data.cosmos.exception.CosmosAccessException;
import com.fasterxml.jackson.core.JsonProcessingException;
#Configuration
public class KafkaListenerConfig {
#Bean(name = "kafkaListenerContainerFactory")
public ConcurrentKafkaListenerContainerFactory<Object, Object> kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory, KafkaTemplate<Object, Object> template) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
var recoverer = new DeadLetterPublishingRecoverer(template,
(record, ex) -> new TopicPartition("abc_dlt", record.partition()));
var errorHandler = getDLTDetails(recoverer);
factory.setCommonErrorHandler(errorHandler);
return factory;
}
#Bean(name = "kafkaListenerContainerFactoryForDLT")
public ConcurrentKafkaListenerContainerFactory<Object, Object> kafkaListenerContainerFactoryForDLT(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory, KafkaTemplate<Object, Object> template) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
var errorHandler = new DefaultErrorHandler(new FixedBackOff(3, 2));
factory.setCommonErrorHandler(errorHandler);
factory.getContainerProperties().setIdleEventInterval(70000L);
return factory;
}
private DefaultErrorHandler getDLTDetails(DeadLetterPublishingRecoverer recoverer) {
var errorHandler = new DefaultErrorHandler(recoverer, new FixedBackOff(3, 2));
errorHandler.addNotRetryableExceptions(JsonProcessingException.class);
errorHandler.addRetryableExceptions(DBException.class, DataAccessException.class);//Sample db exceptions
errorHandler.setCommitRecovered(true);
return errorHandler;
}
}
Below is the class in which the error is throw from listener.java
#Component
public class Listener {
#Autowired
private LogUtil log; //Custom logger
public Listener (final LogUtil log) {
this.log= log;
}
#KafkaListener(id = "hub_topic", topics = "hub_topic", groupId = "hub_topic_0", containerFactory = "kafkaListenerContainerFactory", clientIdPrefix = "hub_topic")
public void listen(ConsumerRecord<String, String> consumerRecord, Acknowledgment ack) {
log.printLog("INFO",consumerRecord.value());
saveToDB(consumerRecord.value()); //Method to persist obtained request to Database Columns //: id, String Payload-Consumer record
ack.acknowledge();
}
}

You can prevent the stack trace logs by setting the error handler's logLevel property to TRACE.
Then, subclass the error hander and do whatever logging you want do yourself after calling the super class methods.
EDIT
For example:
#Bean
CommonErrorHandler eh() {
DefaultErrorHandler eh = new DefaultErrorHandler((rec, ex) -> {
System.out.println("Recovered " + KafkaUtils.format(rec));
}, new FixedBackOff(3000L, 2)) {
#Override
public void handleRemaining(Exception thrownException, List<ConsumerRecord<?, ?>> records,
Consumer<?, ?> consumer, MessageListenerContainer container) {
try {
super.handleRemaining(thrownException, records, consumer, container);
// log record was recovered
}
catch (RuntimeException ex) {
// log retrying or recovery failed
throw ex;
}
}
};
eh.setLogLevel(Level.TRACE);
return eh;
}

Related

How to skip a msg that have error in kafka when i use ConcurrentMessageListenerContainer?

I'm using spring-kafka-2.1.10.RELEASE try to skip the error data but failed,
it fall in a infinity consume loop
is there anything i mssing?
Appreciate if you can help
simple code:
=========================
#Service
public class testConsumerFactoryService {
#Autowired
private PlatformTransactionManager tx;
#Autowired
private TestRepository testRepository; // table only have 1 column , String
#Autowired
private ConsumerFactory<String, String> consumerFactory;
#PostConstruct
public void consumerFactoryTest() {
ContainerProperties containerProps = new ContainerProperties("test_1");
containerProperties.setTransactionManager(this.tx);
containerProperties
.setMessageListener(new ConsumerAwareMessageListener<String, String>() {
#Override
public void onMessage(final ConsumerRecord<String, String> record,
final Consumer<?, ?> consumer) {
final String rec = record.value();
//add the 'rec' string to TestRepository's entity , aka: tests
try {
this.testRepository.save(tests); // >> 1. try to save in DB, but failed
} catch (final Throwable e) {
System.out.println("error !!" + e);
System.out.println("##records:" + record);
consumer.commitSync(); // >> 2. try to commit offset, but seems not working ?
System.out.println("##after:");
}
}
}
);
final ConcurrentMessageListenerContainer<String, String> container = new ConcurrentMessageListenerContainer<>(
this.consumerFactory, containerProperties);
container.start();
}
}
===============================
the config:
===============================
#Configuration
#EnableKafka
public class Config {
#Bean
ConcurrentKafkaListenerContainerFactory<String, String>
kafkaListenerContainerFactory() {
ConcurrentKafkaListenerContainerFactory<String, String> factory =
new ConcurrentKafkaListenerContainerFactory<>();
factory.setConsumerFactory(consumerFactory());
//use AckMode.RECORD
factory.getContainerProperties().setAckMode(AckMode.RECORD);
return factory;
}
#Bean
public ConsumerFactory<String, String> consumerFactory() {
return new DefaultKafkaConsumerFactory<>(consumerConfigs());
}
#Bean
public Map<String, Object> consumerConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, embeddedKafka.getBrokersAsString());
props.put(ConsumerConfig.GROUP_ID_CONFIG, 'Test1');
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
return props;
}
In your ConcurrentKafkaListenerContainerFactory, just associate a SeekToCurrentErrorHandler:
factory.setErrorHandler(
new SeekToCurrentErrorHandler(
(rec, ex) -> {
log.error(
"An exception happened during a message processing: {}", ex);
// good to put something like a dispatch to a DLQ etc
}));
With this, you will carry on to the next offset and you app won't get blocked due to a bad message etc.

Exception in thread "main" java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available

I created an EJB project and another project to test the first.
This screenshot gives an overview about my two projects.
The class main on the test project is:
public class TestEjb
{
public static void main(String[] args)
{
GestionEmployeeRemote gestion = null;
try {
Properties jndiProperties = new Properties();
jndiProperties.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
Context context = new InitialContext(jndiProperties);
Object o = context.lookup("ejb:/FirstEJBProject/GestionEmployee!services.GestionEmployeeRemote");
gestion = (GestionEmployeeRemote) o;
} catch (NamingException e) {
e.printStackTrace();
}
createEmployee(gestion);
}
public static void createEmployee(GestionEmployeeRemote gestion)
{
Employee employee = new Employee("Foulen", "Ben Foulen", new Date(), "Directeur");
gestion.createEmployee(employee);
}
The file jndi.properties is:
java.naming.factory.url.pkgs=org.jboss.ejb.client.naming
java.naming.factory.initial=org.jboss.naming.remote.client.InitialContextFactory
java.naming.provider.url=remote://localhost:4447
jboss.naming.client.ejb.context=true
jboss.naming.client.connect.options.org.xnio.Options.SASL_POLICY_NOPLAINTEXT=false
The class GestionEmployee.java is:
package services;
import java.util.List;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import persistance.Employee;
/**
* Session Bean implementation class GestionEmployee
*/
#Stateless
public class GestionEmployee implements GestionEmployeeRemote, GestionEmployeeLocal {
#PersistenceContext
EntityManager em;
public GestionEmployee() {
// TODO Auto-generated constructor stub
}
#Override
public void createEmployee(Employee employee) {
em.persist(employee);
}
#Override
public void updateEmployee(Employee employee) {
em.merge(employee);
}
#Override
public void deleteEmployee(Employee employee) {
em.remove(employee);
}
#Override
public Employee getEmployeeById(int idEmployee) {
Employee elmployee = em.find(Employee.class, idEmployee);
return null;
}
#Override
public List<Employee> getAllEmployee() {
Query query = em.createQuery("select e from Employee e");
return query.getResultList();
}
}
The class GestionEmployeeRemote.java is:
package services;
import java.util.List;
import javax.ejb.Remote;
import persistance.Employee;
#Remote
public interface GestionEmployeeRemote
{
public void createEmployee (Employee employee);
public void updateEmployee (Employee employee);
public void deleteEmployee (Employee employee);
public Employee getEmployeeById (int idEmployee);
public List<Employee> getAllEmployee();
}
After running the class main, I got this error:
Exception in thread "main" java.lang.IllegalStateException: EJBCLIENT000025: No EJB receiver available for handling [appName:, moduleName:FirstEJBProject, distinctName:] combination for invocation context org.jboss.ejb.client.EJBClientInvocationContext#a47962
at org.jboss.ejb.client.EJBClientContext.requireEJBReceiver(EJBClientContext.java:749)
at org.jboss.ejb.client.ReceiverInterceptor.handleInvocation(ReceiverInterceptor.java:116)
at org.jboss.ejb.client.EJBClientInvocationContext.sendRequest(EJBClientInvocationContext.java:183)
at org.jboss.ejb.client.EJBInvocationHandler.sendRequestWithPossibleRetries(EJBInvocationHandler.java:253)
at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:198)
at org.jboss.ejb.client.EJBInvocationHandler.doInvoke(EJBInvocationHandler.java:181)
at org.jboss.ejb.client.EJBInvocationHandler.invoke(EJBInvocationHandler.java:144)
at com.sun.proxy.$Proxy0.createEmployee(Unknown Source)
at test.TestEjb.createEmployee(TestEjb.java:37)
at test.TestEjb.main(TestEjb.java:31)
I'm looking for finding a solution for this issue, any help is appreciated.Thanks a lot.

how to convert code of an activity to fragment

I am converting my activity unto a fragment, then I received an error which I think I just need to add some code, what should I add
private class AsyncLogin extends AsyncTask<String, String, String> {
ProgressDialog pdLoading = new ProgressDialog(NotifMainActivity.this);
HttpURLConnection conn;
URL url = null;
errors are from the "NotifMainActivity.this" it says "in progress dialog cannot be applied" I have included an image for you to see the error in actual.Click here to see the image
Update. So I have change the "NotifMainActivity.this" with getActivity as suggested by sir Kashif Anwar but doing so made the function of the fragment to not work maybe you can provide me an alternative solution.
Here is the whole code of the fragment(this fragment is the activity of a recyclerview)
package com.capstone.jmilibraryapp;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
public class NotifMainActivity extends Fragment {
// CONNECTION_TIMEOUT and READ_TIMEOUT are in milliseconds
public static final int CONNECTION_TIMEOUT = 10000;
public static final int READ_TIMEOUT = 15000;
private RecyclerView mRVFishPrice;
private AdapterNotif mAdapter;
/*#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_notifmainactivity);
//Make call to AsyncTask
new AsyncLogin().execute();
}*/
View myView;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
myView = inflater.inflate(R.layout.activity_notifmainactivity, container, false);
return myView;
}
private class AsyncLogin extends AsyncTask<String, String, String> {
ProgressDialog pdLoading = new ProgressDialog(getActivity());
HttpURLConnection conn;
URL url = null;
#Override
protected void onPreExecute() {
super.onPreExecute();
//this method will be running on UI thread
pdLoading.setMessage("\tLoading...");
pdLoading.setCancelable(false);
pdLoading.show();
}
#Override
protected String doInBackground(String... params) {
try {
// Enter URL address where your json file resides
// Even you can make call to php file which returns json data
url = new URL("http://192.168.1.101/notif.php");
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
return e.toString();
}
try {
// Setup HttpURLConnection class to send and receive data from php and mysql
conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(READ_TIMEOUT);
conn.setConnectTimeout(CONNECTION_TIMEOUT);
conn.setRequestMethod("GET");
// setDoOutput to true as we recieve data from json file
conn.setDoOutput(true);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
return e1.toString();
}
try {
int response_code = conn.getResponseCode();
// Check if successful connection made
if (response_code == HttpURLConnection.HTTP_OK) {
// Read data sent from server
InputStream input = conn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(input));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line);
}
// Pass data to onPostExecute method
return (result.toString());
} else {
return ("unsuccessful");
}
} catch (IOException e) {
e.printStackTrace();
return e.toString();
} finally {
conn.disconnect();
}
}
#Override
protected void onPostExecute(String result) {
//this method will be running on UI thread
pdLoading.dismiss();
List<DataNotif> data=new ArrayList<>();
pdLoading.dismiss();
try {
JSONArray jArray = new JSONArray(result);
// Extract data from json and store into ArrayList as class objects
for(int i=0;i<jArray.length();i++){
JSONObject json_data = jArray.getJSONObject(i);
DataNotif fishData = new DataNotif();
fishData.NotifTitle= json_data.getString("notif_Title");
fishData.NotifMessage= json_data.getString("notif_Message");
// fishData.sizeName= json_data.getString("size_name");
fishData.Date= json_data.getString("notif_date");
data.add(fishData);
}
// Setup and Handover data to recyclerview
mRVFishPrice = (RecyclerView)myView.findViewById(R.id.fishPriceList);
mAdapter = new AdapterNotif(getActivity(), data);
mRVFishPrice.setAdapter(mAdapter);
mRVFishPrice.setLayoutManager(new LinearLayoutManager(getActivity()));
} catch (JSONException e) {
Toast.makeText(getActivity(), e.toString(), Toast.LENGTH_LONG).show();
Toast.makeText(getActivity(), "There maybe some error try again",Toast.LENGTH_LONG).show();
}
}
}
}
You should use getActivity() instead of class name,
like this
ProgressDialog pd = new ProgressDialog( getActivity() );
Hope it helps!

FirebaseMessagingService singleton? Controlling the instance

I'm having a problem with use the FirebaseMessagingService. The FirebaseMessagingService works fine. I receive the messages and the notifications. But I need send the message to Activity opened, and invoke a method in activity.
I tried create a listener, but when receive a message, is created a new instance and the listener be null. I understand that FirebaseMessagingService is instanciated when have a message to receive. So I thought in singleton and listener together, don't work, the listener keep null.
Someone have a idea how I can send a message to activity opened?
I don't think any listeners are needed for this. Once you get message in onMessageReceived(), just broadcast it using LocalBroadcastmanager. and receive that broadcast in your activity.
Below is the code snippet to achieve what you want:
MyFirebaseMessagingService.java
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Intent intent = new Intent(**Action**);
intent.putExtra("Some Payload", message.getBody());
mLocalBroadcastManager.sendBroadcast(intent);
}
MainActivity.java
private LocalBroadcastManager mLocalBroadcastManager;
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
}
};
#Override
protected void onStart() {
super.onStart();
registerBroadcastReceiver();
}
#Override
protected void onStop() {
super.onStop();
unregisterBroadcastReceiver();
}
private void registerBroadcastReceiver() {
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(**Action**);
mLocalBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
}
private void unregisterBroadcastReceiver() {
mLocalBroadcastManager.unregisterReceiver(mBroadcastReceiver);
}
Be care full though not to exceed limitations of data sizes that can be passed using intents
refer here
https://code.google.com/p/android/issues/detail?id=5878
I understood your question with out any code. Here is pretty much a turn key solution for you that should work. I use this method a lot to pass data between services and activities.
The first thing you need to to is convert the data you want to pass to something that can be passed through an intent.
Strings are easy to work with so convert to string and put in an intent. On the activity side using onCreate and onNewItent you can receive this data no problem. Then convert it back how ever you wish. See code below for an example.
Working with broadcast receivers has the possibilities of giving you data leaks if the receiver is not unRegistered. It will happen if you app crashes and the unRegister is not told to shutdown.
In your FirebaseMessagingService class
import android.os.AsyncTask;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
/**
* Created by acopp
* Date: 12/31/2016.
* Time: 1:41 PM
* You have permission to use this file for any reason that is not for evil doing
*/
public class FBMService extends FirebaseMessagingService {
static String TAG = "FBMService";
static String FBMServiceAction = "FBMService.Action";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
MainActivity.passIntent(this,FBMServiceAction,getString(remoteMessage));
}
String getString(RemoteMessage message){
Map<String, String> messageData = message.getData();
JSONObject j = new JSONObject();
for (String key : messageData.keySet()) {
String value = messageData.get(key);
try {
j.put(key, value);
} catch (JSONException e) {
e.printStackTrace();
}
}
return j.toString();
}
}
Activity Class
//In your activity class
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created by acopp
* Date: 12/31/2016.
* Time: 1:41 PM
* You have permission to use this file for any reason that is not for evil doing
*/
public class MainActivity extends Activity {
private String TAG = "MainActivity";
//Call this from FBMService to start your activity or if your activity is start to receive a new intent
static void passIntent(Context context, String action, String messageDataString) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("action", action);
intent.putExtra("message", messageDataString);
context.startActivity(intent);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
intentHandler(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent");
intentHandler(intent);
}
//Use an intent handler to manage onNewIntent and onCreate the same way
void intentHandler(Intent intent) {
if (intent.hasExtra("action")) {
String action = intent.getStringExtra("action");
if(action.equals(FBMService.FBMServiceAction)){
if (intent.hasExtra("message")) {
String messageDataString = intent.getStringExtra("message");
new iterEat().execute(messageDataString);
}
}
}
}
//Convert your string to a HashMap in the background off the main thread
class iterEat extends AsyncTask<String,Void,Map<String, String> > {
#Override
protected Map<String, String> doInBackground(String... rm) {
String messageDataString = rm[0];
try{
return fromString(messageDataString);
}catch (NullPointerException e){
return null;
}
}
#Override
protected void onPostExecute(Map<String, String> s) {
//Your data is pooped out here
Map<String, String> messageData = s;//PLOP
}
}
Map<String, String> fromString(String jsonString) throws NullPointerException{
try {
Map<String, String> messageData = new HashMap<>();
JSONObject j = new JSONObject(jsonString);
Iterator<String> i = j.keys();
while(i.hasNext()){
String key = i.next();
String value = j.getString(key);
messageData.put(key,value);
}
return messageData;
} catch (JSONException e) {
throw new NullPointerException("Didn't work");
}
}
}

JavaFx 2.0 FXML project How to access variables and project structure

I am making an IRC client in a javafx fxml project.
I am having difficulties understanding the structure of the project. I get the MVC pattern however i don't know where i have to position the main code part of the client(object initialisation and communication start).
I want to be able to update a textarea with a String from a serverConnection object when the server sends a message.
So where do i position the code and how do i notify my controller that it has to update its text passing the String simultaneously?
This is the application class:
package jircclient;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
import java.util.concurrent.*;
public class JircClient extends Application
{
#Override
public void start(Stage stage) throws Exception
{
Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
Scene scene = new Scene(root);
stage.setTitle("jircClient");
stage.setScene(scene);
stage.show();
System.out.println("Stage SET!");
//This is where i thought that i could write the main part of the program
//but the stage will not load unless the start method finishes
//Creating a serverConnetion is not a problem since it's only an object
//but starting the communication does not let the method we are in to exit
//since it sticks in a while loop forever until i probably cancel it using a
//button (not implemented yet)
//serverConnection server1 = new serverConnection();
//server1.startCommunication();
}
public static void main(String[] args) throws Exception
{
//In this method i cannot go further unless i close the stage-window
//The purpose of it is to launch the start method
Application.launch(args);
}
}
This is the controller class:
package jircclient;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextArea;
public class FXMLDocumentController implements Initializable
{
#FXML
private TextArea txt;
#FXML
private void handleButtonAction(ActionEvent event)
{
//In here i will be handling events, however i will need to have
//access to serverConnection objects
System.out.println("You clicked me!");
}
#Override
public void initialize(URL url, ResourceBundle rb)
{
}
}
This is the serverConnection class:
package jircclient;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
public class serverConnection
{
//VARIABLES
private clientInfo client;
private String server_to_connect = "someserver";
private String channel_to_connect = "#somechannel";
private String serv_resp;
private int port = 6667;
private Socket socket;
private BufferedWriter writer;
private BufferedReader reader;
private ArrayList<channel> channels;
//DEFAULT CONSTRUCTOR
public serverConnection() {client = new clientInfo("test1", "test1", "test1");}
//FULL CONSTRUCTOR
public serverConnection(String server_to_connect, int port, String channel_to_connect) throws IOException
{
client = new clientInfo("test1", "test1", "test1");
this.server_to_connect = server_to_connect;
this.port = port;
this.channel_to_connect = server_to_connect;
try
{
//Creating socket connection
this.socket = new Socket(this.server_to_connect,port);
//Socket output writer
writer = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream()));
//Socket input writer
reader = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
serv_resp = null;
System.out.println("Connection established.");
}
catch(UnknownHostException exc)
{
System.out.println("ERROR: "+ exc.toString());
}
catch(IOException exc)
{
System.out.println("ERROR: "+ exc.toString());
}
finally
{
System.out.println("Closing connection.");
socket.close();
}
}
//server response getter
public String getServerResponse()
{
return serv_resp;
}
//Introduction to server and listen
public void startCommunication() throws IOException
{
this.socket = new Socket(this.server_to_connect,port);
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
serv_resp = null;
writer.write("NICK " + client.getClientNickname() + "\r\n");
writer.write("USER " + client.getClientLogin() + " 0 * : " +
client.getClientRealName() + "\r\n");
while ((serv_resp = reader.readLine()) != null)
{
System.out.println(serv_resp);
//FXMLDocumentController.txt.setText(serv_resp);
if (serv_resp.indexOf("004") >= 0)
{
break;
}
else if (serv_resp.indexOf("433") >= 0)
{
System.out.println("Nickname is already in use.");
return;
}
}
//Get channel list
writer.write("LIST \r\n");
writer.flush();
//Join desired client
writer.write("JOIN " + channel_to_connect + "\r\n");
writer.flush();
//keep listening
while ((serv_resp = reader.readLine()) != null)
{
//FXMLDocumentController.txt.setText(serv_resp);
if (serv_resp.startsWith("PING "))
{
this.pingPong();
} else
{
System.out.println(serv_resp);
}
}
}
//Ping respond
public void pingPong() throws IOException
{
writer.write("PONG " + serv_resp.substring(5) + "\r\n");
writer.flush();
}
}
I believe there is no need to add the fxml document since it's big and there is no need.
I also have to state that oracle tutorials were not helpful as they only use the event handling in the controller and they don't implement any other logic.
Here is one possible way to do this:
Give your ServerConnection class a callback to call when it receives a message:
public class ServerConnection {
private Consumer<String> messageCallback ;
public void setMessageCallback(Consumer<String> messageCallback) {
this.messageCallback = mesasgeCallback ;
}
// other fields and methods as before...
public void startCommunication() throws IOException {
// code as before ...
while ((servResp = reader.readLine()) !=null) {
if (messageCallback != null) {
messageCallback.accept(servResp);
}
// etc....
}
// etc
}
}
Now instantiate your ServerConnection class from the controller and pass it a callback:
public class FXMLDocumentController {
ServerConnection serverConnection ;
// ...
public void initialize() {
serverConnection = new ServerConnection();
serverConnection.setMessageCallback(message ->
Platform.runLater(() -> txt.appendText(message+"\n")));
Thread serverThread = new Thread(() -> serverConnection.startListening());
serverThread.setDaemon(true); // thread will not stop application from exiting
serverThread.start();
}
// ...
}

Resources