/*
* Copyright (c) 2007-2012 Eike Stepper (Berlin, Germany) and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.util.om.trace;
import org.eclipse.net4j.internal.util.bundle.OM;
import org.eclipse.net4j.util.io.IOUtil;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.EventObject;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* A server that {@link RemoteTraceHandler agents} can connect to and that passes the received {@link OMTraceHandlerEvent trace events}
* to {@link #addListener(Listener) registered} {@link Listener listeners}.
*
* @author Eike Stepper
*/
public class RemoteTraceServer
{
public static final String DEFAULT_ADDRESS = "0.0.0.0"; //$NON-NLS-1$
public static final int DEFAULT_PORT = 2035;
public static final int ANY_PORT = 0;
private static long lastEventID;
private int port;
private String address;
private ServerSocket serverSocket;
private Queue<Listener> listeners = new ConcurrentLinkedQueue<Listener>();
public RemoteTraceServer() throws IOException
{
this(DEFAULT_PORT);
}
public RemoteTraceServer(int port) throws IOException
{
this(port, DEFAULT_ADDRESS);
}
public RemoteTraceServer(int port, String address) throws IOException
{
this.port = port;
this.address = address;
serverSocket = bind();
start();
}
/**
* @since 3.0
*/
public void start()
{
new Thread("RemoteTraceServer") //$NON-NLS-1$
{
@Override
public void run()
{
handleConnections();
}
}.start();
}
public void addListener(Listener listener)
{
if (!listeners.contains(listener))
{
listeners.add(listener);
}
}
public void removeListener(Listener listener)
{
listeners.remove(listener);
}
public Exception close()
{
try
{
serverSocket.close();
return null;
}
catch (IOException ex)
{
OM.LOG.error(ex);
return ex;
}
}
protected ServerSocket bind() throws IOException
{
InetAddress addr = InetAddress.getByName(address);
return new ServerSocket(port, 5, addr);
}
protected void handleConnections()
{
for (;;)
{
try
{
final Socket socket = serverSocket.accept();
new Thread()
{
@Override
public void run()
{
handleSession(socket);
}
}.start();
}
catch (IOException ex)
{
if (!serverSocket.isClosed())
{
IOUtil.print(ex);
}
break;
}
}
}
protected void handleSession(Socket socket)
{
try
{
InputStream inputStream = socket.getInputStream();
DataInputStream in = new DataInputStream(inputStream);
for (;;)
{
handleTrace(in);
}
}
catch (IOException ex)
{
IOUtil.print(ex);
}
}
protected void handleTrace(DataInputStream in) throws IOException
{
Event event = new Event(this);
event.timeStamp = in.readLong();
event.agentID = in.readUTF();
event.bundleID = in.readUTF();
event.tracerName = in.readUTF();
event.context = in.readUTF();
event.message = in.readUTF();
if (in.readBoolean())
{
event.throwable = in.readUTF();
int size = in.readInt();
event.stackTrace = new StackTraceElement[size];
for (int i = 0; i < size; i++)
{
String className = in.readUTF();
String methodName = in.readUTF();
String fileName = in.readUTF();
int lineNumber = in.readInt();
event.stackTrace[i] = new StackTraceElement(className, methodName, fileName, lineNumber);
}
}
fireEvent(event);
}
protected void fireEvent(Event event)
{
for (Listener listener : listeners)
{
try
{
listener.notifyRemoteTrace(event);
}
catch (RuntimeException ex)
{
IOUtil.print(ex);
}
}
}
/**
* A trace event being passed by a remote trace {@link RemoteTraceServer server} to
* {@link RemoteTraceServer#addListener(Listener) registered} {@link Listener listeners}.
*
* @author Eike Stepper
*/
public static class Event extends EventObject
{
private static final long serialVersionUID = 1L;
private long id;
long timeStamp;
/**
* @since 3.2
*/
protected String agentID;
/**
* @since 3.2
*/
protected String bundleID;
/**
* @since 3.2
*/
protected String tracerName;
/**
* @since 3.2
*/
protected String context;
/**
* @since 3.2
*/
protected String message;
/**
* @since 3.2
*/
protected String throwable;
/**
* @since 3.2
*/
protected StackTraceElement[] stackTrace;
/**
* @since 3.2
*/
protected Event(RemoteTraceServer server)
{
super(server);
id = ++lastEventID;
}
public RemoteTraceServer getRemoteTraceServer()
{
return (RemoteTraceServer)source;
}
public long getID()
{
return id;
}
public long getTimeStamp()
{
return timeStamp;
}
public String getAgentID()
{
return agentID;
}
public String getBundleID()
{
return bundleID;
}
public String getContext()
{
return context;
}
public String getMessage()
{
return message;
}
public StackTraceElement[] getStackTrace()
{
return stackTrace;
}
public String getThrowable()
{
return throwable;
}
public String getTracerName()
{
return tracerName;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("TraceEvent[agentID="); //$NON-NLS-1$
builder.append(agentID);
builder.append(", bundleID="); //$NON-NLS-1$
builder.append(bundleID);
builder.append(", tracerName="); //$NON-NLS-1$
builder.append(tracerName);
builder.append(", context="); //$NON-NLS-1$
builder.append(context);
builder.append(", message="); //$NON-NLS-1$
builder.append(message);
builder.append(", throwable="); //$NON-NLS-1$
builder.append(throwable);
builder.append(", stackTrace="); //$NON-NLS-1$
builder.append(stackTrace);
builder.append("]"); //$NON-NLS-1$
return builder.toString();
}
public String getText(int index)
{
switch (index)
{
case 0:
return Long.toString(id);
case 1:
return new Date(timeStamp).toString();
case 2:
return agentID;
case 3:
return bundleID;
case 4:
return tracerName;
case 5:
return context;
case 6:
return message;
case 7:
return throwable;
}
throw new IllegalArgumentException("Invalid index: " + index); //$NON-NLS-1$
}
public boolean hasError()
{
return throwable != null && throwable.length() != 0 //
|| stackTrace != null && stackTrace.length != 0;
}
}
/**
* Listens to {@link Event trace events} being passed by a remote trace {@link RemoteTraceServer server}.
*
* @author Eike Stepper
* @see RemoteTraceServer#addListener(Listener)
* @see RemoteTraceServer#removeListener(Listener)
* @see PrintListener
*/
public interface Listener
{
public void notifyRemoteTrace(Event event);
}
/**
* A {@link Listener listener} that appends {@link Event trace events} to a {@link #getStream() print stream}.
*
* @author Eike Stepper
*/
public static class PrintListener implements Listener
{
public static final PrintListener CONSOLE = new PrintListener();
private PrintStream stream;
public PrintListener(PrintStream stream)
{
this.stream = stream;
}
protected PrintListener()
{
this(IOUtil.OUT());
}
/**
* @since 3.2
*/
public PrintStream getStream()
{
return stream;
}
public void notifyRemoteTrace(Event event)
{
stream.println("[TRACE] " + event.getAgentID()); //$NON-NLS-1$
stream.println(event.getBundleID());
stream.println(event.getTracerName());
stream.println(event.getContext());
stream.println(event.getMessage());
String throwable = event.getThrowable();
if (throwable != null && throwable.length() != 0)
{
stream.println(throwable);
}
StackTraceElement[] stackTrace = event.getStackTrace();
if (stackTrace != null)
{
for (StackTraceElement element : stackTrace)
{
stream.print(element.getClassName());
stream.print("." + element.getMethodName()); //$NON-NLS-1$
stream.print("(" + element.getFileName()); //$NON-NLS-1$
stream.print(":" + element.getLineNumber()); //$NON-NLS-1$
stream.println(")"); //$NON-NLS-1$
}
}
stream.println();
}
}
}