/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.mail;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.Properties;

import javax.management.ObjectName;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;

import javax.naming.InitialContext;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.naming.Name;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.NameNotFoundException;

import javax.mail.Session;
import javax.mail.PasswordAuthentication;
import javax.mail.Authenticator;

import org.jboss.system.ServiceMBeanSupport;
import org.jboss.naming.NonSerializableFactory;

/**
 * MBean that gives support for JavaMail. Object of class javax.mail.Session will be bound
 * in JNDI under java:/ namespace with the name provided with method {@link #setJNDIName}.
 *
 * @jmx:mbean name="jboss:type=Service,service=Mail"
 *            extends="org.jboss.system.ServiceMBean"
 *
 * @version <tt>$Revision: 1.9.2.4 $</tt>
 * @author  <a href="mailto:simone.bordet@compaq.com">Simone Bordet</a>
 * @author  <a href="mailto:jason@planet57.com">Jason Dillon</a>
 */
public class MailService
      extends ServiceMBeanSupport
      implements MailServiceMBean
{
   public static final String JNDI_NAME = "java:/Mail";

   private String user;
   private String password;
   private String jndiName = JNDI_NAME;
   private Element config;

   /** Object Name of the JSR-77 representant of this service */
   ObjectName mMail;
   
   /** save properties here */
   Properties ourProps = null;

   /**
    * User id used to connect to a mail server
    *
    * @see #setPassword
    *
    * @jmx:managed-attribute
    */
   public void setUser(final String user)
   {
      this.user = user;
   }

   /**
    * @jmx:managed-attribute
    */
   public String getUser()
   {
      return user;
   }

   /**
    * Password used to connect to a mail server
    *
    * @see #setUser
    *
    * @jmx:managed-attribute
    */
   public void setPassword(final String password)
   {
      this.password = password;
   }

   /**
    * Password is write only.
    */
   protected String getPassword()
   {
      return password;
   }

   /**
    * Configuration for the mail service.
    *
    * @jmx:managed-attribute
    */
   public Element getConfiguration()
   {
      return config;
   }

   /**
    * Configuration for the mail service.
    *
    * @jmx:managed-attribute
    */
   public void setConfiguration(final Element element)
   {
      config = element;
   }

   /**
    * The JNDI name under the java:/ namespace to which javax.mail.Session
    * objects are bound.
    *
    * @jmx:managed-attribute
    */
   public void setJNDIName(final String name)
   {
      if (!name.startsWith("java:/"))
      {
         jndiName = "java:/" + name;
      }
      else
      {
         jndiName = name;
      }
   }

   /**
    * @jmx:managed-attribute
    */
   public String getJNDIName()
   {
      return jndiName;
   }

	/**
	 * @jmx:managed-attribute
	 */
	public String getStoreProtocol()
	{
		if (ourProps!=null)
			return ourProps.getProperty("mail.store.protocol");
		else
			return null;   	
	}
	/**
	 * @jmx:managed-attribute
	 */
	public String getTransportProtocol()
	{
		if (ourProps!=null)
			return ourProps.getProperty("mail.transport.protocol");
		else
			return null;   	
	}
	/**
	 * @jmx:managed-attribute
	 */
	public String getDefaultSender()
	{
		if (ourProps!=null)
			return ourProps.getProperty("mail.from");
		else
			return null;   	
	}


	/**
	 * @jmx:managed-attribute
	 */
   public String getSMTPServerHost()
   {
		if (ourProps!=null)
			return ourProps.getProperty("mail.smtp.host");
		else
			return null;   	
   }

	/**
	 * @jmx:managed-attribute
	 */
   public String getPOP3ServerHost()
   {
   	if (ourProps!=null)
   		return ourProps.getProperty("mail.pop3.host");
		else
			return null;
   }

   protected ObjectName getObjectName(MBeanServer server, ObjectName name)
         throws MalformedObjectNameException
   {
      return name == null ? OBJECT_NAME : name;
   }

   protected void startService() throws Exception
   {
      // Setup password authentication
      final PasswordAuthentication pa = new PasswordAuthentication(getUser(), getPassword());
      Authenticator a = new Authenticator()
      {
         protected PasswordAuthentication getPasswordAuthentication()
         {
            return pa;
         }
      };

      Properties props = getProperties();

      // Finally create a mail session
      Session session = Session.getInstance(props, a);
      bind(session);
      
      // now make the properties available
      ourProps= props;
   }

   protected Properties getProperties() throws Exception
   {
      boolean debug = log.isDebugEnabled();

      Properties props = new Properties();
      if (config == null)
      {
         log.warn("No configuration specified; using empty properties map");
         return props;
      }

      NodeList list = config.getElementsByTagName("property");
      int len = list.getLength();

      for (int i = 0; i < len; i++)
      {
         Node node = list.item(i);

         switch (node.getNodeType())
         {
            case Node.ELEMENT_NODE:
               Element child = (Element) node;
               String name, value;

               // get the name
               if (child.hasAttribute("name"))
               {
                  name = child.getAttribute("name");
               }
               else
               {
                  log.warn("Ignoring invalid element; missing 'name' attribute: " + child);
                  break;
               }

               // get the value
               if (child.hasAttribute("value"))
               {
                  value = child.getAttribute("value");
               }
               else
               {
                  log.warn("Ignoring invalid element; missing 'value' attribute: " + child);
                  break;
               }

               if (debug)
               {
                  log.debug("setting property " + name + "=" + value);
               }
               props.setProperty(name, value);
               break;

            case Node.COMMENT_NODE:
               // ignore
               break;

            default:
               log.debug("ignoring unsupported node type: " + node);
               break;
         }
      }

      if (debug)
      {
         log.debug("Using properties: " + props);
      }

      return props;
   }

   protected void stopService() throws Exception
   {
      unbind();
   }

   private void bind(Session session) throws NamingException
   {
      String bindName = getJNDIName();

      // Ah ! Session isn't serializable, so we use a helper class
      NonSerializableFactory.bind(bindName, session);

      Context ctx = new InitialContext();
      try
      {
         Name n = ctx.getNameParser("").parse(bindName);
         while (n.size() > 1)
         {
            String ctxName = n.get(0);
            try
            {
               ctx = (Context) ctx.lookup(ctxName);
            }
            catch (NameNotFoundException e)
            {
               ctx = ctx.createSubcontext(ctxName);
            }
            n = n.getSuffix(1);
         }


         // The helper class NonSerializableFactory uses address type nns, we go on to
         // use the helper class to bind the javax.mail.Session object in JNDI

         StringRefAddr addr = new StringRefAddr("nns", bindName);
         Reference ref = new Reference(Session.class.getName(),
               addr,
               NonSerializableFactory.class.getName(),
               null);
         ctx.bind(n.get(0), ref);
      }
      finally
      {
         ctx.close();
      }

      log.info("Mail Service bound to " + bindName);
   }

   private void unbind() throws NamingException
   {
      String bindName = getJNDIName();

      if (bindName != null)
      {
         InitialContext ctx = new InitialContext();
         try
         {
            ctx.unbind(bindName);
         }
         finally
         {
            ctx.close();
         }

         NonSerializableFactory.unbind(bindName);
         log.info("Mail service '" + getJNDIName() + "' removed from JNDI");
      }
   }
}
