CzechIdM – Java – E-maily s přílohou

Minulý týden jsem naučil náš Identity Manager CzechIdM odesílat e-maily s přílohami. Situací, ve kterých se to může hodit, je celá řada: předvyplněný formulář v PDF k tisku, konfigurační soubor k nově zřízené VPN, certifikát nebo třeba aktuální verze telefonního seznamu. V článku vás rychle s novou funkcí seznámím, naučím vás ji používat a společně se podíváme, jak CzechIdM tvoří instanci třídy MimeMessage, která se pro odesílání e-mailu z Javy standardně používá.

email

Třída Application

Identity Manager CzechIdM ctí obvyklé rozdělení na datovou, aplikační a prezentační vrstvu. Odesílání e-mailu provádí samozřejmě vrstva aplikační – tuto službu poskytuje především procesům, které jsou napsány na míru zákazníkovi. Univerzálním rozhraním aplikační vrstvy pro zákaznické procesy je třída Application. Na ní coby programátoři a administrátoři CzechIdM najdete spoustu užitečných statických metod, logováním do auditního logu počínaje a ověřováním práv přihlášeného uživatele konče.

Odesílání e-mailů mají na starost dvě statické metody: sendEmailToIdentity a sendEmailToAddress. Obě jsou přetížené, tedy existují ve více variantách s různými počty parametrů. My se budeme zabývat pouze těmi nejobecnějšími, ve kterých může programátor uvést parametrů nejvíc. Jsou to:

Application.sendEmailToIdentity(String userName, String emailTemplate, Map params, String language, List<EmailAttachment> attachments)
Application.sendEmailToAddress(String address, String emailTemplate, Map params, String language, List<EmailAttachment> attachments)

Volání metod

Parametr userName, respektive address určují adresáta zprávy (buď názvem identity v CzechIdM nebo přímo e-mailovou adresou), emailTemplate označuje šablonu, která má být pro sestavení zprávy použita a params značí atributy zprávy, které mají být do šablony vloženy. Parametrem language lze zvolit jazyk, pokud šablona existuje ve více jazykových mutacích. Zadáte-li null, zvolí se první nalezená šablona. Poslední parametr – attachments – je nový a značí seznam příloh k odeslání.

Instance třídy EmailAttachment představuje jednu přílohu. CzechIdM umí posílat přílohy dvěma způsoby – buď odešle soubor existující někde na disku, nebo dynamicky generované pole bytů. Třída EmailAttachment představuje jen zapouzdřené informace o jedné příloze, jako programátor nebo administrátor bohatě vystačíte s jejími dvěma konstruktory:

EmailAttachment(String path, String fileName)
EmailAttachment(byte[] bytes, String mimeType, String fileName)

První značí přílohu uloženou v již existujícím souboru na disku (cesta v parametru path), druhý slouží k odeslání pole bytů (parametr bytes). Pomocí parametru fileName lze přílohu přejmenovat pro oči adresáta. Parametr mimeType umožní blíže specifikovat, jak mají být byty interpretovány – jestli jako obrázek, text, video…

Příklad

Následující kód odesílá přílohu postupně oběma způsoby, jako pole bytů i jako existující soubor na disku:

import eu.bcvsolutions.idm.app.Application;
import eu.bcvsolutions.idm.app.email.EmailAttachment;
HashMap params = new HashMap();

    //mapa parametru. Temito parametry bude doplnena e-mailova sablona, #{attr1} v sablone bude nahrazeno retezcem "val1"
    params.put("attr1", "val1");
    params.put("attr2", "val2");
    params.put("attr3", "val3");

    //seznam priloh, ktere v mailu odesleme
    List attachments = new ArrayList();
    //prilohy lze pridat dvema zpusoby - bud primym predanim cesty k souboru a nazvu, pod jakym ma byt predan uzivateli
    EmailAttachment attach1 = new EmailAttachment("/opt/attachments/ahoj.txt", "ahoj.txt");
    //nebo primym predanim pole bytu vcetne mime typu a nazvu souboru
    byte[] bytes = {0x41, 0x48, 0x4F, 0x4A};
    EmailAttachment attach2 = new EmailAttachment(bytes, "text/plain", "ahoj.txt");

    attachments.add(attach1);
    attachments.add(attach2);

    Application.sendEmailToAddress("moje.adresa@test.com", "sablonaTestovaci", params, "cz", attachments);

