/***********************************************************************
 * 
 *  Copyright (C) 2006 Novell, Inc. All Rights Reserved.
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; version 2.1
 *  of the License.
 *
 *  This library 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
 *  Library Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, Novell, Inc.
 * 
 *  To contact Novell about this file by physical or electronic mail, 
 *  you may find current contact information at www.novell.com.
 * 
 *  Author: Juan Carlos Luciani <jluciani@novell.com>
 *  
 ***********************************************************************/

package com.novell.casa.authtoksvc;

import org.bandit.ia.IAContext;
import org.apache.log4j.Logger;
import org.apache.log4j.NDC;

import java.util.*;

import java.io.*;
import java.io.PrintWriter;

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


/**
 * Rpc Servlet Class.
 * <p>
 * This class processes Rpcs to the Authentication Token Service.
 * 
 */
public final class Rpc extends javax.servlet.http.HttpServlet implements javax.servlet.Servlet
{
   private static final long serialVersionUID = -8264027868130334613L;

   private static final Logger m_log = Logger.getLogger(Rpc.class);
   private static final Logger m_trace = Logger.getLogger("ATS_Trace");

   private     String               m_appFolderPath = null;
   private     String               m_configFolderPath = null;

   private     boolean              m_enabledSvcsOnly;

   protected   ReconfigureThread    m_reconfigureThread = null;
   protected   int                  m_reconfigureInterval; // seconds

   private     Map<String,RpcMethod>   m_methodsMap;

   /**
    * Reconfigure Thread Class.
    * <p>
    * This class implements a runnable thread that reconfigures an Rpc Servlet instance.
    *
    */
   private final class ReconfigureThread implements Runnable
   {
      private final Rpc m_rpc;
      private volatile boolean m_run = true;

      /**
       * Constructor.
       *
       * @param rpc  Rpc object.
       */
      public ReconfigureThread (Rpc rpc)
      {
         m_rpc = rpc;
         Thread thread = new Thread(this);
         thread.start();
      }

      /**
       * run() implementation.
       */
      public final void run ()
      {
         m_log.debug("ReconfigureThread.run()- Running");

         while (m_run)
         {
            // Sleep an ammount equal the reconfigure interval for the Rpc
            try
            {
               Thread.sleep(m_rpc.m_reconfigureInterval * 1000);
            }
            catch (InterruptedException e) { /* nothing to do */ }

            // Re-configure the Rpc servlet.
            try
            {
               m_rpc.configureServlet();

               // Also force IA to re-read its configuration
               IAContext.clearRealmCache();

               // Check if it is no longer necessary to re-configure the servlet
               if (m_rpc.m_reconfigureInterval == 0)
               {
                  m_log.info("ReconfigureThread.run()- Configuration changed to no longer perform timed re-configuration");
                  break;
               }
            }
            catch (Exception e)
            {
               m_log.warn("ReconfigureThread.run()- Exception caught during re-configure process, " + e.toString());
            }
         }
      }

      /**
       * stop() implementation.
       */
      public final void stop ()
      {
         m_run = false;
      }
   }

   /**
    * Constructor.
    */
   public Rpc ()
   {
      super();
   }

   /**
    * configureServlet() implementation.
    *
    * @throws Exception
    */
   protected void configureServlet () throws Exception
   {
      // Read service configuration
      SvcConfig svcConfig = new SvcConfig(m_appFolderPath, m_configFolderPath);

      // Get the reconfigure interval
      try
      {
         m_reconfigureInterval = Integer.parseInt(svcConfig.getSetting(SvcConfig.ReconfigureInterval));
      }
      catch (NumberFormatException e)
      {
         m_log.warn("configureServlet()- Invalid reconfigure interval value");
         m_reconfigureInterval = Integer.parseInt(SvcConfig.DefaultReconfigureIntervalValue);
      }

      // Read enabled services configuration
      EnabledSvcsConfig enabledSvcsConfig = new EnabledSvcsConfig(m_configFolderPath, m_enabledSvcsOnly);

      // Create a map to keep track of the Rpc methods
      Map<String,RpcMethod> methodsMap = new HashMap<String, RpcMethod>();

      // Instantiate the Rpc Methods
      RpcMethod getAuthPolicy = new GetAuthPolicy();
      getAuthPolicy.init(svcConfig, enabledSvcsConfig, m_trace);
      methodsMap.put(getAuthPolicy.getId(), getAuthPolicy);

      RpcMethod authenticate = new Authenticate();
      authenticate.init(svcConfig, enabledSvcsConfig, m_trace);
      methodsMap.put(authenticate.getId(), authenticate);

      RpcMethod getAuthToken = new GetAuthToken();
      getAuthToken.init(svcConfig, enabledSvcsConfig, m_trace);
      methodsMap.put(getAuthToken.getId(), getAuthToken);

      // Set the map as the methods map used by the servlet
      m_methodsMap = methodsMap;
   }

