/*
 * 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.receivers.net;

import org.apache.log4j.component.plugins.Pauseable;
import org.apache.log4j.component.plugins.Plugin;
import org.apache.log4j.component.plugins.Receiver;
import org.apache.log4j.net.ZeroConfSupport;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.LoggingEvent;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Vector;


/**
  XMLSocketReceiver receives a remote logging event via XML on a configured
  socket and "posts" it to a LoggerRepository as if the event were
  generated locally. This class is designed to receive events from
  the XMLSocketAppender class (or classes that send compatible events).
  <p>
  This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging
  XMLFormatter (via the org.apache.log4j.spi.Decoder interface).
  <p>
  By default, log4j's XMLLayout is supported (no need to specify a decoder in that case).
  <p>
  To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param
  of org.apache.log4j.xml.UtilLoggingXMLDecoder.
  <p>
  Once the event has been "posted", it will be handled by the
  appenders currently configured in the LoggerRespository.

  @author Mark Womack
  @author Scott Deboy <sdeboy@apache.org>

*/
public class XMLSocketReceiver extends Receiver implements Runnable, PortBased, Pauseable {
  private boolean paused;
  //default to log4j xml decoder
  protected String decoder = "org.apache.log4j.xml.XMLDecoder";
  private ServerSocket serverSocket;
  private List socketList = new Vector();
  private Thread rThread;
  public static final int DEFAULT_PORT = 4448;
  protected int port = DEFAULT_PORT;
  private boolean advertiseViaMulticastDNS;
  private ZeroConfSupport zeroConf;

  /**
   * The MulticastDNS zone advertised by an XMLSocketReceiver
   */
  public static final String ZONE = "_log4j_xml_tcpaccept_receiver.local.";

  /*
   * Log4j doesn't provide an XMLSocketAppender, but the MulticastDNS zone that should be advertised by one is:
   * _log4j_xml_tcpconnect_appender.local.
   */

  public XMLSocketReceiver() {
  }

  public XMLSocketReceiver(int _port) {
    port = _port;
  }

  public XMLSocketReceiver(int _port, LoggerRepository _repository) {
    port = _port;
    repository = _repository;
  }

  /**
    Get the port to receive logging events on. */
  public int getPort() {
    return port;
  }

  /**
    Set the port to receive logging events on. */
  public void setPort(int _port) {
    port = _port;
  }

  public String getDecoder() {
    return decoder;
  }

  /**
   *Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file.
   */
  public void setDecoder(String _decoder) {
    decoder = _decoder;
  }

  public boolean isPaused() {
    return paused;
  }

  public void setPaused(boolean b) {
    paused = b;
  }

  /**
   * Returns true if the receiver is the same class and they are
   * configured for the same properties, and super class also considers
   * them to be equivalent. This is used by PluginRegistry when determining
   * if the a similarly configured receiver is being started.
   * 
   * @param testPlugin The plugin to test equivalency against.
   * @return boolean True if the testPlugin is equivalent to this plugin.
   */
  public boolean isEquivalent(Plugin testPlugin) {
    if ((testPlugin != null) && testPlugin instanceof XMLSocketReceiver) {
      XMLSocketReceiver sReceiver = (XMLSocketReceiver) testPlugin;

      return (port == sReceiver.getPort() && super.isEquivalent(testPlugin));
    }

    return false;
  }

  public int hashCode() {
  	
  	int result = 37 * (repository != null? repository.hashCode():0);
  	result = result * 37 + port;
  	return (result * 37 + (getName() != null? getName().hashCode():0));
  }

  /**
    Sets the flag to indicate if receiver is active or not.
   @param b new value
   */
  protected synchronized void setActive(final boolean b) {
    active = b;
  }

  /**
    Starts the SocketReceiver with the current options. */
  public void activateOptions() {
    if (!isActive()) {
      rThread = new Thread(this);
      rThread.setDaemon(true);
      rThread.start();

      if (advertiseViaMulticastDNS) {
        zeroConf = new ZeroConfSupport(ZONE, port, getName());
        zeroConf.advertise();
      }

      active = true;
    }
  }

  public void setAdvertiseViaMulticastDNS(boolean advertiseViaMulticastDNS) {
    this.advertiseViaMulticastDNS = advertiseViaMulticastDNS;
  }

  public boolean isAdvertiseViaMulticastDNS() {
    return advertiseViaMulticastDNS;
  }

  /**
    Called when the receiver should be stopped. Closes the
    server socket and all of the open sockets. */
  public synchronized void shutdown() {
    // mark this as no longer running
    active = false;

    if (rThread != null) {
      rThread.interrupt();
      rThread = null;
    }
    doShutdown();
  }

    /**
     * Does the actual shutting down by closing the server socket
     * and any connected sockets that have been created.
     */
    private synchronized void doShutdown() {
      active = false;

      getLogger().debug("{} doShutdown called", getName());

      // close the server socket
      closeServerSocket();

      // close all of the accepted sockets
      closeAllAcceptedSockets();

      if (advertiseViaMulticastDNS) {
          zeroConf.unadvertise();
      }
    }

    /**
      * Closes the server socket, if created.
      */
     private void closeServerSocket() {
       getLogger().debug("{} closing server socket", getName());

       try {
         if (serverSocket != null) {
           serverSocket.close();
         }
       } catch (Exception e) {
         // ignore for now
       }

       serverSocket = null;
     }

    /**
      * Closes all the connected sockets in the List.
      */
     private synchronized void closeAllAcceptedSockets() {
       for (int x = 0; x < socketList.size(); x++) {
         try {
           ((Socket) socketList.get(x)).close();
         } catch (Exception e) {
           // ignore for now
         }
       }

       // clear member variables
       socketList.clear();
     }

  /**
    Loop, accepting new socket connections. */
  public void run() {
      /**
        * Ensure we start fresh.
        */
    getLogger().debug("performing socket cleanup prior to entering loop for {}",  name);
    closeServerSocket();
    closeAllAcceptedSockets();
    getLogger().debug("socket cleanup complete for {}", name);       
    active = true;

    // start the server socket
    try {
      serverSocket = new ServerSocket(port);
    } catch (Exception e) {
      getLogger().error(
        "error starting SocketReceiver (" + this.getName()
        + "), receiver did not start", e);
      active = false;
      doShutdown();

      return;
    }

    Socket socket = null;

    try {
      getLogger().debug("in run-about to enter while isactiveloop");

      active = true;

      while (!rThread.isInterrupted()) {
        // if we have a socket, start watching it
        if (socket != null) {
          getLogger().debug("socket not null - creating and starting socketnode");
          socketList.add(socket);

          XMLSocketNode node = new XMLSocketNode(decoder, socket, this);
          node.setLoggerRepository(this.repository);
          new Thread(node).start();
          socket = null;
        }

        getLogger().debug("waiting to accept socket");

        // wait for a socket to open, then loop to start it
        socket = serverSocket.accept();
        getLogger().debug("accepted socket");
      }

      // socket not watched because we a no longer running
      // so close it now.
      if (socket != null) {
        socket.close();
      }
    } catch (Exception e) {
      getLogger().warn(
        "socket server disconnected, stopping");
    }
  }

  /* (non-Javadoc)
   * @see org.apache.log4j.plugins.Receiver#doPost(org.apache.log4j.spi.LoggingEvent)
   */
  public void doPost(LoggingEvent event) {
    if(!isPaused()){
      super.doPost(event);
    }
  }


}