Jak to funguje uvnitř

Nejdůležitější částí kódu je sestavení MimeMessage, tedy instance třídy, která bude následně odeslána. V CzechIdM se o tento úkol stará metoda createMimeMessage na třídě IdMMailerQueueListener. Netradiční je především použití třídy Multipart, která umožňuje sestavit zprávu z více částí, přičemž některými částmi jsou právě přibalované přílohy:

private MimeMessage createMimeMessage(Session session, MapMessage mapMessage) throws AddressException, MessagingException, JMSException {
        Charset charset = Charset.forName("UTF-8");

        MimeMessage mimeMessage = new MimeMessage(session);
        mimeMessage.setFrom(new InternetAddress(mapMessage.getString(Constants.EMAIL_FROM)));

        InternetAddress [] recipients = { new InternetAddress(mapMessage.getString(Constants.EMAIL_TO)) };
        mimeMessage.setRecipients(javax.mail.Message.RecipientType.TO, recipients);
        mimeMessage.setSubject(mapMessage.getString(Constants.EMAIL_SUBJECT), charset.name());
        mimeMessage.setSentDate(new java.util.Date());

        Multipart multipart = new MimeMultipart();
        // cast s textem
        MimeBodyPart messageBodyPart = new MimeBodyPart();
        //naplnim cast textem
        messageBodyPart.setText(mapMessage.getString(Constants.EMAIL_CONTENT), charset.name());
        messageBodyPart.setHeader("Content-Type", mapMessage.getString(Constants.EMAIL_CONTENT_TYPE) + "; charset=" + charset.name());
        multipart.addBodyPart(messageBodyPart);

        //zpracovani priloh, ktere jsou z technickych duvodu predany serializovane
        byte[] serializedList = mapMessage.getBytes(Constants.EMAIL_ATTACHMENTS);
        ObjectInputStream ois = null;
        List<EmailAttachment> attachs = new ArrayList<EmailAttachment>();
        if (serializedList != null) {
            try {
                ois = new ObjectInputStream(new ByteArrayInputStream(serializedList));
                attachs = (List<EmailAttachment>) ois.readObject();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
            e.printStackTrace();
            } finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        //postupne pridam do zpravy vsechny prilohy, ktere mi byly zadany
        for (EmailAttachment attach : attachs) {
            messageBodyPart = new MimeBodyPart();
            DataSource source = null;
            //podle typu prilohy se rozhodnu, jak budu nacitat data
            switch(attach.getType()) {
            case FILE:
                File attachment = new File(attach.getFilePath());
                if (!attachment.canRead() || attachment.isDirectory() || attachment.isHidden()) {
                    Application.logError("Attachment " + attachment.getAbsolutePath() + " was not sent!", new Object[0]);
                    throw new RuntimeException("Attachment failed: " + attach.getFilePath());
                }
                source = new FileDataSource(attachment);
                break;
            case BYTES:
                source = new ByteArrayDataSource(attach.getBytes(), attach.getMimeType());
                break;
            default:
                throw new RuntimeException("Unknown EmailAttachment Type " + attach.getType());
            }

            messageBodyPart.setDataHandler(new DataHandler(source));
            messageBodyPart.setFileName(attach.getFileName());
            multipart.addBodyPart(messageBodyPart);
        }

        // pridam vsechny casti do zpravy
        mimeMessage.setContent(multipart);

        mimeMessage.saveChanges();
//zprava je pripravena k odeslani
        return mimeMessage;
    }

Závěr

V článku jsem vás naučil odesílat prostřednictvím CzechIdM e-maily s přílohou a nechal jsem vás nahlédnout do jedné z klíčových procedur aplikační vrstvy, která používá běžné mechanismy Javy. Kdybyste se chtěli na cokoli zeptat, napište mi na vojtech.matocha@bcvsolutions.eu.