   /**
    * init() implementation.
    *
    * @param config  Servlet configuration object.
    * @throws ServletException
    */
   public void init (ServletConfig config) throws ServletException
   {
      super.init(config);

      m_log.debug("init()");

      try
      {
         // Get the path to our configuration folder
         //
         // First check if it has been specified via a system property
         ServletContext context = config.getServletContext();
        // m_appFolderPath = context.getRealPath(File.separator);
         m_appFolderPath = context.getRealPath("/");
 		 if(!m_appFolderPath.endsWith(File.separator)) m_appFolderPath = m_appFolderPath + File.separator;

         m_configFolderPath = System.getProperty("com.novell.casa.authtoksvc.config");
         if (m_configFolderPath == null)
         {
            // The path to the svc config folder was not specified via a system
            // property, assume that it's location is off the WEB-INF folder for
            // our web application.
            m_configFolderPath = m_appFolderPath + "WEB-INF/conf";
         }

         // Check if we support services that are not explicitedly enabled
         String enabledSvcsOnly = System.getProperty("com.novell.casa.authtoksvc.enabled_svcs_only");
         if (enabledSvcsOnly != null
             && enabledSvcsOnly.equalsIgnoreCase("true"))
         {
            m_enabledSvcsOnly = true;
         }
         else
         {
            m_enabledSvcsOnly = false;
         }

         // Configure ourselves
         configureServlet();

         // Check if we must start a thread to periodically reconfigure ourselves
         if (m_reconfigureInterval != 0)
         {
            m_reconfigureThread = new ReconfigureThread(this);
         }
      }
      catch (Exception e)
      {
         m_log.fatal("init()- Exception caught: " + e.toString());
         e.printStackTrace();
         throw new ServletException("Exception caught while instantiating Rpc methods", e);
      }
      catch (Error e)
      {
         m_log.fatal("init()- Error caught: " + e.toString());
         e.printStackTrace();
         throw new Error("Error caught while instantiating Rpc methods", e);
      }
   }

   /**
    * destroy() implementation.
    */
   public void destroy ()
   {
      super.destroy();

      m_log.debug("destroy()");

      // Stop our re-configure thread
      if (m_reconfigureThread != null)
      {
         m_reconfigureThread.stop();
      }
   }

   /**
    * doGet() implementation.
    *
    * @param request    Http servlet request object.
    * @param response   Http servlet response object.
    * @throws ServletException
    * @throws IOException
    */
   protected void doGet (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      doPost(request, response);
   }     

   /**
    * doPost() implementation.
    *
    * @param request    Http servlet request object.
    * @param response   Http servlet response object.
    * @throws ServletException
    * @throws IOException
    */
   protected void doPost (HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
   {
      PrintWriter out = null;
      InputStream inStream = null;

      try
      {
         m_log.debug("doPost()");

         // Get ready to trace request
         NDC.push("ClientAddr=" + request.getRemoteAddr());

         // Get ready to send back a reply
         response.setContentType("text/html");
         out = response.getWriter();

         // Obtain the input stream and execute the requested method
         inStream = request.getInputStream();

         String requestedMethod = request.getParameter("method");
         if (requestedMethod != null)
         {
            // Get the necessary method
            RpcMethod method = m_methodsMap.get(requestedMethod);
            if (method != null)
            {
               // Invoke the method to process the Rpc
               method.invoke(inStream, out);
            }
            else
            {
               // Unsupported method
               m_log.warn("doPost()- Unsupported method");
               m_trace.warn("Rpc, Method=" + requestedMethod + ", Status=UNSUPPORTED");
               response.sendError(HttpServletResponse.SC_BAD_REQUEST);
            }
         }
         else
         {
            // Missing method parameter
            m_log.warn("doPost()- Missing method parameter");
            m_trace.warn("Rpc, Status=INVALID_REQUEST");
            response.sendError(HttpServletResponse.SC_BAD_REQUEST);
         }
      }
      catch (Exception e)
      {
         m_log.error("doPost()- Exception caught: " + e.toString());
         m_trace.warn("Rpc, Status=INTERNAL_ERROR");
         e.printStackTrace();
         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      }
      catch (Error e)
      {
         m_log.error("doPost()- Error caught: " + e.toString());
         m_trace.warn("Rpc, Status=INTERNAL_ERROR");
         e.printStackTrace();
         response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
      }
      finally
      {
         if (out != null)
         {
            try
            {
               out.flush();
               out.close();
            }
            catch (Exception e)
            {
               // Do nothing
            }
         }

         if (inStream != null)
         {
            try
            {
               inStream.close();
            }
            catch (IOException e)
            {
               // Do nothing
            }
         }

         NDC.pop();
      }
   }
}