// BlogBridge -- RSS feed reader, manager, and web based service
// Copyright (C) 2002-2006 by R. Pito Salas
//
// This program is free software; you can redistribute it and/or modify it under
// the terms of the GNU General Public License as published by the Free Software Foundation;
// either version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
// without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with this program;
// if not, write to the Free Software Foundation, Inc., 59 Temple Place,
// Suite 330, Boston, MA 02111-1307 USA
//
// Contact: R. Pito Salas
// mailto:pitosalas@users.sourceforge.net
// More information: about BlogBridge
// http://www.blogbridge.com
// http://sourceforge.net/projects/blogbridge
//
// $Id: IPC.java,v 1.6 2007/02/14 09:46:22 spyromus Exp $
//
package com.salas.bb.utils.ipc;
import EDU.oswego.cs.dl.util.concurrent.CopyOnWriteArrayList;
import java.io.*;
import java.nio.channels.FileChannel;
import java.nio.ByteBuffer;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.List;
import java.util.Iterator;
import java.net.URL;
import java.net.MalformedURLException;
import java.text.MessageFormat;
import com.salas.bb.utils.StringUtils;
/**
* File-based IPC implementation.
*/
public class IPC
{
private final static Logger LOG = Logger.getLogger(IPC.class.getName());
private final File socketFile;
private final FileChannel socketChannel;
private final List listeners;
private SocketListener socketListener;
/**
* Creates IPC channel for a given socket file. All the data in the file
* is removed.
*
* @param socketFile socket file.
*
* @throws java.io.IOException in case when it's not possible to create a file.
*/
public IPC(File socketFile)
throws IOException
{
if (socketFile == null) throw new IllegalArgumentException("Socket file should be specified.");
this.socketFile = socketFile;
this.listeners = new CopyOnWriteArrayList();
configureSocketFile();
socketChannel = new FileInputStream(socketFile).getChannel();
configureSocketListener();
}
/**
* Initializes socket file.
*
* @throws IOException if failed to create the socket file.
*/
private void configureSocketFile()
throws IOException
{
socketFile.delete();
socketFile.createNewFile();
socketFile.deleteOnExit();
}
/**
* Creates socket listener thread.
*/
private void configureSocketListener()
{
socketListener = new SocketListener();
socketListener.start();
}
/**
* Closes active socket channel which lets the listener thread terminate.
*/
public void close()
{
try
{
if (socketChannel != null && socketChannel.isOpen())
{
socketChannel.close();
socketListener = null;
}
} catch (IOException e)
{
LOG.log(Level.WARNING, "Failed to close socket channel.", e);
}
}
/**
* Sends command using the given socket file.
*
* @param socketFile socket file.
* @param cmd command.
* @param args command arguments.
*
* @return <code>TRUE</code> if sent.
*/
public static boolean sendCommand(File socketFile, String cmd, String[] args)
{
boolean sent = false;
if (socketFile.exists() && !StringUtils.isEmpty(cmd) && args != null)
{
String command = cmd + " " + StringUtils.join(args, " ") + "\n";
try
{
RandomAccessFile raf = new RandomAccessFile(socketFile, "rws");
raf.seek(raf.length());
raf.getChannel().write(ByteBuffer.wrap(command.getBytes()));
raf.close();
sent = true;
} catch (IOException e)
{
LOG.log(Level.WARNING, "Failed to send IPC command.", e);
}
}
return sent;
}
/**
* Adds a listener to the list.
*
* @param l listener.
*/
public void addListener(IIPCListener l)
{
if (!listeners.contains(l)) listeners.add(l);
}
/**
* Removes a listener from the list.
*
* @param l listener.
*/
public void removeListener(IIPCListener l)
{
listeners.remove(l);
}
/**
* Fires subscribe to URL event.
*
* @param url URL.
*/
public void fireSubscribe(URL url)
{
Iterator it = listeners.iterator();
while (it.hasNext())
{
IIPCListener l = (IIPCListener)it.next();
try
{
l.subscribe(url);
} catch (Throwable e)
{
LOG.log(Level.SEVERE, "Unhandled exception", e);
}
}
}
/**
* Invoked when new command string arrives.
*
* @param cmd command.
*/
private void onCommand(String cmd)
{
// Collapse multi-spaces
cmd = cmd.replaceAll("\\s+", " ").trim();
// Break the command into pieces
String[] chunks = cmd.split(" ");
if (chunks.length > 0)
{
String op = chunks[0];
if ("subscribe".equalsIgnoreCase(op))
{
if (chunks.length == 2)
{
String arg = chunks[1];
URL url = argToURL(arg);
if (url != null)
{
fireSubscribe(url);
} else
{
LOG.warning("Invalid URL '" + arg + "' for 'subscribe' command");
}
} else
{
LOG.warning(MessageFormat.format(
"Invalid number of arguments for ''subscribe'': {0} expected 1",
new Object[] { new Integer(chunks.length - 1) }));
}
}
}
}
/**
* Converts command-line argument into the URL if it's some valid URL or the path
* to a file.
*
* @param arg argument.
*
* @return URL.
*/
public static URL argToURL(String arg)
{
URL url = null;
if (!StringUtils.isEmpty(arg))
{
arg = arg.trim();
try
{
if (arg.matches("^(file|https?):.*"))
{
url = new URL(arg);
} else
{
File f = new File(arg);
if (f.exists() && f.isFile()) url = f.toURI().toURL();
}
} catch (MalformedURLException e)
{
url = null;
}
}
return url;
}
/**
* Listener of socket commands.
*/
private class SocketListener extends Thread
{
/** Time in ms to sleep between socket channel checks. */
private static final int SLEEP_TIME = 1000;
/** The size of a buffer for commands. */
private static final int BUFFER_SIZE = 2048;
/**
* Initializes socket listener thread.
*/
public SocketListener()
{
super("IPC Socket Listener");
setDaemon(true);
}
public void run()
{
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
buffer.clear();
int bytes;
while (socketChannel != null && socketChannel.isOpen())
{
try
{
bytes = socketChannel.read(buffer);
// socketChannel.truncate(0);
if (bytes != -1)
{
byte[] strb = new byte[bytes];
buffer.position(0);
buffer.get(strb, 0, bytes);
String str = new String(strb);
String[] cmds = str.split("\\n");
for (int i = 0; i < cmds.length; i++)
{
onCommand(cmds[i]);
}
buffer.clear();
} else Thread.sleep(SLEEP_TIME);
} catch (IOException e)
{
LOG.log(Level.SEVERE, "Failed to work with socket file.", e);
} catch (InterruptedException e)
{
LOG.warning("Interruption.");
}
}
}
}
}