package org.apache.log4j.contrib;

import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.TTCCLayout;
import org.apache.log4j.spi.LoggingEvent;


/** <p>The Log4jServlet allows to follow the logging output
 * from a remote machine by accessing the servlet with a
 * browser. A running servlet instance adds an
 * {@link org.apache.log4j.Appender} to the
 * root {@link org.apache.log4j.Logger} which sends output
 * to the servlets response output stream. If the client
 * terminates the request, the appender is removed.</p>
 *
 * @author <a href="mailto:joe@ispsoft.de">Jochen Wiedmann</a>
 */
public class Log4jServlet extends AuthenticatingServlet {
  private static class ServletAppender extends AppenderSkeleton {
    private class Listener {
      private final Writer writer;
      private boolean isRunning = true;
      private boolean isActive;

      public Listener(Writer pWriter) {
        writer = pWriter;
      }

      public void write(String pMessage) {
        if (isRunning()) {
          try {
            setActive(true);
            writer.write(pMessage);
            writer.flush();
          } catch (IOException e) {
            removeListener(this);
          }
        }
      }

      public void setRunning(boolean pRunning) {
        isRunning = pRunning;
      }

      public boolean isRunning() {
        return isRunning;
      }

      public void setActive(boolean pActive) {
        isActive = true;
      }

      public boolean isActive() {
        return isActive;
      }
    }

    private List listeners = new ArrayList();

    public synchronized Listener addListener(Writer pWriter) {
      Listener result = new Listener(pWriter);
      listeners.add(result);
      return result;
    }

    public synchronized void removeListener(Listener pListener) {
      listeners.remove(pListener);
      pListener.setRunning(false);
      synchronized(pListener) {
        pListener.notify();
      }
    }

    private synchronized void write(String pMessage) {
      for (int i = 0;  i < listeners.size();  i++) {
        ((Listener) listeners.get(i)).write(pMessage);
      }
    }

    protected void append(LoggingEvent pEvent) {
      write(super.getLayout().format(pEvent));
    }

    public synchronized void close() {
      while (listeners.size() > 0) {
        removeListener((Listener) listeners.get(0));
      }
    }

    public boolean requiresLayout() {
      return true;
    }

    public synchronized boolean hasListeners() {
      return !listeners.isEmpty();
    }
  }

  private static final Logger logger = Logger.getLogger(Log4jServlet.class);
  static {
    BasicConfigurator.configure();
  }
  private static ServletAppender currentAppender;

  private synchronized static ServletAppender.Listener attach(Writer pWriter) {
    if (currentAppender == null) {
      currentAppender = new ServletAppender();
      currentAppender.setLayout(new TTCCLayout());
      Logger.getRootLogger().addAppender(currentAppender);
    }
    ServletAppender.Listener result = currentAppender.addListener(pWriter);
    return result;
  }

  private synchronized static void detach(ServletAppender.Listener pListener) {
    pListener.setRunning(false);
    if (currentAppender != null) {
      currentAppender.removeListener(pListener);
    }
    if (!currentAppender.hasListeners()) {
      Logger.getRootLogger().removeAppender(currentAppender);
      currentAppender = null;
    }
  }

  /** <p>Creates a new instance of Log4jServlet.</p>
   */
  public Log4jServlet() {
  }

  public void run(HttpServletRequest pRequest, HttpServletResponse pResponse,
                   boolean usePost) throws IOException {
    int timeout = 900;
    String t = pRequest.getParameter("timeout");
    if (t != null) {
      try { timeout = Integer.parseInt(t); } catch (Exception e) {}
    }
    
    logger.debug("doGet: -> ");
    pResponse.setContentType("text/html");
    Writer w = new OutputStreamWriter(pResponse.getOutputStream());
    w.write("<html><head><title>Log4jServlet</title></head>\n");
    w.write("<body><h1>Log4jServlet</h1>\n");
    w.write("<form action=\"" + pRequest.getRequestURI() + "\" method=\"" +
            (usePost ? "POST" : "GET") + "\">\n");
    w.write("<table>\n");
    w.write("<tr><th>Timeout:</th><td><input name=\"timeout\" value=\"" + timeout + "\"></input></td>");
    w.write("<td><input type=\"submit\" value=\"Refresh\"></input></td></tr>");
    w.write("</table>\n");
    w.write("</form>\n");
    w.write("<pre>\n");
    w.flush();
    
    ServletAppender.Listener listener = null;
    boolean haveTimeout = false;
    try {
      listener = attach(w);
      logger.debug("doGet: Created listener " + listener);
      while (listener.isRunning()) {
        synchronized(listener) {
          try {
            logger.debug("doGet: Waiting " + timeout + " seconds for listener " + listener + " ...");
            listener.setActive(false);
            listener.wait(timeout * 1000);
            if (listener.isRunning()) {
              if (!listener.isActive()) {
                haveTimeout = true;
                break;
              }
            } else {
              logger.debug("doGet: Received notification for listener " + listener);
            }
          } catch (InterruptedException e) {
            logger.error(e.getMessage(), e);
          }
        }
      }
    } finally {
      if (listener != null) {
        detach(listener);
      }
    }

    w.write("</pre>\n");
    if (haveTimeout) {
      w.write("Timeout, closing the connection.\n");
    }
    w.write("</body></html>\n");
    w.close();
    logger.debug("doGet: <- ");
  }
}


