I have tried a program which download files parallely using java.nio by creating a thread per file download.
package com.java.tftp.nio;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
/**
* This class is used to download files concurrently from tftp server by
* configuring the filenames, no of files.
*
* #author SHRIRAM
*
*/
public class TFTP_NIO_Client {
/**
* destination folder
* */
private String destinationFolder;
/**
* list of files names to download
* */
private List<String> fileNames;
/**
* integer indicates the number of files to download concurrently
* */
private int noOfFilesToDownload;
public TFTP_NIO_Client(List<String> fileNames, String destinationFolder,
int noOfFilesToDownload) {
this.destinationFolder = destinationFolder;
this.fileNames = fileNames;
this.noOfFilesToDownload = noOfFilesToDownload;
initializeHandlers();
}
/**
* This method creates threads to register the channel to process download
* files concurrently.
*
* #param noOfFilesToDownload
* - no of files to download
*/
private void initializeHandlers() {
for (int i = 0; i < noOfFilesToDownload; i++) {
try {
Selector aSelector = Selector.open();
SelectorHandler theSelectionHandler = new SelectorHandler(
aSelector, fileNames.get(i));
theSelectionHandler.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* Setup RRQ/WRQ packet Packet : | Opcode | FileName | 0 | mode | 0 |
* Filename -> Filename in array of bytes. 0 -> indicates end of file mode
* -> string in byte array 'netascii' or 'octet'
*
* #param aOpcode
* #param aMode
* #param aFileName
* #throws IOException
*/
private void sendRequest(int aOpcode, int aMode, String aFileName,
DatagramChannel aChannel, InetSocketAddress aAddress)
throws IOException {
// Read request packet
TFTPReadRequestPacket theRequestPacket = new TFTPReadRequestPacket();
aChannel.send(
theRequestPacket.constructReadRequestPacket(aFileName, aMode),
aAddress);
}
/**
* sends TFTP ACK Packet Packet : | opcode | Block# | opcode -> 4 -> 2 bytes
* Block -> block number -> 2bytes
*
* #param aBlock
*/
private ByteBuffer sendAckPacket(int aBlockNumber) {
// acknowledge packet
TFTPAckPacket theAckPacket = new TFTPAckPacket();
return theAckPacket.getTFTPAckPacket(aBlockNumber);
}
/**
* This class is used to handle concurrent downloads from the server.
*
* */
public class SelectorHandler extends Thread {
private Selector selector;
private String fileName;
/**
* flag to indicate the file completion.
* */
private boolean isFileReadFinished = false;
public SelectorHandler(Selector aSelector, String aFileName)
throws IOException {
this.selector = aSelector;
this.fileName = aFileName;
registerChannel();
}
private void registerChannel() throws IOException {
DatagramChannel theChannel = DatagramChannel.open();
theChannel.configureBlocking(false);
selector.wakeup();
theChannel.register(selector, SelectionKey.OP_READ);
sendRequest(Constants.OP_READ, Constants.ASCII_MODE, fileName,
theChannel, new InetSocketAddress(Constants.HOST,
Constants.TFTP_PORT));
}
#Override
public void run() {
process();
}
private void process() {
System.out.println("Download started for " + fileName + " ");
File theFile = new File(destinationFolder
+ fileName.substring(fileName.lastIndexOf("/")));
FileOutputStream theFout = null;
try {
theFout = new FileOutputStream(theFile);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
while (!isFileReadFinished) {
try {
if (selector.select() == 0) {
try {
// sleep 2sec was introduced because selector is
// thread safe but keys are not thread safe
Thread.sleep(2000);
} catch (InterruptedException e) {
continue;
}
continue;
}
Set<SelectionKey> theSet = selector.selectedKeys();
Iterator<SelectionKey> theSelectedKeys = theSet.iterator();
synchronized (theSelectedKeys) {
while (theSelectedKeys.hasNext()) {
SelectionKey theKey = theSelectedKeys.next();
theSelectedKeys.remove();
if (theKey.isReadable()) {
isFileReadFinished = read(theKey, theFout,
fileName);
if (!isFileReadFinished) {
theKey.interestOps(SelectionKey.OP_READ);
}
} else if (theKey.isWritable()) {
// there is no implementation for file write to
// server.
theKey.interestOps(SelectionKey.OP_READ);
}
}
}
} catch (IOException ie) {
ie.printStackTrace();
}
}
System.out.println("Download finished for " + fileName);
try {
if (selector.isOpen()) {
selector.close();
}
if (theFout != null) {
theFout.close();
}
} catch (IOException ie) {
}
}
}
/**
* #param aKey
* registered key for the selector
* #param aOutStream
* - file output stream to write the file contents.
* #return boolean
* #throws IOException
*/
private boolean read(SelectionKey aKey, OutputStream aOutStream,
String aFileName) throws IOException {
DatagramChannel theChannel = (DatagramChannel) aKey.channel();
// data packet
TFTPDataPacket theDataPacket = new TFTPDataPacket();
ByteBuffer theReceivedBuffer = theDataPacket.constructTFTPDataPacket();
SocketAddress theSocketAddress = theChannel.receive(theReceivedBuffer);
theReceivedBuffer.flip();
byte[] theBuffer = theReceivedBuffer.array();
byte[] theDataBuffer = theDataPacket.getDataBlock();
if (theDataPacket.getOpCode() == Constants.OP_DATA) {
int theLimit = theDataPacket.getLimit();
// checks the limit of the buffer because a packet with data less
// than 512 bytes of content signals that it is the last packet in
// transmission for this particular file
if (theLimit != Constants.MAX_BUFFER_SIZE
&& theLimit < Constants.MAX_BUFFER_SIZE) {
byte[] theLastBlock = new byte[theLimit];
System.arraycopy(theBuffer, 0, theLastBlock, 0, theLimit);
// writes the lastblock
aOutStream.write(theLastBlock);
// sends an acknowledgment to the server using TFTP packet
// block number
theChannel
.send(sendAckPacket((((theBuffer[2] & 0xff) << 8) | (theBuffer[3] & 0xff))),
theSocketAddress);
if (theChannel.isOpen()) {
theChannel.close();
}
return true;
} else {
aOutStream.write(theDataBuffer);
// sends an acknowledgment to the server using TFTP packet
// block number
theChannel
.send(sendAckPacket((((theBuffer[2] & 0xff) << 8) | (theBuffer[3] & 0xff))),
theSocketAddress);
return false;
}
} else if (Integer.valueOf(theBuffer[1]) == Constants.OP_ERROR) {
System.out.println("File : " + aFileName + " not found ");
handleError(theReceivedBuffer);
}
return false;
}
/**
* This method handles the error packet received from Server.
*
* #param aBuffer
*/
private void handleError(ByteBuffer aBuffer) {
// Error packet
new TFTPErrorPacket(aBuffer);
}
}
Is it possible to download multiple files in parallel using java.nio by not creating a thread per file download? If yes can anybody suggest a solution to proceed further.
I would provide an approach to achieve what you are aiming for :
Let L the list of files to be downloaded.
Create a Map M which will hold the mapping of File name to be downloaded and the corresponding Selector instance.
For each file F in L
Get Selector SK from M corresponding to F
Process the state of the Selector by checking for any of the events being ready.
If processing is complete then set the Selector corresponding to F as null. This will help in identifying files
whose
processing is completed.Alternatively, you can remove F from
L; so that the next time you are looping you only process files that are not yet completely downloaded.
The above being said, I am curious to understand why you would want to attempt such a feat? If the thought process behind this requirement is to reduce the number of threads to 1 then it is not correct. Remember, you would end up really taxing the single thread running and for sure your throughput would not necessarily be optimal since the single thread would be dealing with both network as well as disk I/O. Also, consider the case of encountering an exception while writing one of the several files to the disk - you would end up aborting the transfer for all the files; something I am sure you do not want.
A better and more scalable approach would be to poll selectors on a single thread, but hand off any I/O activity to a worker thread. A better approach still would be to read the techniques presented in Doug Lea's paper and implement them. In fact Netty library already implements this pattern and is widely used in production.
Related
I tried installing tinyb from the repository: https://github.com/intel-iot-devkit/tinyb
I've build the tinyb.jar and add it to the IntellJ project as a library.
I ran the sample code provided in tinyb examples with small modification(commented out some code in the main method before the Bluetooth Manager initialization):
import tinyb.*;
import java.util.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.TimeUnit;
public class HelloTinyB {
private static final float SCALE_LSB = 0.03125f;
static boolean running = true;
static void printDevice(BluetoothDevice device) {
System.out.print("Address = " + device.getAddress());
System.out.print(" Name = " + device.getName());
System.out.print(" Connected = " + device.getConnected());
System.out.println();
}
static float convertCelsius(int raw) {
return raw / 128f;
}
/*
* After discovery is started, new devices will be detected. We can get a list of all devices through the manager's
* getDevices method. We can the look through the list of devices to find the device with the MAC which we provided
* as a parameter. We continue looking until we find it, or we try 15 times (1 minutes).
*/
static BluetoothDevice getDevice(String address) throws InterruptedException {
BluetoothManager manager = BluetoothManager.getBluetoothManager();
BluetoothDevice sensor = null;
for (int i = 0; (i < 15) && running; ++i) {
List<BluetoothDevice> list = manager.getDevices();
if (list == null)
return null;
for (BluetoothDevice device : list) {
printDevice(device);
/*
* Here we check if the address matches.
*/
if (device.getAddress().equals(address))
sensor = device;
}
if (sensor != null) {
return sensor;
}
Thread.sleep(4000);
}
return null;
}
/*
* Our device should expose a temperature service, which has a UUID we can find out from the data sheet. The service
* description of the SensorTag can be found here:
* http://processors.wiki.ti.com/images/a/a8/BLE_SensorTag_GATT_Server.pdf. The service we are looking for has the
* short UUID AA00 which we insert into the TI Base UUID: f000XXXX-0451-4000-b000-000000000000
*/
static BluetoothGattService getService(BluetoothDevice device, String UUID) throws InterruptedException {
System.out.println("Services exposed by device:");
BluetoothGattService tempService = null;
List<BluetoothGattService> bluetoothServices = null;
do {
bluetoothServices = device.getServices();
if (bluetoothServices == null)
return null;
for (BluetoothGattService service : bluetoothServices) {
System.out.println("UUID: " + service.getUUID());
if (service.getUUID().equals(UUID))
tempService = service;
}
Thread.sleep(4000);
} while (bluetoothServices.isEmpty() && running);
return tempService;
}
static BluetoothGattCharacteristic getCharacteristic(BluetoothGattService service, String UUID) {
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
if (characteristics == null)
return null;
for (BluetoothGattCharacteristic characteristic : characteristics) {
if (characteristic.getUUID().equals(UUID))
return characteristic;
}
return null;
}
/*
* This program connects to a TI SensorTag 2.0 and reads the temperature characteristic exposed by the device over
* Bluetooth Low Energy. The parameter provided to the program should be the MAC address of the device.
*
* A wiki describing the sensor is found here: http://processors.wiki.ti.com/index.php/CC2650_SensorTag_User's_Guide
*
* The API used in this example is based on TinyB v0.3, which only supports polling, but v0.4 will introduce a
* simplied API for discovering devices and services.
*/
public static void main(String[] args) throws InterruptedException {
// if (args.length < 1) {
// System.err.println("Run with <device_address> argument");
// System.exit(-1);
// }
/*
* To start looking of the device, we first must initialize the TinyB library. The way of interacting with the
* library is through the BluetoothManager. There can be only one BluetoothManager at one time, and the
* reference to it is obtained through the getBluetoothManager method.
*/
BluetoothManager manager = BluetoothManager.getBluetoothManager();
/*
* The manager will try to initialize a BluetoothAdapter if any adapter is present in the system. To initialize
* discovery we can call startDiscovery, which will put the default adapter in discovery mode.
*/
boolean discoveryStarted = manager.startDiscovery();
System.out.println("The discovery started: " + (discoveryStarted ? "true" : "false"));
BluetoothDevice sensor = getDevice(args[0]);
/*
* After we find the device we can stop looking for other devices.
*/
try {
manager.stopDiscovery();
} catch (BluetoothException e) {
System.err.println("Discovery could not be stopped.");
}
if (sensor == null) {
System.err.println("No sensor found with the provided address.");
System.exit(-1);
}
System.out.print("Found device: ");
printDevice(sensor);
if (sensor.connect())
System.out.println("Sensor with the provided address connected");
else {
System.out.println("Could not connect device.");
System.exit(-1);
}
Lock lock = new ReentrantLock();
Condition cv = lock.newCondition();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
running = false;
lock.lock();
try {
cv.signalAll();
} finally {
lock.unlock();
}
}
});
BluetoothGattService tempService = getService(sensor, "f000aa00-0451-4000-b000-000000000000");
if (tempService == null) {
System.err.println("This device does not have the temperature service we are looking for.");
sensor.disconnect();
System.exit(-1);
}
System.out.println("Found service " + tempService.getUUID());
BluetoothGattCharacteristic tempValue = getCharacteristic(tempService, "f000aa01-0451-4000-b000-000000000000");
BluetoothGattCharacteristic tempConfig = getCharacteristic(tempService, "f000aa02-0451-4000-b000-000000000000");
BluetoothGattCharacteristic tempPeriod = getCharacteristic(tempService, "f000aa03-0451-4000-b000-000000000000");
if (tempValue == null || tempConfig == null || tempPeriod == null) {
System.err.println("Could not find the correct characteristics.");
sensor.disconnect();
System.exit(-1);
}
System.out.println("Found the temperature characteristics");
/*
* Turn on the Temperature Service by writing 1 in the configuration characteristic, as mentioned in the PDF
* mentioned above. We could also modify the update interval, by writing in the period characteristic, but the
* default 1s is good enough for our purposes.
*/
byte[] config = { 0x01 };
tempConfig.writeValue(config);
/*
* Each second read the value characteristic and display it in a human readable format.
*/
while (running) {
byte[] tempRaw = tempValue.readValue();
System.out.print("Temp raw = {");
for (byte b : tempRaw) {
System.out.print(String.format("%02x,", b));
}
System.out.print("}");
/*
* The temperature service returns the data in an encoded format which can be found in the wiki. Convert the
* raw temperature format to celsius and print it. Conversion for object temperature depends on ambient
* according to wiki, but assume result is good enough for our purposes without conversion.
*/
int objectTempRaw = (tempRaw[0] & 0xff) | (tempRaw[1] << 8);
int ambientTempRaw = (tempRaw[2] & 0xff) | (tempRaw[3] << 8);
float objectTempCelsius = convertCelsius(objectTempRaw);
float ambientTempCelsius = convertCelsius(ambientTempRaw);
System.out.println(
String.format(" Temp: Object = %fC, Ambient = %fC", objectTempCelsius, ambientTempCelsius));
lock.lock();
try {
cv.await(1, TimeUnit.SECONDS);
} finally {
lock.unlock();
}
}
sensor.disconnect();
}
}
I got the following stack error when I run it (image below) :
I've tried many solution such as this fix:
https://github.com/intel-iot-devkit/tinyb/issues/75
but gave me the same error.
Any tips and tricks appreciated Thanks.
I figured it out, for tinyb to work properly it need library files generated from the build. For me it was in /home/user/Desktop/tinyb-master/src where those files are in the folder (libtinyb.so, libtinyb.so.0, libtinyb.so.0.5.0) and also in :/home/user/Desktop/tinyb-master/java/jni where those files are in the folder (libjavatiny.so, libjavatinyb.so.0, libjavatinyb.so.0.5.0).These two path must be added in the VM Option as follow:
-Djava.library.path=/home/user/Desktop/tinyb-master/java/jni:/home/user/Desktop/tinyb-master/src and also don't forget the tinyb.jar (../tinyb-master/java/tinyb.jar) that need to be added in the java project.
I am trying to achieve the automatic update for my java web-start applicaiton. Logic: I am fetching the jnlp file from the server and comparing the timestamp with the current one. if there is difference then download the latest file and restart the application with javaws command. Now I have two problems.
1. I am not able to fetch the local jnlp file (because the location for jnlp file is different for different operating system as mentioned here
2. I am not able to find a graceful way to restart the application after killing the current running application. I would appreciate if there is any other graceful solution available. Thanks
My code:
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Date;
import javax.swing.JOptionPane;
import org.apache.log4j.Logger;
import com.ibm.nzna.projects.qit.app.PropertySystem;
/**
* QitUpdater . java
*
* #Info Checks for updates for the JWS Application
*
* This program will try to use a given URL to connect online to check for
* a newer version of the jnlpFile. The program then compares the last
* modified dates of the local copy and the URL copy, and notifies the
* user if the URL copy is newer, via a Dialog box popup. The newer
* version can then, be downloaded and installed using JWS.
*
* #Warnings If the server containing QIT changes its directory or location then
* the program will error out every time since the URL link is
* hard-coded.
*
* #author Ashish Tyagi
* #version 4/22/2013
*
*/
public abstract class QitUpdater {
// constant which hold the location of property file and server location
public final static String jnlpFile = PropertySystem.getString(PropertySystem.PROPERTY_JNLP_FILE);
public final static String fileDirectoryURL = PropertySystem.getString(PropertySystem.PROPERTY_JNLP_SERVER);
static Logger logger = Logger.getLogger(QitUpdater.class.getName());
// private variables
private static HttpURLConnection huc;
private static File oldFile = null;
private static long localDate = 0;
private static long remoteDate = 0;
public static void checkUpdates(boolean displayErrors,boolean displayIsUpToDate,boolean autoRunner){
logger.info("value of jnlp file: "+fileDirectoryURL+"/"+jnlpFile);
if(jnlpFile !=null && fileDirectoryURL != null)
checkForUpdates(displayErrors, displayIsUpToDate, autoRunner);
}
/**
* Starts a new task to check for QIT updates.
*
* If there is no connection or the program is up to date, then this method
* returns normally. Otherwise, it will exit the old program and execute the
* newer QIT program.
*
* #param displayErrors
* will show all error messages in popups, if any occur. This is
* recommended to be false on boostrapping, so the 'offline user'
* is not annoyed.
*
* #param displayIsUpToDate
* will show a popup letting the user know if their current
* version of QIT is up to date, if they have the most up to date
* version. Otherwise, it will ignore the popup.
*
* #param autoRunNewer
* will automatically execute the newer JNLP file, if it is found
* or downloaded. This is recommended to be false if this method
* is being called from within a user action, like a button
* press. If it is set to false, a dialog will ask the user to
* restart QIT in order to finish updating the program, if an
* update was found.
*/
public static void checkForUpdates(boolean displayErrors,
boolean displayIsUpToDate, boolean autoRunNewer) {
// Try to find a similarly named file
// in the current local directory
oldFile = new File(jnlpFile);
if (oldFile.exists()) // find the JNLP file
{
// grab the local last modified date to compare
localDate = oldFile.lastModified();
}
// Try to access the base URL
int code = 404;
try {
URL u = new URL(fileDirectoryURL);
huc = (HttpURLConnection) u.openConnection();
huc.setRequestMethod("GET");
huc.connect();
code = huc.getResponseCode();
} catch (MalformedURLException e) {
if (displayErrors)
printError("Error: Could not check for updates.\nMalformedURLException: "
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
return;
} catch (IOException e) {
if (displayErrors)
printError("Error: Could not check for updates.\nIOException: "
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
return;
}
if (code == 200) {
// 200 is a valid connection
// scan URL for versions
try {
StringBuilder sb = new StringBuilder();
sb.append(fileDirectoryURL).append("/").append(jnlpFile);
URL u = new URL(sb.toString());
huc = (HttpURLConnection) u.openConnection();
huc.setRequestMethod("GET");
huc.connect();
// grab the last modified date
remoteDate = huc.getLastModified();
} catch (MalformedURLException e) {
if (displayErrors)
printError("Error: Failed to download.\n\n"
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
return;
} catch (IOException e) {
if (displayErrors)
printError("Error: Failed to download.\n\n"
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
return;
}
// compare last modified dates of JNLP files
if (remoteDate != localDate) {
// found a newer version of JNLP
// ask to download
if (0 == printQuestion("An updated version of QIT is available.\n\nLast updated:\n"
+ new Date(remoteDate).toString()
+ "\n\nDo you want to download and install it?")) {
// download and install
try {
downloadUrlFile(jnlpFile, fileDirectoryURL + "/"
+ jnlpFile);
oldFile.setLastModified(remoteDate);
// set the date to the date on the server
if (autoRunNewer) {
// run the JNLP file
try {
Runtime.getRuntime()
.exec("javaws "+jnlpFile);
System.exit(0);// quit this version of QIT
} catch (IOException e) {
if (displayErrors)
printError("Error:\n"
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
}
} else
printInfo("In order to finish installing the\nupdate, QIT needs to be restarted.");
} catch (Exception e) {
if (displayErrors)
printError("Error: Failed to download " + jnlpFile
+ ".\n\n" + e.getMessage()
+ "\n\nClick OK to continue using QIT.");
}
}
} else {
// modified dates are the same
// try to launch the current JNLP
if(oldFile.exists())
{
// run the JNLP file
try {
Runtime.getRuntime()
.exec("javaws "+jnlpFile);
System.exit(0);// quit this version of QIT
} catch (IOException e) {
if (displayErrors)
printError("Error:\n"
+ e.getMessage()
+ "\n\nClick OK to continue using QIT.");
}
}
// up to date
if (displayIsUpToDate)
printInfo("QIT is up to date.\n\nLast update was:\n"
+ (new Date(localDate).toString())
+ "\n\nClick OK to continue.");
}
}
return;
}
/**
* Downloads the urlString to the filename at the current directory.
*
* #param filename
* #param urlString
* #throws MalformedURLException
* #throws IOException
*/
public static void downloadUrlFile(String filename, String urlString)
throws MalformedURLException, IOException {
BufferedInputStream in = null;
FileOutputStream fout = null;
try {
URL url = new URL(urlString);
huc = (HttpURLConnection) url.openConnection();
in = new BufferedInputStream(url.openStream());
fout = new FileOutputStream(filename);
byte data[] = new byte[1024];
int count;
while ((count = in.read(data, 0, 1024)) != -1) {
fout.write(data, 0, count);
}
} finally {
if (in != null)
in.close();
if (fout != null)
fout.close();
}
}
/**
* Display an error
*
* #param e
*/
private static void printError(String e) {
JOptionPane.showMessageDialog(null, e, "Error",
JOptionPane.ERROR_MESSAGE);
}
/**
* Display some information
*
* #param s
*/
private static void printInfo(String s) {
JOptionPane.showMessageDialog(null, s, "Information",
JOptionPane.INFORMATION_MESSAGE);
}
/**
* Prompt a Yes/No Question
*
* #param String
* q
* #return int 0 means YES, 1 means NO, -1 means they clicked "X" close
*/
private static int printQuestion(String q) {
return JOptionPane.showConfirmDialog(null, q, "Question",
JOptionPane.YES_NO_OPTION);
}
}
I am trying to achieve the automatic update for my java web-start application
Use the DownloadService of the JNLP API.
DownloadService service allows an application to control how its own resources are cached, to determine which of its resources are currently cached, to force resources to be cached, and to remove resources from the cache. The JNLP Client is responsible for providing a specific implementation of this service.
I'm not a very experienced Java programmer, so forgive me if this is a bit of a newbie question.
I'm designing a system that consists broadly of 3 modules. A client, a server and an application. The idea is the client sends a message to the server. The server triggers a use case in the application. The result of the use case is returned to the server, and the server sends the results to the client. I opted for this architecture because I'm expecting to need to support multiple clients at once, I want to be able to reuse the server module in other applications, I want to keep the code responsible for managing client connections as uncoupled from the code that implements the domain logic as possible, and because of the opportunity to learn some more advanced java.
I'm planning to tie the various modules together with queues. The client is simple enough. Issue a message and block until a response arrives (it may be oversimplifying but it's a reasonable model for now). The application is equally not a problem. It blocks on its input queue, executes a use case when it receives a valid message and pushes the results to an output queue. Having multiple clients makes things a bit more tricky but still within my grasp with my experience level. The server maintains threads for every open connection, and the server outbound/application inbound queue is synchronised, so if 2 messages arrive at once the second thread will just have to wait a moment for the first thread to deliver its payload into the queue.
The problem is the part in the middle, the server, which will have to block on two independent things. The server is watching both the client, and the application's output queue (which serves as an input queue for the server). The server needs to block until either a message comes in from the client (which it then forwards to the application), or until the application completes a task and pushes the results into the application outbound/server inbound queue.
As far as I can tell, Java can only block on one thing.
Is it possible to have the server block until either the client sends a message or the server inbound queue ceases to be empty?
UPDATE:
I've had a bit of time to work on this, and have managed to pare the problem down to the bare minimum that illustrates the problem. There's a somewhat bulky code dump to follow, even with the trimming, so apologies for that. I'll try to break it up as much as possible.
This is the code for the Server:
public class Server implements Runnable {
private int listenPort = 0;
private ServerSocket serverSocket = null;
private BlockingQueue<Message> upstreamMessaes = null;
private BlockingQueue<Message> downstreamMessages = null;
private Map<Integer, Session> sessions = new ConcurrentHashMap ();
private AtomicInteger lastId = new AtomicInteger ();
/**
* Start listening for clients to process
*
* #throws IOException
*/
#Override
public void run () {
int newSessionId;
Session newSession;
Thread newThread;
System.out.println (this.getClass () + " running");
// Client listen loop
while (true) {
newSessionId = this.lastId.incrementAndGet ();
try {
newSession = new Session (this, newSessionId);
newThread = new Thread (newSession);
newThread.setName ("ServerSession_" + newSessionId);
this.sessions.put (newSessionId, newSession);
newThread.start ();
} catch (IOException ex) {
Logger.getLogger (Server.class.getName ()).log (Level.SEVERE, null, ex);
}
}
}
/**
* Accept a connection from a new client
*
* #return The accepted Socket
* #throws IOException
*/
public Socket accept () throws IOException {
return this.getSocket().accept ();
}
/**
* Delete the specified Session
*
* #param sessionId ID of the Session to remove
*/
public void deleteSession (int sessionId) {
this.sessions.remove (sessionId);
}
/**
* Forward an incoming message from the Client to the application
*
* #param msg
* #return
* #throws InterruptedException
*/
public Server messageFromClient (Message msg) throws InterruptedException {
this.upstreamMessaes.put (msg);
return this;
}
/**
* Set the port to listen to
*
* We can only use ports in the range 1024-65535 (ports below 1024 are
* reserved for common protocols such as HTTP and ports above 65535 don't
* exist)
*
* #param listenPort
* #return Returns itself so further methods can be called
* #throws IllegalArgumentException
*/
public final Server setListenPort (int listenPort) throws IllegalArgumentException {
if ((listenPort > 1023) && (listenPort <= 65535)) {
this.listenPort = listenPort;
} else {
throw new IllegalArgumentException ("Port number " + listenPort + " not valid");
}
return this;
}
/**
* Get the server socket, initialize it if it isn't already started.
*
* #return The object's ServerSocket
* #throws IOException
*/
private ServerSocket getSocket () throws IOException {
if (null == this.serverSocket) {
this.serverSocket = new ServerSocket (this.listenPort);
}
return this.serverSocket;
}
/**
* Instantiate the server
*
* #param listenPort
* #throws IllegalArgumentException
*/
public Server ( int listenPort,
BlockingQueue<Message> incomingMessages,
BlockingQueue<Message> outgoingMessages) throws IllegalArgumentException {
this.setListenPort (listenPort);
this.upstreamMessaes = incomingMessages;
this.downstreamMessages = outgoingMessages;
System.out.println (this.getClass () + " created");
System.out.println ("Listening on port " + listenPort);
}
}
I believe the following method belongs in the Server but is currently commented out.
/**
* Notify a Session of a message for it
*
* #param sessionMessage
*/
public void notifySession () throws InterruptedException, IOException {
Message sessionMessage = this.downstreamMessages.take ();
Session targetSession = this.sessions.get (sessionMessage.getClientID ());
targetSession.waitForServer (sessionMessage);
}
This is my Session class
public class Session implements Runnable {
private Socket clientSocket = null;
private OutputStreamWriter streamWriter = null;
private StringBuffer outputBuffer = null;
private Server server = null;
private int sessionId = 0;
/**
* Session main loop
*/
#Override
public void run () {
StringBuffer inputBuffer = new StringBuffer ();
BufferedReader inReader;
try {
// Connect message
this.sendMessageToClient ("Hello, you are client " + this.getId ());
inReader = new BufferedReader (new InputStreamReader (this.clientSocket.getInputStream (), "UTF8"));
do {
// Parse whatever was in the input buffer
inputBuffer.delete (0, inputBuffer.length ());
inputBuffer.append (inReader.readLine ());
System.out.println ("Input message was: " + inputBuffer);
this.server.messageFromClient (new Message (this.sessionId, inputBuffer.toString ()));
} while (!"QUIT".equals (inputBuffer.toString ()));
// Disconnect message
this.sendMessageToClient ("Goodbye, client " + this.getId ());
} catch (IOException | InterruptedException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
} finally {
this.terminate ();
this.server.deleteSession (this.getId ());
}
}
/**
*
* #param msg
* #return
* #throws IOException
*/
public Session waitForServer (Message msg) throws IOException {
// Generate a response for the input
String output = this.buildResponse (msg.getPayload ()).toString ();
System.out.println ("Output message will be: " + output);
// Output to client
this.sendMessageToClient (output);
return this;
}
/**
*
* #param request
* #return
*/
private StringBuffer buildResponse (CharSequence request) {
StringBuffer ob = this.outputBuffer;
ob.delete (0, this.outputBuffer.length ());
ob.append ("Server repsonded at ")
.append (new java.util.Date ().toString () )
.append (" (You said '" )
.append (request)
.append ("')");
return this.outputBuffer;
}
/**
* Send the given message to the client
*
* #param message
* #throws IOException
*/
private void sendMessageToClient (CharSequence message) throws IOException {
// Output to client
OutputStreamWriter osw = this.getStreamWriter ();
osw.write ((String) message);
osw.write ("\r\n");
osw.flush ();
}
/**
* Get an output stream writer, initialize it if it's not active
*
* #return A configured OutputStreamWriter object
* #throws IOException
*/
private OutputStreamWriter getStreamWriter () throws IOException {
if (null == this.streamWriter) {
BufferedOutputStream os = new BufferedOutputStream (this.clientSocket.getOutputStream ());
this.streamWriter = new OutputStreamWriter (os, "UTF8");
}
return this.streamWriter;
}
/**
* Terminate the client connection
*/
private void terminate () {
try {
this.streamWriter = null;
this.clientSocket.close ();
} catch (IOException e) {
Logger.getLogger (Session.class.getName ()).log (Level.SEVERE, null, e);
}
}
/**
* Get this Session's ID
*
* #return The ID of this session
*/
public int getId () {
return this.sessionId;
}
/**
* Session constructor
*
* #param owner The Server object that owns this session
* #param sessionId The unique ID this session will be given
* #throws IOException
*/
public Session (Server owner, int sessionId) throws IOException {
System.out.println ("Class " + this.getClass () + " created");
this.server = owner;
this.sessionId = sessionId;
this.clientSocket = this.server.accept ();
System.out.println ("Session ID is " + this.sessionId);
}
}
The test application is fairly basic, it just echoes a modified version of the original request message back. The real application will do work on receipt of a message and returning a meaningful response to the Server.
public class TestApp implements Runnable {
private BlockingQueue <Message> inputMessages, outputMessages;
#Override
public void run () {
Message lastMessage;
StringBuilder returnMessage = new StringBuilder ();
while (true) {
try {
lastMessage = this.inputMessages.take ();
// Construct a response
returnMessage.delete (0, returnMessage.length ());
returnMessage.append ("Server repsonded at ")
.append (new java.util.Date ().toString () )
.append (" (You said '" )
.append (lastMessage.getPayload ())
.append ("')");
// Pretend we're doing some work that takes a while
Thread.sleep (1000);
this.outputMessages.put (new Message (lastMessage.getClientID (), lastMessage.toString ()));
} catch (InterruptedException ex) {
Logger.getLogger (TestApp.class.getName ()).log (Level.SEVERE, null, ex);
}
}
}
/**
* Initialize the application
*
* #param inputMessages Where input messages come from
* #param outputMessages Where output messages go to
*/
public TestApp (BlockingQueue<Message> inputMessages, BlockingQueue<Message> outputMessages) {
this.inputMessages = inputMessages;
this.outputMessages = outputMessages;
System.out.println (this.getClass () + " created");
}
}
The Message class is extremely simple and just consists of an originating client ID and a payload string, so I've left it out.
Finally the main class looks like this.
public class Runner {
/**
*
* #param args The first argument is the port to listen on.
* #throws Exception
*/
public static void main (String[] args) throws Exception {
BlockingQueue<Message> clientBuffer = new LinkedBlockingQueue ();
BlockingQueue<Message> appBuffer = new LinkedBlockingQueue ();
TestApp appInstance = new TestApp (clientBuffer, appBuffer);
Server serverInstance = new Server (Integer.parseInt (args [0]), clientBuffer, appBuffer);
Thread appThread = new Thread (appInstance);
Thread serverThread = new Thread (serverInstance);
appThread.setName("Application");
serverThread.setName ("Server");
appThread.start ();
serverThread.start ();
appThread.join ();
serverThread.join ();
System.exit (0);
}
}
While the real application will be more complex the TestApp illustrates the basic pattern of use. It blocks on its input queue until there's something there, processes it, then pushes the result onto its output queue.
Session classes manage a live connection between a particular client and the server. It takes input from the client and converts it to Message objects, and it takes Message objects from the Server and converts them to output to send to the client.
The Server listens for new incoming connections and sets up a Session object for each incoming connection it has. When a Session passes it a Message, it puts it into its upstream queue for the application to deal with.
The difficulty I'm having is getting return messages to travel back down from the TestApp to the various clients. When a message from a client comes in, the Session generates a Message and sends it to the Server, which then puts it into its upstream queue, which is also the input queue for the TestApp. In response, the TestApp generates a response Message and puts it into the output queue, which is also the downstream queue for the Server.
This means that Sessions need to wait for two unrelated events. They should block until
Input arrives from the client (the BufferedReader on the client socket has input to process),
OR a message is sent to it by the Server (the server calls the WaitForServer () method on the session)
As for the Server itself, it also has to wait for two unrelated events.
a Session calls messageFromClient() with a message to pass to the TestApp,
OR the TestApp pushes a message onto the output/downstream queue to be passed onto a Session.
What on the face of it looked like a simple task to achieve is proving a lot more difficult than I first imagined. I expect I'm overlooking something obvious, as I'm still quite new to concurrent programming, but if you can help point out where I'm going wrong I'd appreciate instruction.
Because your implementation is using methods to pass data between client-session-server, you've actually already solved your immediate problem. However, this may not have been your intention. Here's what's happening:
Session's run method is running in its own thread, blocking on the socket. When the server calls waitForServer, this method immediately executes in the server's thread - in Java, if a thread calls a method then that method executes in that thread, and so in this case the Session did not need to unblock. In order to create the problem you are trying to solve, you would need to remove the waitForServer method and replace it with a BlockingQueue messagesFromServer queue - then the Server would place messages in this queue and Session would would need to block on it, resulting in Session needing to block on two different objects (the socket and the queue).
Assuming that you switch to the implementation where the Session will need to block on two objects, I think you can solve this with a hybrid of the two approaches I described in the comments:
Each Session's socket will need a thread to block on it - I don't see any way around this, unless you're willing to replace this with a fixed thread pool (say, 4 threads) that poll the sockets and sleep for a few dozen milliseconds if there's nothing to read from them.
You can manage all Server -> Sessions traffic with a single queue and a single thread that blocks on it - the Server includes the Session "address" in its payload so that the thread blocking on it knows what to do with the message. If you find that this doesn't scale when you have a lot of sessions, then you can always increase the thread/queue count, e.g. with 32 sessions you can have 4 threads/queues, 8 sessions per thread/queue.
I may have misunderstood but it seems that where you have the code "listening" for a message, you should be able to use a simple OR statement to solve this.
One other thing that might be useful is to add a unique id to every client so you can tell which client the message is intended for.
I currently have two clients (Producer/Consumer), and I am trying to send a large file via JMS. I am successfully producing and sending the file to the JMS Server without any problems. The problem is when I try to consume the message, and I get the following exception:
Aug 24, 2012 11:25:37 AM client.Client$1 onException
SEVERE: Connection to the Server has been lost, will retry in 30 seconds. weblogic.jms.common.LostServerException: java.lang.Exception: weblogic.rjvm.PeerGoneException: ; nested exception is:
weblogic.socket.MaxMessageSizeExceededException: Incoming message of size: '10000080' bytes exceeds the configured maximum of: '10000000' bytes for protocol: 't3'
<Aug 24, 2012 11:25:37 AM CDT> <Error> <Socket> <BEA-000403> <IOException occurred on socket: Socket[addr=127.0.0.1/127.0.0.1,port=7001,localport=51764]
weblogic.socket.MaxMessageSizeExceededException: Incoming message of size: '10000080' bytes exceeds the configured maximum of: '10000000' bytes for protocol: 't3'.
weblogic.socket.MaxMessageSizeExceededException: Incoming message of size: '10000080' bytes exceeds the configured maximum of: '10000000' bytes for protocol: 't3'
at weblogic.socket.BaseAbstractMuxableSocket.incrementBufferOffset(BaseAbstractMuxableSocket.java:174)
at weblogic.rjvm.t3.MuxableSocketT3.incrementBufferOffset(MuxableSocketT3.java:351)
at weblogic.socket.SocketMuxer.readFromSocket(SocketMuxer.java:983)
at weblogic.socket.SocketMuxer.readReadySocketOnce(SocketMuxer.java:922)
I believe this has to do with my MaxMessage size setting in WebLogic and not a code problem (but I could of course be wrong). Here are my settigns for Maximum Message Size
I am not sure why I am getting this exception since the Maximum message size for this protocol is larger than what the exception is claiming... Any thoughts?
I have also tried adding the server start argument -Dweblogic.MaxMessageSize=200000000, but to no avail.
Here is some code of my where I set the MessageListener, and the message consumer itself.
public boolean setClient(MessageListener listener) {
try {
Properties parm = new Properties();
parm.setProperty("java.naming.factory.initial",
"weblogic.jndi.WLInitialContextFactory");
parm.setProperty("java.naming.provider.url", iProps.getURL());
parm.setProperty("java.naming.security.principal", iProps.getUser());
parm.setProperty("java.naming.security.credentials",
iProps.getPassword());
ctx = new InitialContext(parm);
final QueueConnectionFactory connectionFactory = (QueueConnectionFactory) ctx
.lookup(iProps.getConFactory());
connection = connectionFactory.createQueueConnection();
((WLConnection) connection)
.setReconnectPolicy(JMSConstants.RECONNECT_POLICY_ALL);
((WLConnection) connection).setReconnectBlockingMillis(30000L);
((WLConnection) connection).setTotalReconnectPeriodMillis(-1L);
session = connection.createQueueSession(false,
Session.AUTO_ACKNOWLEDGE);
queue = (Queue) ctx.lookup(iProps.getQueue());
// The following code in the switch statement is the only code that
// differs for the producer and consumer.
switch (cType)
{
case PRODUCER: {
producer = (WLMessageProducer) session
.createProducer(queue);
// Setting to send large files is done in WebLogic
// Administration Console.
// Set
producer.setSendTimeout(60000L);
break;
}
case CONSUMER: {
consumer = session.createConsumer(queue);
if (listener != null) {
consumer.setMessageListener(listener);
}else{
log.warning("A Message listener was not set for the consumer, messages will not be acknowledged!");
}
break;
}
default:
log.info("The client type " + cType.toString()
+ " is not currently supported!");
return false;
}
connection.setExceptionListener(new ExceptionListener() {
#Override
public void onException(JMSException arg0) {
Logger log2 = new MyLogger().getLogger("BPEL Client");
if (arg0 instanceof LostServerException) {
log2.severe("Connection to the Server has been lost, will retry in 30 seconds. "
+ arg0.toString());
} else {
log2.severe(arg0.toString());
}
}
});
shutdownListener = new ShutdownListener(connection, session, producer, consumer);
connection.start();
log.info("Successfully connected to " + iProps.getURL());
return true;
} catch (JMSException je) {
log.severe("Could not connect to the WebLogic Server, will retry in 30 seconds. "
+ je.getMessage());
try {
Thread.sleep(30000L);
} catch (InterruptedException e) {
log.warning(e.toString());
}
return setClient(listener);
} catch (Exception e) {
log.severe("Could not connect to the WebLogic Server, will retry in 30 seconds. "
+ e.toString());
try {
Thread.sleep(30000L);
} catch (InterruptedException ie) {
log.warning(ie.toString());
}
return setClient(listener);
}
}
Here's the MessageListener:
public class ConsumerListener implements MessageListener {
private Logger log;
private File destination;
private Execute instructions;
public ConsumerListener(Execute instructions, File destination) {
this.instructions = instructions;
this.destination = destination;
log = new MyLogger().getLogger("BPEL Client");
}
#Override
public void onMessage(Message arg0) {
try {
if (arg0.getJMSRedelivered()) {
log.severe("A re-delivered message has been received, and it has been ignored!"
+ arg0.toString());
} else {
try {
if (arg0 instanceof TextMessage) {
consumeMessage((TextMessage) arg0);
} else if (arg0 instanceof BytesMessage) {
consumeMessage((BytesMessage) arg0);
} else {
log.warning("Currently, only TextMessages and BytesMessages are supported!");
}
} catch (JMSException e) {
log.severe(e.toString());
} catch (IOException e) {
log.severe(e.toString());
} catch (Throwable t) {
log.severe(t.toString());
}
}
} catch (JMSException e) {
log.severe(e.toString());
}
}
/**
* Unwraps the JMS message received and creates a file and a control file if
* there are instructions present.
*
* #param textMessage
* JMS message received to be consumed.
* #throws JMSException
* #throws IOException
*/
protected void consumeMessage(TextMessage textMessage) throws JMSException,
IOException {
// ***All properties should be lowercase. for example fileName
// should be
// filename.***
String fileName = textMessage.getStringProperty("filename");
if (fileName == null || fileName.isEmpty()) {
fileName = textMessage.getStringProperty("fileName");
}
if (fileName != null && !fileName.isEmpty()) {
// Check if the
// file name is equal to the shutdown file. If it
// is, shutdown the consumer. This is probably not a good way to
// do this, as the program can no longer be shutdown locally!
// We have a file in the queue, need to create the file.
createFile(destination.getAbsolutePath() + "\\" + fileName,
textMessage.getText());
log.info("Done creating the file");
String inst = textMessage.getStringProperty("instructions");
// If there are instructions included, then create the
// instruction file, and route the message based on this file.
if (inst != null && !inst.isEmpty()) {
// We need to rout the file.
log.info("Instructions found, executing instructions");
String[] tokens = fileName.split("\\.");
String instFileName = "default.ctl";
if (tokens.length == 2) {
instFileName = tokens[0] + ".ctl";
}
File controlFile = createFile(destination.getAbsolutePath()
+ "\\" + instFileName, inst);
Control control = new Control(controlFile);
instructions.execute(control);
log.info("Done executing instructions");
} else {
log.info("No instructions were found");
}
log.info("Done consuming message: " + textMessage.getJMSMessageID());
}
}
/**
* Unwraps the JMS message received and creates a file and a control file if
* there are instructions present.
*
* #param bytesMessage
* The bytes payload of the message.
* #throws JMSException
* #throws IOException
*/
protected void consumeMessage(BytesMessage bytesMessage)
throws JMSException, IOException {
// ***All properties should be lowercase. for example fileName
// should be
// filename.***
log.info("CONSUME - 1");
String fileName = bytesMessage.getStringProperty("filename");
if (fileName == null || fileName.isEmpty()) {
fileName = bytesMessage.getStringProperty("fileName");
}
if (fileName != null && !fileName.isEmpty()) {
// Check if the
// file name is equal to the shutdown file. If it
// is, shutdown the consumer. This is probably not a good way to
// do this, as the program can no longer be shutdown locally!
// We have a file in the queue, need to create the file.
byte[] payload = new byte[(int) bytesMessage.getBodyLength()];
bytesMessage.readBytes(payload);
createFile(destination.getAbsolutePath() + "\\" + fileName, payload);
log.info("Done creating the file");
String inst = bytesMessage.getStringProperty("instructions");
// If there are instructions included, then create the
// instruction file, and route the message based on this file.
if (inst != null && !inst.isEmpty()) {
// We need to rout the file.
log.info("Instructions found, executing instructions");
String[] tokens = fileName.split("\\.");
String instFileName = "default.ctl";
if (tokens.length == 2) {
instFileName = tokens[0] + ".ctl";
}
File controlFile = createFile(destination.getAbsolutePath()
+ "\\" + instFileName, inst);
Control control = new Control(controlFile);
instructions.execute(control);
log.info("Done executing instructions");
} else {
log.info("No instructions were found");
}
log.info("Done consuming message: "
+ bytesMessage.getJMSMessageID());
}
}
/**
* Creates a file with the given filename (this should be an absolute path),
* and the text that is to be contained within the file.
*
* #param fileName
* The filename including the absolute path of the file.
* #param fileText
* The text to be contained within the file.
* #return The newly created file.
* #throws IOException
*/
protected File createFile(String fileName, String fileText)
throws IOException {
File toCreate = new File(fileName);
FileUtils.writeStringToFile(toCreate, fileText);
return toCreate;
}
/**
* Creates a file with the given filename (this should be an absolute path),
* and the text that is to be contained within the file.
*
* #param fileName
* The filename including the absolute path of the f ile.
* #param fileBytes
* The bytes to be contained within the file.
* #return The newly created file.
* #throws IOException
*/
protected File createFile(String fileName, byte[] fileBytes)
throws IOException {
File toCreate = new File(fileName);
FileUtils.writeByteArrayToFile(toCreate, fileBytes);
return toCreate;
}
}
You will also have to increase the Maximum Message Size from the WLS console as shown in the screenshot for all the managed servers.
Afterwards perform a restart and the problem will be solved.
Furthermore there is a second alternative solution. According the Oracle Tuning WebLogic JMS Doc:
Tuning MessageMaximum Limitations
If the aggregate size of the messages pushed to a consumer is larger than the current protocol's maximum message size (default size is 10 MB and is configured on a per WebLogic Server instance basis using the console and on a per client basis using the Dweblogic.MaxMessageSize command line property), the message delivery fails.
Setting Maximum Message Size on a Client
You may need to configure WebLogic clients in addition to the WebLogic Server instance, when sending and receiving large messages. To set the maximum message size on a client, use the following command line property:
-Dweblogic.MaxMessageSize
Note: This setting applies to all WebLogic Server network packets delivered to the client, not just JMS related packets.
EDITED:
This issue can be resolved by following one or more of the below actions.
Configure the System Property -Dweblogic.MaxMessageSize
Increase the max message size using the WLS console for the admin and all the managed servers. The steps in the WLS console are: server / Protocols / General
Increase the Maximum Message Size from WLS console. The steps in the WLS console are: Queue / Configuration / Threshholds and Quotas / Maximum Message Size
PROCEDURE for applying the weblogic.MaxMessageSize Property
Keep a backup of the setDomainEnv files. Stop all the servers. Add the -Dweblogic.MaxMessageSize=yourValue in each setDomainEnv file and more specifically in the EXTRA_JAVA_PROPERTIES line. Afterwards start the ADMIN first and when the ADMIN is in status RUNNING, then start the MANAGED servers.
I hope this helps.
In my case setting the -Dweblogic.MaxMessageSize solved the issue. My questions is what should be the maximum limit of the message size? We just can not keep on increasing the
message size to resolve this issue. Is there any way to optimise this value in addition
with certain other values?
I'm trying to figure out how to continuously read a file and once there is a new line added, output the line. I'm doing this using a sleep thread however it just seems to blow through the whole file and exit the program.
Any suggestions what I'm doing wrong?
Here is my code:
import java.io.*;
import java.lang.*;
import java.util.*;
class jtail {
public static void main (String args[])
throws InterruptedException, IOException{
BufferedReader br = new BufferedReader(
new FileReader("\\\\server01\\data\\CommissionPlanLog.txt"));
String line = null;
while (br.nextLine ) {
line = br.readLine();
if (line == null) {
//wait until there is more of the file for us to read
Thread.sleep(1000);
}
else {
System.out.println(line);
}
}
} //end main
} //end class jtail
thanks in advance
UPDATE: I've since changed the line "while (br.nextLine ) {" to just "while (TRUE) {"
This in somewhat old, but I have used the mechanism and it works pretty well.
edit: link no longer works, but I found it in the internet archive
https://web.archive.org/web/20160510001134/http://www.informit.com/guides/content.aspx?g=java&seqNum=226
The trick is to use a java.io.RandomAccessFile, and periodically check if the file length is greater that your current file position. If it is, then you read the data. When you hit the length, you wait. wash, rinse, repeat.
I copied the code, just in case that new link stops working
package com.javasrc.tuning.agent.logfile;
import java.io.*;
import java.util.*;
/**
* A log file tailer is designed to monitor a log file and send notifications
* when new lines are added to the log file. This class has a notification
* strategy similar to a SAX parser: implement the LogFileTailerListener interface,
* create a LogFileTailer to tail your log file, add yourself as a listener, and
* start the LogFileTailer. It is your job to interpret the results, build meaningful
* sets of data, etc. This tailer simply fires notifications containing new log file lines,
* one at a time.
*/
public class LogFileTailer extends Thread
{
/**
* How frequently to check for file changes; defaults to 5 seconds
*/
private long sampleInterval = 5000;
/**
* The log file to tail
*/
private File logfile;
/**
* Defines whether the log file tailer should include the entire contents
* of the exising log file or tail from the end of the file when the tailer starts
*/
private boolean startAtBeginning = false;
/**
* Is the tailer currently tailing?
*/
private boolean tailing = false;
/**
* Set of listeners
*/
private Set listeners = new HashSet();
/**
* Creates a new log file tailer that tails an existing file and checks the file for
* updates every 5000ms
*/
public LogFileTailer( File file )
{
this.logfile = file;
}
/**
* Creates a new log file tailer
*
* #param file The file to tail
* #param sampleInterval How often to check for updates to the log file (default = 5000ms)
* #param startAtBeginning Should the tailer simply tail or should it process the entire
* file and continue tailing (true) or simply start tailing from the
* end of the file
*/
public LogFileTailer( File file, long sampleInterval, boolean startAtBeginning )
{
this.logfile = file;
this.sampleInterval = sampleInterval;
}
public void addLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.add( l );
}
public void removeLogFileTailerListener( LogFileTailerListener l )
{
this.listeners.remove( l );
}
protected void fireNewLogFileLine( String line )
{
for( Iterator i=this.listeners.iterator(); i.hasNext(); )
{
LogFileTailerListener l = ( LogFileTailerListener )i.next();
l.newLogFileLine( line );
}
}
public void stopTailing()
{
this.tailing = false;
}
public void run()
{
// The file pointer keeps track of where we are in the file
long filePointer = 0;
// Determine start point
if( this.startAtBeginning )
{
filePointer = 0;
}
else
{
filePointer = this.logfile.length();
}
try
{
// Start tailing
this.tailing = true;
RandomAccessFile file = new RandomAccessFile( logfile, "r" );
while( this.tailing )
{
try
{
// Compare the length of the file to the file pointer
long fileLength = this.logfile.length();
if( fileLength < filePointer )
{
// Log file must have been rotated or deleted;
// reopen the file and reset the file pointer
file = new RandomAccessFile( logfile, "r" );
filePointer = 0;
}
if( fileLength > filePointer )
{
// There is data to read
file.seek( filePointer );
String line = file.readLine();
while( line != null )
{
this.fireNewLogFileLine( line );
line = file.readLine();
}
filePointer = file.getFilePointer();
}
// Sleep for the specified interval
sleep( this.sampleInterval );
}
catch( Exception e )
{
}
}
// Close the file that we are tailing
file.close();
}
catch( Exception e )
{
e.printStackTrace();
}
}
}
If you're planning to implement this on a reasonable sized application where multiple objects might be interested in processing the new lines coming to the file, you might want to consider the Observer pattern.
The object reading from the file will notify each object subscribed to it as soon as a line has been processed.
This will allow you to keep logic well separated on the class where it's needed.
Also consider org.apache.commons.io.input.Tailer if you do not have the requirement of writing this from scratch.
The way your code is written now, you will not go through your while loop when your 'line==null' because you are checking to see that it has a next line before you even get into the loop.
Instead, try doing a while(true){ } loop. That way, you will always be looping through it, catching your pause cases, until you hit a condition that would cause the program to end.