JAL-3253 srcjar_unused/ moved to unused/
[jalview.git] / unused / srcjar_unused / org / apache / log4j / net / SocketHubAppender.java
diff --git a/unused/srcjar_unused/org/apache/log4j/net/SocketHubAppender.java b/unused/srcjar_unused/org/apache/log4j/net/SocketHubAppender.java
new file mode 100644 (file)
index 0000000..74d7186
--- /dev/null
@@ -0,0 +1,514 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.net;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.ObjectOutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.Vector;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.helpers.CyclicBuffer;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+  Sends {@link LoggingEvent} objects to a set of remote log servers,
+  usually a {@link SocketNode SocketNodes}.
+    
+  <p>Acts just like {@link SocketAppender} except that instead of
+  connecting to a given remote log server,
+  <code>SocketHubAppender</code> accepts connections from the remote
+  log servers as clients.  It can accept more than one connection.
+  When a log event is received, the event is sent to the set of
+  currently connected remote log servers. Implemented this way it does
+  not require any update to the configuration file to send data to
+  another remote log server. The remote log server simply connects to
+  the host and port the <code>SocketHubAppender</code> is running on.
+  
+  <p>The <code>SocketHubAppender</code> does not store events such
+  that the remote side will events that arrived after the
+  establishment of its connection. Once connected, events arrive in
+  order as guaranteed by the TCP protocol.
+
+  <p>This implementation borrows heavily from the {@link
+  SocketAppender}.
+
+  <p>The SocketHubAppender has the following characteristics:
+  
+  <ul>
+  
+  <p><li>If sent to a {@link SocketNode}, logging is non-intrusive as
+  far as the log event is concerned. In other words, the event will be
+  logged with the same time stamp, {@link org.apache.log4j.NDC},
+  location info as if it were logged locally.
+  
+  <p><li><code>SocketHubAppender</code> does not use a layout. It
+  ships a serialized {@link LoggingEvent} object to the remote side.
+  
+  <p><li><code>SocketHubAppender</code> relies on the TCP
+  protocol. Consequently, if the remote side is reachable, then log
+  events will eventually arrive at remote client.
+  
+  <p><li>If no remote clients are attached, the logging requests are
+  simply dropped.
+  
+  <p><li>Logging events are automatically <em>buffered</em> by the
+  native TCP implementation. This means that if the link to remote
+  client is slow but still faster than the rate of (log) event
+  production, the application will not be affected by the slow network
+  connection. However, if the network connection is slower then the
+  rate of event production, then the local application can only
+  progress at the network rate. In particular, if the network link to
+  the the remote client is down, the application will be blocked.
+  
+  <p>On the other hand, if the network link is up, but the remote
+  client is down, the client will not be blocked when making log
+  requests but the log events will be lost due to client
+  unavailability. 
+
+  <p>The single remote client case extends to multiple clients
+  connections. The rate of logging will be determined by the slowest
+  link.
+    
+  <p><li>If the JVM hosting the <code>SocketHubAppender</code> exits
+  before the <code>SocketHubAppender</code> is closed either
+  explicitly or subsequent to garbage collection, then there might
+  be untransmitted data in the pipe which might be lost. This is a
+  common problem on Windows based systems.
+  
+  <p>To avoid lost data, it is usually sufficient to {@link #close}
+  the <code>SocketHubAppender</code> either explicitly or by calling
+  the {@link org.apache.log4j.LogManager#shutdown} method before
+  exiting the application.
+  
+  </ul>
+     
+  @author Mark Womack */
+
+public class SocketHubAppender extends AppenderSkeleton {
+
+  /**
+     The default port number of the ServerSocket will be created on. */
+  static final int DEFAULT_PORT = 4560;
+  
+  private int port = DEFAULT_PORT;
+  private Vector oosList = new Vector();
+  private ServerMonitor serverMonitor = null;
+  private boolean locationInfo = false;
+  private CyclicBuffer buffer = null;
+  private String application;
+  private boolean advertiseViaMulticastDNS;
+  private ZeroConfSupport zeroConf;
+
+  /**
+   * The MulticastDNS zone advertised by a SocketHubAppender
+   */
+  public static final String ZONE = "_log4j_obj_tcpaccept_appender.local.";
+  private ServerSocket serverSocket;
+
+
+    public SocketHubAppender() { }
+
+  /**
+     Connects to remote server at <code>address</code> and <code>port</code>. */
+  public
+  SocketHubAppender(int _port) {
+    port = _port;
+    startServer();
+  }
+
+  /**
+     Set up the socket server on the specified port.  */
+  public
+  void activateOptions() {
+    if (advertiseViaMulticastDNS) {
+      zeroConf = new ZeroConfSupport(ZONE, port, getName());
+      zeroConf.advertise();
+    }
+    startServer();
+  }
+
+  /**
+     Close this appender. 
+     <p>This will mark the appender as closed and
+     call then {@link #cleanUp} method. */
+  synchronized
+  public
+  void close() {
+    if(closed) {
+        return;
+    }
+
+       LogLog.debug("closing SocketHubAppender " + getName());
+    this.closed = true;
+    if (advertiseViaMulticastDNS) {
+      zeroConf.unadvertise();
+    }
+    cleanUp();
+
+       LogLog.debug("SocketHubAppender " + getName() + " closed");
+  }
+
+  /**
+     Release the underlying ServerMonitor thread, and drop the connections
+     to all connected remote servers. */
+  public 
+  void cleanUp() {
+    // stop the monitor thread
+       LogLog.debug("stopping ServerSocket");
+    serverMonitor.stopMonitor();
+    serverMonitor = null;
+
+    // close all of the connections
+       LogLog.debug("closing client connections");
+    while (oosList.size() != 0) {
+      ObjectOutputStream oos = (ObjectOutputStream)oosList.elementAt(0);
+      if(oos != null) {
+        try {
+               oos.close();
+        } catch(InterruptedIOException e) {
+            Thread.currentThread().interrupt();
+            LogLog.error("could not close oos.", e);
+        } catch(IOException e) {
+            LogLog.error("could not close oos.", e);
+        }
+        
+        oosList.removeElementAt(0);     
+      }
+    }
+  }
+
+  /**
+    Append an event to all of current connections. */
+  public
+  void append(LoggingEvent event) {
+    if (event != null) {
+      // set up location info if requested
+      if (locationInfo) {
+        event.getLocationInformation();
+      }
+      if (application != null) {
+          event.setProperty("application", application);
+        } 
+        event.getNDC();
+        event.getThreadName();
+        event.getMDCCopy();
+        event.getRenderedMessage();
+        event.getThrowableStrRep();
+        
+      if (buffer != null) {
+        buffer.add(event);
+      }
+    }
+
+    // if no event or no open connections, exit now
+    if ((event == null) || (oosList.size() == 0)) {
+      return;
+    }
+
+       // loop through the current set of open connections, appending the event to each
+    for (int streamCount = 0; streamCount < oosList.size(); streamCount++) {           
+
+      ObjectOutputStream oos = null;
+      try {
+        oos = (ObjectOutputStream)oosList.elementAt(streamCount);
+      }
+      catch (ArrayIndexOutOfBoundsException e) {
+        // catch this, but just don't assign a value
+        // this should not really occur as this method is
+        // the only one that can remove oos's (besides cleanUp).
+      }
+      
+      // list size changed unexpectedly? Just exit the append.
+      if (oos == null) {
+        break;
+    }
+        
+      try {
+       oos.writeObject(event);
+       oos.flush();
+       // Failing to reset the object output stream every now and
+       // then creates a serious memory leak.
+       // right now we always reset. TODO - set up frequency counter per oos?
+       oos.reset();
+      }
+      catch(IOException e) {
+        if (e instanceof InterruptedIOException) {
+            Thread.currentThread().interrupt();
+        }
+          // there was an io exception so just drop the connection
+       oosList.removeElementAt(streamCount);
+       LogLog.debug("dropped connection");
+       
+       // decrement to keep the counter in place (for loop always increments)
+       streamCount--;
+      }
+    }
+  }
+  
+  /**
+     The SocketHubAppender does not use a layout. Hence, this method returns
+     <code>false</code>. */
+  public
+  boolean requiresLayout() {
+    return false;
+  }
+  
+  /**
+     The <b>Port</b> option takes a positive integer representing
+     the port where the server is waiting for connections. */
+  public
+  void setPort(int _port) {
+    port = _port;
+       }
+
+  /**
+   * The <b>App</b> option takes a string value which should be the name of the application getting logged. If property was already set (via system
+   * property), don't set here.
+   */
+  public 
+  void setApplication(String lapp) {
+    this.application = lapp;
+  }
+
+  /**
+   * Returns value of the <b>Application</b> option.
+   */
+  public 
+  String getApplication() {
+    return application;
+  }
+  
+  /**
+     Returns value of the <b>Port</b> option. */
+  public
+  int getPort() {
+    return port;
+  }
+
+  /**
+   * The <b>BufferSize</b> option takes a positive integer representing the number of events this appender will buffer and send to newly connected
+   * clients.
+   */
+  public 
+  void setBufferSize(int _bufferSize) {
+    buffer = new CyclicBuffer(_bufferSize);
+  }
+
+  /**
+   * Returns value of the <b>bufferSize</b> option.
+   */
+  public 
+  int getBufferSize() {
+    if (buffer == null) {
+      return 0;
+    } else {
+      return buffer.getMaxSize();
+    }
+  }
+  
+  /**
+     The <b>LocationInfo</b> option takes a boolean value. If true,
+     the information sent to the remote host will include location
+     information. By default no location information is sent to the server. */
+  public
+  void setLocationInfo(boolean _locationInfo) {
+    locationInfo = _locationInfo;
+  }
+  
+  /**
+     Returns value of the <b>LocationInfo</b> option. */
+  public
+  boolean getLocationInfo() {
+    return locationInfo;
+  }
+
+  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
+    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
+  }
+
+  public boolean isAdvertiseViaMulticastDNS() {
+    return advertiseViaMulticastDNS;
+  }
+
+  /**
+    Start the ServerMonitor thread. */
+  private
+  void startServer() {
+    serverMonitor = new ServerMonitor(port, oosList);
+  }
+  
+  /**
+   * Creates a server socket to accept connections.
+   * @param socketPort port on which the socket should listen, may be zero.
+   * @return new socket.
+   * @throws IOException IO error when opening the socket. 
+   */
+  protected ServerSocket createServerSocket(final int socketPort) throws IOException {
+      return new ServerSocket(socketPort);
+  }
+
+  /**
+    This class is used internally to monitor a ServerSocket
+    and register new connections in a vector passed in the
+    constructor. */
+  private class ServerMonitor implements Runnable {
+    private int port;
+    private Vector oosList;
+    private boolean keepRunning;
+    private Thread monitorThread;
+    
+    /**
+      Create a thread and start the monitor. */
+    public
+    ServerMonitor(int _port, Vector _oosList) {
+      port = _port;
+      oosList = _oosList;
+      keepRunning = true;
+      monitorThread = new Thread(this);
+      monitorThread.setDaemon(true);
+      monitorThread.setName("SocketHubAppender-Monitor-" + port);
+      monitorThread.start();
+    }
+    
+    /**
+      Stops the monitor. This method will not return until
+      the thread has finished executing. */
+    public synchronized void stopMonitor() {
+      if (keepRunning) {
+       LogLog.debug("server monitor thread shutting down");
+        keepRunning = false;
+        try {
+            if (serverSocket != null) {
+                serverSocket.close();
+                serverSocket = null;
+            }
+        } catch (IOException ioe) {}
+
+        try {
+          monitorThread.join();
+        }
+        catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+          // do nothing?
+        }
+        
+        // release the thread
+        monitorThread = null;
+       LogLog.debug("server monitor thread shut down");
+      }
+    }
+    
+    private 
+    void sendCachedEvents(ObjectOutputStream stream) throws IOException {
+      if (buffer != null) {
+        for (int i = 0; i < buffer.length(); i++) {
+          stream.writeObject(buffer.get(i));
+        }
+        stream.flush();
+        stream.reset();
+      }
+    }
+
+    /**
+      Method that runs, monitoring the ServerSocket and adding connections as
+      they connect to the socket. */
+    public
+    void run() {
+      serverSocket = null;
+      try {
+        serverSocket = createServerSocket(port);
+        serverSocket.setSoTimeout(1000);
+      }
+      catch (Exception e) {
+        if (e instanceof InterruptedIOException || e instanceof InterruptedException) {
+            Thread.currentThread().interrupt();
+        }
+        LogLog.error("exception setting timeout, shutting down server socket.", e);
+        keepRunning = false;
+        return;
+      }
+
+      try {
+       try {
+               serverSocket.setSoTimeout(1000);
+       }
+       catch (SocketException e) {
+          LogLog.error("exception setting timeout, shutting down server socket.", e);
+          return;
+       }
+      
+       while (keepRunning) {
+          Socket socket = null;
+          try {
+            socket = serverSocket.accept();
+          }
+          catch (InterruptedIOException e) {
+            // timeout occurred, so just loop
+          }
+          catch (SocketException e) {
+            LogLog.error("exception accepting socket, shutting down server socket.", e);
+            keepRunning = false;
+          }
+          catch (IOException e) {
+            LogLog.error("exception accepting socket.", e);
+          }
+               
+          // if there was a socket accepted
+          if (socket != null) {
+            try {
+              InetAddress remoteAddress = socket.getInetAddress();
+              LogLog.debug("accepting connection from " + remoteAddress.getHostName() 
+                          + " (" + remoteAddress.getHostAddress() + ")");
+                       
+              // create an ObjectOutputStream
+              ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
+              if (buffer != null && buffer.length() > 0) {
+                sendCachedEvents(oos);
+              }
+                   
+              // add it to the oosList.  OK since Vector is synchronized.
+              oosList.addElement(oos);
+            } catch (IOException e) {
+              if (e instanceof InterruptedIOException) {
+                    Thread.currentThread().interrupt();
+              }
+              LogLog.error("exception creating output stream on socket.", e);
+            }
+          }
+        }
+      }
+      finally {
+       // close the socket
+       try {
+               serverSocket.close();
+       } catch(InterruptedIOException e) {
+            Thread.currentThread().interrupt();  
+        } catch (IOException e) {
+               // do nothing with it?
+       }
+      }
+    }
+  }
+}
+