/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.regex.Pattern;

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

import org.apache.log4j.Logger;

/**
 *
 *	This servlet is used to parse the GECAMed.jnlp file
 * and replace the codebase by the URL of the Server this servlet
 * and the JBoss server are running on. This allows it to deploy the same
 * GECAMEd-Client war file on every server, without changing the jnlp file by hand.
 *
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 * 
 * @Version
 * <br>$Log: JNLPServlet.java,v $
 * <br>Revision 1.12  2009-10-30 09:58:55  hermen
 * <br>added departed status to patient
 * <br>
 * <br>Revision 1.11  2008-09-25 09:42:27  heinemann
 * <br>fixed copyrights
 * <br>
 * <br>Revision 1.10  2008-04-21 15:19:09  hermen
 * <br>fixed webstart problem for machines without network
 * <br>
 * <br>Revision 1.9  2008-01-15 10:33:46  hermen
 * <br>updated Javadoc and refactured code
 * <br>
 */
public class JNLPServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	
	private static final String	LOCALHOST = "localhost";
	
	private static Pattern	IPV4_PATTERN;
	private static Pattern	IPV4_LOCALHOST_PATTERN;
	private static Pattern	IPV6_LOCALHOST_PATTERN;

	private static Logger logger = Logger
			.getLogger(JNLPServlet.class.getName());
	
	private String	WEB_CONTEXT		= "gecamed";
	
	
	
	/* (non-Javadoc)
	 * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
	 */
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		int		webPort;
		String	hostAddress;
		
		response.setContentType("application/x-java-jnlp-file");
		// no longer used.
