//////////////////////////////////////////////////////////////////////////// 
// 
// Copyright (C) DSTC Pty Ltd (ACN 052 372 577) 1993, 1994, 1995.
// Unpublished work.  All Rights Reserved.
// 
// The software contained on this media is the property of the
// DSTC Pty Ltd.  Use of this software is strictly in accordance
// with the license agreement in the accompanying LICENSE.DOC 
// file. If your distribution of this software does not contain 
// a LICENSE.DOC file then you have no rights to use this 
// software in any manner and should contact DSTC at the address 
// below to determine an appropriate licensing arrangement.
// 
//      DSTC Pty Ltd
//      Level 7, GP South
//      University of Queensland
//      St Lucia, 4072
//      Australia
//      Tel: +61 7 3365 4310
//      Fax: +61 7 3365 4311
//      Email: jcsi@dstc.qut.edu.au
// 
// This software is being provided "AS IS" without warranty of
// any kind.  In no event shall DSTC Pty Ltd be liable for
// damage of any kind arising out of or in connection with
// the use or performance of this software.
// 
//////////////////////////////////////////////////////////////////////////// 

package com.dstc.security.smime;

import java.util.Set;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Date;
import java.io.*;
import javax.mail.*;
import javax.mail.internet.*;
import javax.activation.*;
import java.security.*;
import java.security.spec.*;
import java.security.cert.*;
import com.dstc.security.smime.*;
import com.dstc.security.provider.OID;
import com.dstc.security.asn1.Asn1;
import com.dstc.security.asn1.OctetString;
import com.dstc.security.asn1.Asn1Exception;
import com.dstc.security.x509.ExtensionFactory;
import com.dstc.security.x509.Base64OutputStream;
import com.dstc.security.x509.extns.SubjectAltName;
import com.dstc.security.cms.*;
import com.dstc.security.pkcs8.*;
import com.dstc.security.util.Config;

/**
 * A class which simplifies the use of SMIMEPart and MimeMultipartSigned
 * for S/MIME. 
 *
 * <p>Its primary purpose is to be used as a transformer between S/MIME
 * and non-S/MIME MIME messages. It includes a simple mechanism for
 * certificate caching and certificate path validation.
 *
 * <p>This class is used by SecMail to do the actual S/MIME processing.
 *
 * @author Ming Yung
 */
public class Transformer
{
  private static final String SMIME_CONF = "jcsi.smime.conf";
  private static final String SMIME_PROPERTIES = "smime.properties";
  private static final int FORWARD_MODE = 0;
  private static final int REVERSE_MODE = 1;

  private Session session;
  private MimeMessage msg;
  private String sender;
  private String to;
  private String subject;
  private int mode;
  private boolean debug = true;
  private MimeBodyPart currentPart;
  private MimeMultipart currentMultipart;
  private PrivateKey myPriv = null;
  private CertificateFactory certFact;
  private String sigAlgName;
  private X509Certificate myCert = null;
  private Set trustedCerts = null;
  private Hashtable receiverCertTable = null;
  private Set myCerts = null;
  private Properties props;
  private String keyDir = null;

  //Caution!! Trivial seeding for testing only
  private SecureRandom rand = new SecureRandom(new byte[8]);

