/*
 * 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.File;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Hashtable;

import org.apache.log4j.Hierarchy;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.spi.LoggerRepository;
import org.apache.log4j.spi.RootLogger;

/**
 * A {@link SocketNode} based server that uses a different hierarchy for each
 * client.
 * 
 * <pre>
   <b>Usage:</b> java org.apache.log4j.net.SocketServer port configFile configDir

   where <b>port</b> is a part number where the server listens,
         <b>configFile</b> is a configuration file fed to the {@link PropertyConfigurator} and
         <b>configDir</b> is a path to a directory containing configuration files, possibly one for each client host.
 * </pre>
 * 
 * <p>
 * The <code>configFile</code> is used to configure the log4j default hierarchy
 * that the <code>SocketServer</code> will use to report on its actions.
 * 
 * <p>
 * When a new connection is opened from a previously unknown host, say
 * <code>foo.bar.net</code>, then the <code>SocketServer</code> will search for
 * a configuration file called <code>foo.bar.net.lcf</code> under the directory
 * <code>configDir</code> that was passed as the third argument. If the file can
 * be found, then a new hierarchy is instantiated and configured using the
 * configuration file <code>foo.bar.net.lcf</code>. If and when the host
 * <code>foo.bar.net</code> opens another connection to the server, then the
 * previously configured hierarchy is used.
 * 
 * <p>
 * In case there is no file called <code>foo.bar.net.lcf</code> under the
 * directory <code>configDir</code>, then the <em>generic</em> hierarchy is
 * used. The generic hierarchy is configured using a configuration file called
 * <code>generic.lcf</code> under the <code>configDir</code> directory. If no
 * such file exists, then the generic hierarchy will be identical to the log4j
 * default hierarchy.
 * 
 * <p>
 * Having different client hosts log using different hierarchies ensures the
 * total independence of the clients with respect to their logging settings.
 * 
 * <p>
 * Currently, the hierarchy that will be used for a given request depends on the
 * IP address of the client host. For example, two separate applicatons running
 * on the same host and logging to the same server will share the same
 * hierarchy. This is perfectly safe except that it might not provide the right
 * amount of independence between applications. The <code>SocketServer</code> is
 * intended as an example to be enhanced in order to implement more elaborate
 * policies.
 * 
 * 
 * @author Ceki G&uuml;lc&uuml;
 * 
 * @since 1.0
 */

public class SocketServer {

    static String GENERIC = "generic";
    static String CONFIG_FILE_EXT = ".lcf";

    static Logger cat = Logger.getLogger(SocketServer.class);
    static SocketServer server;
    static int port;

    // key=inetAddress, value=hierarchy
    Hashtable hierarchyMap;
    LoggerRepository genericHierarchy;
    File dir;

    public static void main(String argv[]) {
	if (argv.length == 3)
	    init(argv[0], argv[1], argv[2]);
	else
	    usage("Wrong number of arguments.");

	try {
	    cat.info("Listening on port " + port);
	    ServerSocket serverSocket = new ServerSocket(port);
	    while (true) {
		cat.info("Waiting to accept a new client.");
		Socket socket = serverSocket.accept();
		InetAddress inetAddress = socket.getInetAddress();
		cat.info("Connected to client at " + inetAddress);

		LoggerRepository h = (LoggerRepository) server.hierarchyMap.get(inetAddress);
		if (h == null) {
		    h = server.configureHierarchy(inetAddress);
		}

		cat.info("Starting new socket node.");
		new Thread(new SocketNode(socket, h)).start();
	    }
	} catch (Exception e) {
	    e.printStackTrace();
	}
    }

    static void usage(String msg) {
	System.err.println(msg);
	System.err.println("Usage: java " + SocketServer.class.getName() + " port configFile directory");
	System.exit(1);
    }

    static void init(String portStr, String configFile, String dirStr) {
	try {
	    port = Integer.parseInt(portStr);
	} catch (java.lang.NumberFormatException e) {
	    e.printStackTrace();
	    usage("Could not interpret port number [" + portStr + "].");
	}

	PropertyConfigurator.configure(configFile);

	File dir = new File(dirStr);
	if (!dir.isDirectory()) {
	    usage("[" + dirStr + "] is not a directory.");
	}
	server = new SocketServer(dir);
    }

    public SocketServer(File directory) {
	this.dir = directory;
	hierarchyMap = new Hashtable(11);
    }

    // This method assumes that there is no hiearchy for inetAddress
    // yet. It will configure one and return it.
    LoggerRepository configureHierarchy(InetAddress inetAddress) {
	cat.info("Locating configuration file for " + inetAddress);
	// We assume that the toSting method of InetAddress returns is in
	// the format hostname/d1.d2.d3.d4 e.g. torino/192.168.1.1
	String s = inetAddress.toString();
	int i = s.indexOf("/");
	if (i == -1) {
	    cat.warn("Could not parse the inetAddress [" + inetAddress + "]. Using default hierarchy.");
	    return genericHierarchy();
	} else {
	    String key;
	    if (i == 0) {
		key = s.substring(i);
	    } else {
		key = s.substring(0, i);
	    }

	    File configFile = new File(dir, key + CONFIG_FILE_EXT);
	    if (configFile.exists()) {
		Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));
		hierarchyMap.put(inetAddress, h);

		new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);

		return h;
	    } else {
		cat.warn("Could not find config file [" + configFile + "].");
		return genericHierarchy();
	    }
	}
    }

    LoggerRepository genericHierarchy() {
	if (genericHierarchy == null) {
	    File f = new File(dir, GENERIC + CONFIG_FILE_EXT);
	    if (f.exists()) {
		genericHierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
		new PropertyConfigurator().doConfigure(f.getAbsolutePath(), genericHierarchy);
	    } else {
		cat.warn("Could not find config file [" + f + "]. Will use the default hierarchy.");
		genericHierarchy = LogManager.getLoggerRepository();
	    }
	}
	return genericHierarchy;
    }
}
