Java™ - How to Zip the Path (Files or Directory) and Send Them as Attachment Over Network Using Gmail SMTP

in #utopian-io7 years ago (edited)

java-programming.jpg
Source: https://academy.oracle.com/en/oa-assets/i/c82/c82-java-prog-logo.jpg

What Will I Learn?

In this tutorial, I will guide you how to archive/zip files or directory using standard Java™ class library in the package of java.util.zip.*. After we grasp it, we'll move on to the lesson about sending it as an attachment in an email over the network using Gmail SMTP. All the classes we need to create the email application are available in the javax.mail package.

  • Java™ I/O Stream
  • Java™ Zip Output Stream
  • Java™ Mail

Requirements

  • Any current JDK.
  • IntelliJ IDEA.
  • At least two google accounts, as sender and recipient.

Difficulty

  • Intermediate

Tutorial Contents

1. Create a Archiver Class
  • Archive a Single File

    All we need to archive a file is:

    1. java.util.zip.ZipEntry - This class is used to represent a ZIP file entry.
    2. java.util.zip.ZipOutputStream - This class implements an output stream filter for writing files in the ZIP file format.
    3. java.io.InputStream, in which the implementor is: java.io.FileInputStream - Used for reading files to be archived.
    4. java.io.OutputStream, in which the implementor is: java.io.FileOutputStream - As a argument of ZipOutputStream.
      Input:
      Source - The source path will be zipped.
      Target - The path of file as the output of zipping the source.
      And here's the codes:
    if(Files.isRegularFile(source)) {
        ZipEntry entry = new ZipEntry(source.toString());
        try(FileInputStream in = new FileInputStream(source.toFile());
            ZipOutputStream zOut = new ZipOutputStream(new FileOutputStream(target.toFile()))) {
            zOut.putNextEntry(entry);
            /*
             * Hold 2MB in memory.
             */
            byte[] BYTES = new byte[2048];
            for(int length; (length = in.read(BYTES)) > -1;)
                zOut.write(BYTES, 0, length);
        } catch(Exception e) {
            throw new RuntimeException(e);
        }
    }
    
  • Archive a Directory

    Archive a directory is a bit more complicated, because we will recursively match the representation of a directory in which there are files and other directories inside it.
    Here's the codes:

    public static void test(Path source) {
        if(Files.isDirectory(source)) {
            List<Path> paths = new java.util.ArrayList<>();
            try(Stream<Path> stream = Files.walk(source, 1)) {
                stream.forEach((path) -> paths.add(path));
            } catch(Exception e) {
                throw new RuntimeException(e);
                paths.clear();
                return;
            }
            paths.remove(source);
            if(paths.size() > 0)
                for(Path e : paths)
                    //Recursively, the same action as in archiving files as above
                    zip(e);
            else {
                //Keep zipping  an empty directory
                String name = source.toString();
                try { zOut.putNextEntry(new ZipEntry(name + java.io.File.separator)); }
                catch(Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
  • Final Source Code

    By setting file archiving as the base case, we can now define the zip method based on a path completely as follows:
    Right click on src/main/java directory. And on pop-up dialog, type:
    com.murez.branch.io.ZipIOStream.
    Next, type this source code:

    
       package com.murez.branch.io;
       
       import java.io.FileInputStream;
       import java.io.FileOutputStream;
       import java.io.IOException;
       import java.nio.channels.FileLock;
       import java.nio.file.Files;
       import java.nio.file.Path;
       import java.util.List;
       import java.util.Map;
       import java.util.stream.Stream;
       import java.util.zip.ZipEntry;
       import java.util.zip.ZipOutputStream;
       
       import static java.io.File.separator;
       
       public class ZipIOStream {
           /**
            * Zip the files or directory.
            * @param source The path to be zipped
            * @param target The path of file as the output of zipping {@code source}
            * @param report Map of errors that occurred while zipping {@code source}. Or just pass {@code null}, if you want to throw the exception.
            * @return {@code True} if this process was successful, otherwise {@code false}.
            */
           public static boolean zip(Path source, Path target, Map<Path, Throwable> report) {
               ZipIOStream zipStream;
               boolean b = report == null;
               try{ zipStream = new ZipIOStream(source, target); }
               catch(Throwable e) {
                   e = new RuntimeException("Initializing was failed", e);
                   if(b) throw (RuntimeException) e;
                   report.put(null, e);
                   return false;
               }
               if(!zipStream.tryLock(b))
                   return false;
               zipStream.zip(source, report);
               /*
                * Make sure all sources are closed by first executing the `close()` method under the conditions below.
                */
               return zipStream.close(b) && (b || report.size() < 1);
           }
       
           private final FileOutputStream F_OUT;
           private final ZipOutputStream Z_OUT;
           private final int N;
           private FileLock lock;
       
           private ZipIOStream(Path source, Path target) throws Throwable {
               if(source == null || Files.notExists(source))
                   throw new IllegalArgumentException("The source never existed");
               if(target == null)
                   throw new IllegalArgumentException("The target can't be null");
               F_OUT = new FileOutputStream(target.toFile());
               Z_OUT = new ZipOutputStream(F_OUT);
               N = source.getNameCount() - 1;
           }
       
           private void zip(Path source, Map<Path, Throwable> report) {
               final byte[] BYTES;
               if(Files.isRegularFile(source)) {
                   ZipEntry entry = new ZipEntry(source.subpath(N, source.getNameCount()).toString());
                   try(FileInputStream in = new FileInputStream(source.toFile())) {
                       Z_OUT.putNextEntry(entry);
                       /*
                        * Hold 2MB in memory.
                        */
                       BYTES = new byte[2048];
                       for(int length; (length = in.read(BYTES)) > -1;)
                           Z_OUT.write(BYTES, 0, length);
                   } catch(Exception e) {
                       e = new RuntimeException(source.toString(), e);
                       if(report == null) throw (RuntimeException) e;
                       report.put(source, e);
                   }
               } else
               if(Files.isDirectory(source)) {
                   List paths = new java.util.ArrayList<>();
                   try(Stream stream = Files.walk(source, 1)) {
                       stream.forEach(paths::add);
                   } catch(Exception e) {
                       e = new RuntimeException("Walking path was failed: " + source, e);
                       if(report == null) throw (RuntimeException) e;
                       report.put(source, e);
                       paths.clear();
                       return;
                   }
                   paths.remove(source);
                   if(paths.size() > 0)
                       for(Path e : paths) zip(e, report);
                   else {
                       String name = source.subpath(N, source.getNameCount()).toString();
                       try { Z_OUT.putNextEntry(new ZipEntry(name + separator)); }
                       catch(Exception e) {
                           e = new RuntimeException(source.toString(), e);
                           if(report == null) throw (RuntimeException) e;
                           report.put(source, e);
                       }
                   }
               } else {
                   if(report != null)
                       report.put(source, new IllegalArgumentException("Source's neither a file nor a directory"));
               }
           }
       
           private boolean close(boolean thrown) {
               boolean b = false;
               try {
                   if(lock != null)
                       lock.close();
                   Z_OUT.close();
                   b = true;
               } catch(IOException e) {
                   if(thrown) throw new RuntimeException("Closing resources was failed", e);
               }
               return b;
           }
       
           private boolean tryLock(boolean thrown) {
               try { lock = F_OUT.getChannel().tryLock(); }
               catch(Throwable e) {
                   if(thrown) throw new RuntimeException("Locking the target path has failed", e);
                   return false;
               }
               return true;
           }
       }
       

    Untitled 014.png

  • Test

    Here's a test that we will do:
    Main

    
       public static void main(String[] args) {
           final String ROOT = "D:/";
           Path source = Paths.get(ROOT, "Sandbox/Test/Test");
           Path target = Paths.get(ROOT, "Sandbox/Test/Out.zip");
           Map<Path, Throwable> report = new java.util.HashMap<>();
           if(!zip(source, target, report)) {
               System.out.println("Failed!");
               for(Map.Entry<Path, Throwable> e : report.entrySet()) {
                   System.out.println("Path: " + e.getKey());
                   e.getValue().printStackTrace();
               }
           }
           else
               System.out.println("Success");
       }
       

    Input: D:/Sandbox/Test/Test
    Untitled 015.png
    Log
    Untitled 016.png
    Output: D:/Sandbox/Test/Out.zip
    Untitled 017.png
    Congratulations! We have successfully completed the module to create a ZIP file using the Java™ standard classes.


2. Create a Mail Class

All we need to send an email with attachment is:

  1. Add dependencies to build.gradle to be able to use the classes in the javax.mail package.
    compile group: 'javax.mail', name: 'mail', version: '1.4.1'
  2. javax.activation.DataHandler - It provides a consistent interface to data available in many different sources and formats.
  3. javax.activation.FileDataSource - It provides data typing services via a FileTypeMap object.
  4. javax.mail.Authenticator - Represents an object that knows how to obtain authentication for a network connection.
  5. javax.mail.BodyPart - This class models a Part that is contained within a Multipart.
  6. javax.mail.Message - This class models an email message.
  7. javax.mail.Multipart - As a container that holds multiple body parts.
  8. javax.mail.PasswordAuthentication - As a data holder that is used by Authenticator.
  9. javax.mail.Session - Represents a mail session and is not subclassed.
  10. javax.mail.Transport.send - Send a message to all recipient addresses specified in the message.
  11. Make sure to change sender's settings of Allow less secure apps: ON.
    https://myaccount.google.com/lesssecureapps
    Untitled 019.png
  12. Define properties of Gmail SMTP,

   private static final java.util.Properties GMAIL = new java.util.Properties();
   
   static {
       GMAIL.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
       GMAIL.put("mail.smtp.socketFactory.port", "465");
       GMAIL.put("mail.smtp.ssl.enable", "true");
       GMAIL.put("mail.smtp.auth", "true");
       GMAIL.put("mail.smtp.host", "smtp.gmail.com");
       GMAIL.put("mail.smtp.port", "465");
   }
   

And here's the complete code:


   package com.murez.branch.io;
   
   import javax.activation.DataHandler;
   import javax.activation.FileDataSource;
   import javax.mail.Authenticator;
   import javax.mail.BodyPart;
   import javax.mail.Message;
   import javax.mail.MessagingException;
   import javax.mail.Multipart;
   import javax.mail.PasswordAuthentication;
   import javax.mail.Session;
   import javax.mail.Transport;
   import javax.mail.internet.InternetAddress;
   import javax.mail.internet.MimeBodyPart;
   import javax.mail.internet.MimeMessage;
   import javax.mail.internet.MimeMultipart;
   import java.nio.file.Path;
   
   public class Mail {
       private final Message MESSAGE;
   
       private static final java.util.Properties GMAIL = new java.util.Properties();
   
       static {
           GMAIL.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
           GMAIL.put("mail.smtp.socketFactory.port", "465");
           GMAIL.put("mail.smtp.ssl.enable", "true");
           GMAIL.put("mail.smtp.auth", "true");
           GMAIL.put("mail.smtp.host", "smtp.gmail.com");
           GMAIL.put("mail.smtp.port", "465");
       }
   
       public static Mail getInstance(String username, String password, String receiver, boolean thrown) {
           try {
               return new Mail(username, password, receiver);
           } catch(Throwable e) {
               if(thrown)
                   throw new RuntimeException(e);
               return null;
           }
       }
   
       public static Mail getInstance(String username, String password, String receiver) {
           return getInstance(username, password, receiver, false);
       }
   
       private Mail(String username, String password, String receiver) throws Throwable {
           Session session = Session.getInstance(GMAIL, new Authenticator() {
               @Override
               protected PasswordAuthentication getPasswordAuthentication() {
                   return new PasswordAuthentication(username, password);
               }
           });
           MESSAGE = new MimeMessage(session);
           MESSAGE.setFrom(new InternetAddress(username));
           MESSAGE.setRecipient(Message.RecipientType.TO, new InternetAddress(receiver));
       }
   
       public void setContent(String message, boolean thrown, Path... files) {
           Multipart multiPart = new MimeMultipart();
           BodyPart body;
           try {
               (body = new MimeBodyPart()).setText(message);
               multiPart.addBodyPart(body);
           } catch(MessagingException e) {
               if(thrown)
                   throw new RuntimeException(e);
           }
           if(files != null)
               for(Path file : files)
                   try {
                       body = new MimeBodyPart();
                       body.setDataHandler(new DataHandler(new FileDataSource(file.toFile())));
                       body.setFileName(file.getFileName().toString());
                       multiPart.addBodyPart(body);
                   } catch(MessagingException e) {
                       if(thrown)
                           throw new RuntimeException(file + " > " + e.getMessage(), e);
                   }
           try { MESSAGE.setContent(multiPart); }
           catch(MessagingException e) {
               if(thrown)
                   throw new RuntimeException(e);
           }
       }
   
       public boolean setSubject(String subject, boolean thrown) {
           boolean b = true;
           try { MESSAGE.setSubject(subject); }
           catch(MessagingException e) {
               if(thrown)
                   throw new RuntimeException(e);
               b = false;
           }
           return b;
       }
   
       public boolean send(boolean thrown) {
           boolean b = true;
           try { Transport.send(MESSAGE); }
           catch(MessagingException e) {
               if(thrown)
                   throw new RuntimeException(e);
               b = false;
           }
           return b;
       }
   }
   

Untitled 018.png

  • Test
    Suppose the two google accounts are [email protected] as the sender and [email protected] as the recipient, and the following is the scenario:

    Main

    
       public static void main(String[] args) {
           String sender = "rezanas***@gmail.com";
           String password = "**************";
           String receiver = "murez***@gmail.com";
           Mail mail = Mail.getInstance(sender, password, receiver, true);
           if(mail == null) {
               System.out.println("Failed!");
               return;
           }
           if(!mail.setSubject("Test")) {
               System.out.println("Submission of subject has failed");
               return;
           }
           /*
            * Plain text file
            */
           final String ROOT = "D:\\Sandbox\\Test";
           Path plainText = Paths.get(ROOT, "Plain.txt");
           /*
            * Archive file
            */
           Path zipFile = Paths.get(ROOT, "Out.zip");
           ZipIOStream.zip(Paths.get(ROOT, "Test"), zipFile, null);
           System.out.println("Zipping was complete!");
           mail.setContent("Hello World! My First email using Java mail", true, plainText, zipFile);
           if(!mail.send(true)) {
               System.out.println("Sending has failed");
               return;
           }
           System.out.println("Successful!");
       }
       

    Input

    • Subject: "Test"
    • Message: "Hello World! My First email using Java mail"
    • Attachments: D:\Sandbox\Test\Plain.txt and D:\Sandbox\Test\Out.zip
      Untitled 025.png

    Log
    Untitled 022.png

    Sender
    Untitled 021.png

Recipient
Untitled 020.png
Untitled 023.png
Untitled 024.png

Congratulations! We have successfully sent an email using Gmail SMTP and have finished this tutorial completely.

Thank you!

Share with Heart.


Curriculum



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

You can contact us on Discord.
[utopian-moderator]