//		response.setDateHeader("Last-Modified", System.currentTimeMillis());
		PrintWriter out = response.getWriter();
		
		// obtain web port of the jboss (default 8080)
		webPort		= request.getServerPort();
		// obtain the InetAddress of the computer on which this program is running
		hostAddress	= getHostIP(request, response);
		
		logger.info(new StringBuilder()
				.append("JNLP generation for client: ").append(request.getRemoteAddr())
				.append(" -> with address: ").append(hostAddress)
				.toString());
		
		String newCodebase = "http://" + hostAddress + ":" + webPort + "/" + WEB_CONTEXT + "/";

		// read jnlp-file from url
		URL jnlpURL = new URL(newCodebase + "GECAMed.jnlp.orig");

		
		try {
			URLConnection con = jnlpURL.openConnection();
			
			// set generation date of the original JNLP File
			response.setDateHeader("Last-Modified", con.getLastModified());
			
			InputStream in = con.getInputStream();
			BufferedReader dis = new BufferedReader(new InputStreamReader(in));
			String line = null;
			while ((line = dis.readLine()) != null) {
				// replace the codebase
				out.println(line.replaceFirst("codebase=\".+?\"", "codebase=\""
						+ newCodebase + "\""));
			}
			dis.close();
			in.close();
		} catch (IOException e) {
			logger.error("Error while creating JNLP file.", e);
		}
		out.flush();
		out.close();
	}
	
	
	public static String getHostIP (HttpServletRequest request, HttpServletResponse response) throws UnknownHostException
	{
		String hostAddress		= "localhost";
		InetAddress localaddr	= InetAddress.getLocalHost();
		String serverAddress	= localaddr.getHostAddress();
		String remoteAddress	= request.getRemoteAddr();
		String localAddress		= request.getLocalAddr();
		String bindAddress		= System.getProperty("bind.address");
		
		int remoteAddressCheck	= isLocalhost(remoteAddress,null);
		int bindAddressCheck	= isLocalhost(bindAddress,	remoteAddress);
		int serverAddressCheck	= isLocalhost(serverAddress,remoteAddress);
		int localAddressCheck	= isLocalhost(localAddress,	remoteAddress);
		
		
		logger.info(new StringBuilder()
		.append("Request Network parameter: ")
		.append("ServerAddr: ").append(serverAddress)
		.append(" | RemoteAddr: ").append(remoteAddress)
		.append(" | LocalAddr: ").append(localAddress)
		.append(" | LocalPort: ").append(request.getLocalPort())
		.append(" | bind.address: ").append(bindAddress == null ? "" : bindAddress)
		.toString());
		
		if (bindAddressCheck > 0)
		{
			/* bindAddress is set, so use it
			 */
			hostAddress = bindAddress;
			logger.info("JBoss bind to \"bind.address\" " + hostAddress);
		}
		else if (remoteAddressCheck == 1
				|| localAddressCheck == 1)
		{
			/* remoteAddress or localAddress stands for local machine
			 * or remoteAddress and loacalAddress are equal. 
			 */
			hostAddress = LOCALHOST;
			logger.info("request from localhost...");
		}
		else if (localAddressCheck > 1)
		{
			/* The localAddress is valid and stands for a remote machine.
			 * As this is the most reliable, use it.
			 */
			hostAddress = localAddress;
			logger.info("request from external host...");
		}
		else if (serverAddressCheck == 1)
		{
			/* localAddress is not valid, so use the serverAddress,
			 * although this might not be a good option on Linux.
			 * 
			 * -> localAddress stands for local machine
			 */
			hostAddress = LOCALHOST;
			logger.info("request from localhost...");
		}
		else if (serverAddressCheck > 1)
		{
			/* localAddress is not valid, so use the serverAddress,
			 * although this might not be a good option on Linux.
			 * 
			 * -> localAddress stands for a remote machine
			 */
			hostAddress = serverAddress;
			logger.info("request from external host...");
		}
		else 
		{
			/* None of the IP addresses can be used for some reason. 
			 * Try local host and log the problem description.
			 */
			hostAddress = LOCALHOST;
			logger.warn("Couldn't find the valid IP address. Therefore trying \"localhost\"");
		}
		
		return hostAddress;
	}
	
	
	private static boolean isIPv4 (String ipAddress)
	{
		try
		{
			if (IPV4_PATTERN == null)
				IPV4_PATTERN = Pattern.compile("\\d{1,3}(\\.\\d{1,3}){3}");
			
			return IPV4_PATTERN.matcher(ipAddress).matches();
		}
		catch (Throwable e)
		{
			logger.error("Problem to test if IP address \""+ipAddress+"\" is IPv4. Will return false ...", e);
			return false;
		}
	}
	
	
	/**
	 * @param ipAddress The IP address to check
	 * @return 
	 * -1 if an error occurred
	 * 	0 if ipAddress is <code>null</code>, '0.0.0.0' 
	 * 		or not an IPv4 address and not a IPv6 address that stands for localhost.<br>
	 * 	1 if ipAddress stands for localhost.
	 * 	2 if ipAddress is an external IPv4 address 
	 */
	private static int isLocalhost (String ipAddress, String requestIpAddress)
	{
		try
		{
			if (ipAddress == null)
			{
				return 0;
			}
			else if (ipAddress.equalsIgnoreCase("localhost"))
			{
				return 1;
			}
			else if (isIPv4(ipAddress))
			{
				if (ipAddress.equals("0.0.0.0"))
					return 0;
				
				if (IPV4_LOCALHOST_PATTERN == null)
					IPV4_LOCALHOST_PATTERN = Pattern.compile("127(\\.\\d{1,3}){3}");
				
				return IPV4_LOCALHOST_PATTERN.matcher(ipAddress).matches()
						|| (ipAddress.equals(requestIpAddress)) ? 1 : 2;
			}
			else
			{
				if (IPV6_LOCALHOST_PATTERN == null)
					IPV6_LOCALHOST_PATTERN = Pattern.compile("(::)?(0{1,4}:){0,7}1");
				
				return IPV6_LOCALHOST_PATTERN.matcher(ipAddress).matches()? 1 : 0;
			}
		}
		catch (Throwable e)
		{
			logger.error("Problem to test if IP address \"" + ipAddress + "\" is localhost. Will return false ...", e);
			return -1;
		}
	}
	
}