  /**
   * Constructs a Transformer
   *
   * <p>The following keys are required:
   * <pre>
   *       jcsi.smime.sender          my email address
   *       jcsi.smime.key.dir         directory for key files
   *       jcsi.smime.myKey           filename for user's private key 
   *       jcsi.smime.myCert          filename for user's certificate
   *       jcsi.smime.caCert          filename for user's CA's certificate
   *       jcsi.smime.trustedCerts    filename for trusted CA certs
   *       jcsi.smime.receiverCerts   filename for peers' certificates
   *       jcsi.smime.encryptionAlg   encryption algorithm I will use
   *       jcsi.smime.signatureAlg    signature algorithm I will use
   *       
   *       mail.smtp.host             my SMTP host
   * </pre>
   */
  public Transformer() throws ToolException
  {
    try
    {
      this.props = Config.getProperties(SMIME_CONF, SMIME_PROPERTIES);
      keyDir = (String)props.get("jcsi.smime.key.dir");
      sender = (String)props.get("jcsi.smime.sender");

      this.setupMailcap();

      session = Session.getDefaultInstance(props, null);
      session.setDebug(debug);

      certFact = CertificateFactory.getInstance("X509");
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Sets up the Mailcap to use the S/MIME handlers
   *
   * <p>Not needed if a .mailcap is used
   */
  public void setupMailcap()
  {
    MailcapCommandMap map = new MailcapCommandMap();
    map.addMailcap("application/x-pkcs7-signature;; " +
      " x-java-content-handler=" +
      "com.dstc.security.smime.handlers.x_pkcs7_signature");
    map.addMailcap("multipart/signed;; " +
    " x-java-content-handler=" +
      "com.dstc.security.smime.handlers.multipart_signed");
    map.addMailcap("application/x-pkcs7-mime;; " +
      " x-java-content-handler=" +
      "com.dstc.security.smime.handlers.x_pkcs7_mime");

    CommandMap.setDefaultCommandMap(map);
  }

  /**
   * Saves the message to an OutputStream
   */
  public void save(OutputStream os) throws MessagingException, IOException
  {
    msg.saveChanges();

    if (os != null)
    {
      msg.writeTo(os);
    }
  }

  public void setMessage(InputStream is) throws ToolException
  {
    try
    {
      setMessage(new MimeMessage(session, is));
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  public void setMessage(MimeMessage msg) throws ToolException
  {
    try
    {
      this.msg = msg;

      Address[] a = msg.getRecipients(Message.RecipientType.TO);
      to = a[0].toString();

      if (msg.getContentType().startsWith("multipart"))
      {
        this.currentMultipart = (MimeMultipart)msg.getContent();
        this.currentPart = null;
      }
      else
      {
        this.currentMultipart = null;
        this.currentPart = new MimeBodyPart();
        this.currentPart.setContent(msg.getContent(),
          msg.getContentType());
        this.currentPart.setHeader("Content-Type", msg.getContentType());
        this.currentPart.setHeader("Content-Transfer-Encoding", 
          msg.getEncoding());
        this.currentPart.setDisposition(msg.getDisposition());
        this.currentPart.setDescription(msg.getDescription());
      }

      msg.setDisposition(null);
      msg.setDescription(null);
      msg.removeHeader("Content-Type");
      msg.removeHeader("Content-Transfer-Encoding");
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  public String getSender()
  {
    return this.sender;
  }

  /**
   * Encrypts the message in the buffer
   */
  public void encrypt() throws ToolException
  {
    String encryptionAlg = props.getProperty("jcsi.smime.encryptionAlg");

    if (encryptionAlg == null)
      throw new ToolException("Encryption algorithm unspecified");

    try
    {
      this.mode = FORWARD_MODE;

      if (receiverCertTable == null)
      {
        setupReceiverCertTable();
      }
  
      SMIMEPart encPart = null;

      if (currentMultipart != null)
      {
        encPart = new SMIMEPart(currentMultipart, rand);
      }
      else if (currentPart != null)
      {
        encPart = new SMIMEPart((MimeBodyPart)currentPart, rand);
      }

      encPart.setEncryptionAlgorithm(encryptionAlg);

      X509Certificate receiverCert 
        = (X509Certificate)receiverCertTable.get(to);

      if (receiverCert == null)
      { 
        System.err.println("\nCannot find certificate for receiver");
        System.err.print("Enter filename for receiver certificate: ");

        InputStreamReader isr = new InputStreamReader(System.in);
        BufferedReader in = new BufferedReader(isr);
  
        FileInputStream is = new FileInputStream(in.readLine());
        receiverCert = (X509Certificate)certFact.generateCertificate(is);

        addToReceiverCertTable(receiverCert);
      }
        
      encPart.addRecipient(receiverCert);
      encPart.encrypt();

      currentPart = encPart;
      msg.setContent(currentPart.getContent(), "application/x-pkcs7-mime; " +
         "name=\"smime.p7m\"");

      msg.setHeader("Content-Transfer-Encoding", "base64");
      msg.setHeader("Content-Disposition",
                    "attachment; filename=\"smime.p7m\"");
      msg.setHeader("Content-Description", "S/MIME Encrypted Message");
    } 
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Signs the message in the buffer. Uses SHA-1 as the digest algorithm
   * as per S/MIME
   */
  public void sign() throws ToolException
  {
    sigAlgName = props.getProperty("jcsi.smime.signatureAlg");
    if (sigAlgName == null)
      throw new ToolException("Signature algorithm unspecified");

    try
    {
      this.mode = FORWARD_MODE;

      MimeMultipartSigned mp;

      if (currentPart != null)
        mp = new MimeMultipartSigned(currentPart);
      else
        mp = new MimeMultipartSigned(currentMultipart);

      mp.setSignatureAlgorithm(sigAlgName);
      mp.setDigestAlgorithm("SHA-1");

      //Loads the private key and certificate chain for the signer

      if (myPriv == null || myCert == null)
      {
        loadPrivateKeyAndCert();
      }

      if (myCerts == null)
      {
        myCerts = new HashSet();
        myCerts.add(myCert);

        String fileName  = props.getProperty("jcsi.smime.caCert");

        if (fileName != null)
        {
          FileInputStream is = new FileInputStream(keyDir + "/" + fileName);

          myCerts.add((X509Certificate)certFact.generateCertificate(is));
          is.close();
        }
      }

      mp.setKeys(myPriv, myCert);
      mp.setCertificates(myCerts);

      //Sign it

      mp.sign();
      currentMultipart = mp;
      msg.setContent(currentMultipart);
    } 
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Decodes the S/MIME message in the message buffer until a non-S/MIME
   * message is left.
   */
  public void decodeMessage(InputStream is) throws ToolException
  {
    try
    {
      decodeMessage(new MimeMessage(session, is));
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Decodes the S/MIME message in the message buffer until a non-S/MIME
   * message is left.
   */
  public void decodeMessage(MimeMessage msg) throws ToolException
  {
    try
    {
      this.msg = msg;
      this.mode = REVERSE_MODE;
      currentMultipart = null;
      currentPart = null;

      while (true)
      {
        if (msg.getContentType().startsWith("multipart/signed"))
        {
          verifySignedMessage();
        }
        else if (msg.getContentType().startsWith("application/x-pkcs7-mime"))
        {
          decryptEncryptedMessage();
        }
        else
          break;
  
        if (currentPart == null & currentMultipart == null)
          break;
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Loads the user's Private Key and associated X509Certificate
   */
  public void loadPrivateKeyAndCert() throws ToolException
  {
    KeyFactory keyFact;

    try
    {
      if (this.mode == FORWARD_MODE)
      {
        if (sigAlgName == null)
          throw new ToolException("Signature algorithm not yet set");

        if (sigAlgName.equals("SHA-1/DSA"))
          keyFact = KeyFactory.getInstance("DSA");
        else
          keyFact = KeyFactory.getInstance("RSA");
      }
      else
        keyFact = KeyFactory.getInstance("RSA");

      String fileName = props.getProperty("jcsi.smime.privKey");

      if (fileName == null)
        throw new ToolException("Location of private key file unspecified");

      FileInputStream is = new FileInputStream(keyDir + "/" + fileName);

      byte encoded[] = new byte[is.available()];
      is.read(encoded);

      System.err.println("Enter password to unlock private key");

      myPriv = keyFact.generatePrivate(new PKCS8EncodedKeySpec(encoded));

      fileName = props.getProperty("jcsi.smime.myCert");
     
      if (fileName == null)
        throw new ToolException("Location of user certificate unspecified");

      is = new FileInputStream(keyDir + "/" + fileName);

      myCert = (X509Certificate)certFact.generateCertificate(is);

      is.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Loads trusted CA certs for the signed message verification process.
   *
   * <p>The trusted certs are expected in a location pointed to by
   * jcsi.smime.trustedCerts
   *
   * <p>The file is expected to contain a concatenation of base-64
   * encoded X.509 certificates delimited by "-----BEGIN CERTIFICATE-----"
   * and "-----END CERTIFICATE-----"
   */
  public void loadTrustedCerts() throws ToolException
  {
    try
    {
      String fileName = props.getProperty("jcsi.smime.trustedCerts");

      if (fileName == null)
        throw new ToolException("Location of trusted certs unspecified");

      File file = new File(keyDir + "/" + fileName);
      if (!file.exists())
        file.createNewFile();

      FileInputStream is  = new FileInputStream(file);
      DataInputStream dis = new DataInputStream(is);
      byte[] certData = new byte[dis.available()];
      dis.readFully(certData);
      is.close();

      ByteArrayInputStream bais = new ByteArrayInputStream(certData);

      trustedCerts = new HashSet();

      while (bais.available() > 0)
      {
        X509Certificate cert = 
          (X509Certificate)certFact.generateCertificate(bais);
        trustedCerts.add(cert);
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Verifies the signature on the signed message in the buffer and transforms
   * it to a message with just the contents (the part that was signed)
   */
  public void verifySignedMessage() throws ToolException
  {
    try
    {
      if (trustedCerts == null)
      {
        loadTrustedCerts();
      }

      currentMultipart = (MimeMultipartSigned)msg.getContent();
      ((MimeMultipartSigned)currentMultipart).setTrustedCAs(trustedCerts);
      ((MimeMultipartSigned)currentMultipart).verify();

      currentPart = (MimeBodyPart)currentMultipart.getBodyPart(0);
      msg.setContent(currentPart.getContent(), currentPart.getContentType());
 
      if (receiverCertTable == null)
      {
        setupReceiverCertTable();
      }

      Iterator receivedCerts 
        = ((MimeMultipartSigned)currentMultipart
            ).getSignerCertificates().iterator();

      while (receivedCerts.hasNext())
      {
        addToReceiverCertTable((X509Certificate)receivedCerts.next());
      }

      updateMessage();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Decrypts the encrypted message in the buffer and transforms it to
   * the decrypted message
   */
  public void decryptEncryptedMessage() throws ToolException
  {
    try
    {
      if (myPriv == null)
      {
        loadPrivateKeyAndCert();
      }

      //Decrypt it

      currentPart = new SMIMEPart();
      currentPart.setContent(msg.getContent(), msg.getContentType());

      ((SMIMEPart)currentPart).decrypt(myCert, myPriv);

      byte[] decrypted = ((SMIMEPart)currentPart).getDecrypted();

      msg.removeHeader("Content-Type");
      msg.removeHeader("Content-Transfer-Encoding");
      msg.removeHeader("Content-Description");
      msg.removeHeader("Content-Disposition");

      ByteArrayInputStream bais = new ByteArrayInputStream(decrypted);

      InternetHeaders hdrs = new InternetHeaders(bais);

      byte[] data = new byte[bais.available()];
      bais.read(data);
      MimeBodyPart pt = new MimeBodyPart(hdrs, data);
      msg.setContent(pt.getContent(), pt.getContentType());

      Set originatorCerts = ((SMIMEPart)currentPart).getOriginatorCerts();
      if (originatorCerts != null)
      {
        Iterator it = originatorCerts.iterator();
        while (it.hasNext())
        {
          System.out.println(((X509Certificate)it.next()).toString());
        }
      }

      updateMessage();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Updates the message buffer
   */
  private void updateMessage() throws MessagingException, IOException
  {
    msg.saveChanges();
    Object cont = msg.getContent();
    if (cont instanceof EnvelopedMessage)
    {
      currentPart = new SMIMEPart();
      currentPart.setContent(cont, msg.getContentType());
    }
    else if (cont instanceof MimeMultipartSigned)
    {
      currentMultipart = (MimeMultipartSigned)cont;
      currentPart = null;
    }
    else
    {
      currentPart = null;
    }
  }

  private MimeMessage getMessage()
  {
    return this.msg;
  }

  /**
   * Sets up receiverCertTable, a Hashtable in which receiver certificates
   * are indexed by their email addresses. 
   *
   * <p>Certificates are read in from a file pointed to by 
   * jcsi.smime.receiverCerts.
   *
   * <p>The file is expected to contain a concatenation of base-64
   * encoded X.509 certificates delimited by "-----BEGIN CERTIFICATE-----"
   * and "-----END CERTIFICATE-----"
   *
   * <p>NB. Only certificates for encryption keys should be in this table.
   */
  public void setupReceiverCertTable() throws ToolException
  {
    try
    {
      receiverCertTable = new Hashtable();
      Set receiverCerts = new HashSet();

      String fileName = props.getProperty("jcsi.smime.receiverCerts");
      if (fileName == null)
        return;

      File file = new File(keyDir + "/" + fileName);
      if (!file.exists())
        file.createNewFile();

      FileInputStream is = new FileInputStream(file);
      DataInputStream dis = new DataInputStream(is);
      byte[] certData = new byte[dis.available()];
      dis.readFully(certData);
      is.close();

      ByteArrayInputStream bais = new ByteArrayInputStream(certData);

      while (bais.available() > 0)
      {
        X509Certificate cert 
          = (X509Certificate)certFact.generateCertificate(bais);
        receiverCerts.add(cert);
      }

      Iterator it = receiverCerts.iterator();
      while (it.hasNext())
      {
        X509Certificate cert = (X509Certificate)it.next();
  
        addToReceiverCertTable(cert);
      }
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Adds an X509Certificate to receiverCertTable indexed by the
   * email address of its subject, provided the certificate is
   * an encryption certificate (currently, this means an RSA cert)
   */
  public void addToReceiverCertTable(X509Certificate cert) 
    throws ToolException
  {
    if (!cert.getPublicKey().getAlgorithm().equals("RSA"))
      return;

    SubjectAltName extn 
      = (SubjectAltName)ExtensionFactory.
          getExtension(OID.subjectAltName, cert);

    if (extn != null)
    {
      receiverCertTable.put(extn.getName(), cert);
      return;
    }
    else
    {
      String subject = (cert.getSubjectDN()).getName();
      StringTokenizer tknz = new StringTokenizer(subject, ",");
      while (tknz.hasMoreTokens())
      {
        String token = tknz.nextToken().trim();
        if (token.startsWith("email"))
        {
          receiverCertTable.put(token.substring(6), cert);
          break;
        }
      } 
    }
  }

  /**
   * Writes out all the X509Certificates in receiverCertTable
   * to a file pointed to by jcsi.smime.receiverCerts
   */
  public void saveReceiverCerts() throws ToolException
  {
    if (receiverCertTable == null || receiverCertTable.isEmpty()) 
      return;

    try
    {
      Iterator certs  = receiverCertTable.values().iterator();

      File file 
        = new File(keyDir + "/" +
            props.getProperty("jcsi.smime.receiverCerts"));

      if (!file.exists())
        file.createNewFile();

      RandomAccessFile rFile = new RandomAccessFile(file, "rw");

      rFile.seek(0);

      ByteArrayOutputStream baos;
      Base64OutputStream b64os; 

      while (certs.hasNext())
      {
        baos = new ByteArrayOutputStream();
        b64os = new Base64OutputStream(baos, "-----BEGIN CERTIFICATE-----",
                                       "-----END CERTIFICATE-----");
        b64os.write(((X509Certificate)certs.next()).getEncoded());
        b64os.flush();
        rFile.write(baos.toByteArray());
      }

      rFile.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Writes out all the X509Certificates in trustedCerts
   * to a file pointed to by jcsi.smime.trustedCerts
   */
  public void saveTrustedCerts() throws ToolException
  {
    if (trustedCerts == null) 
      return;

    try
    {
      Iterator certs  = trustedCerts.iterator();

      File file = new File(keyDir + "/" +
        props.getProperty("jcsi.smime.trustedCerts"));
      if (!file.exists())
        file.createNewFile();

      RandomAccessFile rFile = new RandomAccessFile(file, "rw");
      rFile.seek(0);

      ByteArrayOutputStream baos;
      Base64OutputStream b64os; 

      while (certs.hasNext())
      {
        baos = new ByteArrayOutputStream();
        b64os = new Base64OutputStream(baos, "-----BEGIN CERTIFICATE-----",
                                       "-----END CERTIFICATE-----");
        b64os.write(((X509Certificate)certs.next()).getEncoded());
        b64os.flush();
        rFile.write(baos.toByteArray());
      }

      rFile.close();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * Adds a trusted CA cert
   */
  public void addTrustedCert(InputStream is) throws ToolException
  {
    try
    {
      if (trustedCerts == null)
        loadTrustedCerts();

      X509Certificate cert 
        = (X509Certificate)certFact.generateCertificate(is);
      trustedCerts.add(cert);
    }
    catch (Exception e)
    {
      e.printStackTrace();
      throw new ToolException(e.getMessage());
    }
  }

  /**
   * An inner class for SecMail-specific exceptions
   */
  protected class ToolException extends Exception
  {
    protected ToolException(String msg)
    {
      super(msg);
    }
  }

  //////////////////////////////////////////
  //        Utility methods
  //////////////////////////////////////////

  public static void sign(InputStream in, OutputStream out)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.setMessage(in);
      smail.sign();
      smail.save(out);

      smail.saveReceiverCerts();
      smail.saveTrustedCerts();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public static MimeMessage sign(MimeMessage inMsg)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.setMessage(inMsg);
      smail.sign();
      smail.save(null);

      smail.saveReceiverCerts();
      smail.saveTrustedCerts();
      return smail.getMessage();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  public static void encrypt(InputStream in, OutputStream out)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.setMessage(in);
      smail.encrypt();
      smail.save(out);

      smail.saveReceiverCerts();
      smail.saveTrustedCerts();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public static MimeMessage encrypt(MimeMessage inMsg)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.setMessage(inMsg);
      smail.encrypt();
      smail.save(null);

      smail.saveReceiverCerts();
      smail.saveTrustedCerts();
      return smail.getMessage();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }

  public static void decode(InputStream in, OutputStream out)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.decodeMessage(in);

      smail.save(out);
      smail.saveReceiverCerts();
      smail.saveTrustedCerts();
    }
    catch (Exception e)
    {
      e.printStackTrace();
    }
  }

  public static MimeMessage decode(MimeMessage inMsg)
  {
    try
    {
      Transformer smail = new Transformer();

      smail.decodeMessage(inMsg);

      smail.save(null);
      smail.saveReceiverCerts();
      smail.saveTrustedCerts();

      return smail.getMessage();
    }
    catch (Exception e)
    {
      e.printStackTrace();
      return null;
    }
  }
}
