java/Postak/src/cz/frantovo/postak/Postak.java
author František Kučera <franta-hg@frantovo.cz>
Tue, 15 Oct 2013 23:16:04 +0200
changeset 21 798e7455da4e
parent 17 9cb46ca7e26c
permissions -rw-r--r--
XSLT převod XHTML na prostý text: mailto odkazy nebudou obsahovat duplicitně adresu
     1 package cz.frantovo.postak;
     2 
     3 import java.io.File;
     4 import java.io.StringReader;
     5 import java.io.StringWriter;
     6 import java.util.ArrayList;
     7 import java.util.Collection;
     8 import java.util.Properties;
     9 import java.util.logging.Level;
    10 import java.util.logging.Logger;
    11 import java.util.regex.Pattern;
    12 import javax.mail.Address;
    13 import javax.mail.Authenticator;
    14 import javax.mail.MessagingException;
    15 import javax.mail.PasswordAuthentication;
    16 import javax.mail.Session;
    17 import javax.mail.Transport;
    18 import javax.mail.internet.MimeBodyPart;
    19 import javax.mail.internet.MimeMessage;
    20 import javax.mail.internet.MimeMultipart;
    21 import javax.xml.transform.Transformer;
    22 import javax.xml.transform.TransformerFactory;
    23 import javax.xml.transform.stream.StreamResult;
    24 import javax.xml.transform.stream.StreamSource;
    25 
    26 /**
    27  * Odešle hromadnou zprávu pomocí SMTP.
    28  *
    29  * @author fiki
    30  */
    31 public class Postak {
    32 
    33 	private static final String KÓDOVÁNÍ = "UTF-8";
    34 	private static final Logger log = Logger.getLogger(Postak.class.getName());
    35 	/** Regulární výraz pro správnou e-mailovou adresu */
    36 	private static String REGULARNI_EMAIL = "^[_a-zA-Z0-9\\.\\-]+@[_a-zA-Z0-9\\.\\-]+\\.[a-zA-Z]{2,4}$";
    37 	private Nastaveni nastaveni;
    38 	private TransformerFactory transformerFactory;
    39 
    40 	public Postak(Nastaveni nastaveni) {
    41 		this.nastaveni = nastaveni;
    42 		transformerFactory = TransformerFactory.newInstance();
    43 	}
    44 
    45 	public void setNastaveni(Nastaveni nastaveni) {
    46 		this.nastaveni = nastaveni;
    47 	}
    48 
    49 	/**
    50 	 * Nízkoúrovňová odesílací metoda, která už nekontroluje limit příjemců.
    51 	 * Pokud se nevejdou do limitu SMTP serveru, vyhazuje výjimku.
    52 	 *
    53 	 * TODO: lepší to bude nestaické - nastavení si vyžádat v konstruktoru
    54 	 */
    55 	private void odesliSMTP(HromadnaZprava zprava) throws MessagingException {
    56 
    57 		if (zkontrolujZpravu(zprava) && zkontrolujNastaveni(nastaveni)) {
    58 
    59 			/** Inicializace SMTP */
    60 			Properties smtpVlastnosti = System.getProperties();
    61 			smtpVlastnosti.put("mail.smtp.host", nastaveni.getPostovniServer());
    62 			smtpVlastnosti.put("mail.smtp.port", String.valueOf(nastaveni.getPostovniPort()));
    63 
    64 			if (nastaveni.getPostovniPort() == 465) {
    65 				if (new File(nastaveni.getCestaKCertifikatum()).exists()) {
    66 					System.setProperty("javax.net.ssl.trustStore", nastaveni.getCestaKCertifikatum());
    67 					log.log(Level.INFO, "Používám vlastní důvěryhodné certifikáty: {0}", nastaveni.getCestaKCertifikatum());
    68 				}
    69 				/** TODO: neřídit se číslem portu, ale přidat příznak pro šifrování */
    70 				smtpVlastnosti.put("mail.smtp.starttls.enable", "true");
    71 				smtpVlastnosti.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
    72 				smtpVlastnosti.put("mail.smtp.socketFactory.port", String.valueOf(nastaveni.getPostovniPort()));
    73 				smtpVlastnosti.put("mail.smtp.socketFactory.fallback", "false");
    74 				/**
    75 				 * NAHRÁNÍ CERTIFIKÁTU:
    76 				 * 1) stáhneme si certifikát (---BEGIN CERTIFICATE---) a uložíme do vse_ca.cer
    77 				 * 2) keytool -importcert -file vse_ca.cer -keystore DuveryhodneCertifikaty.keystore
    78 				 * -storepass "changeit"
    79 				 * Pokud daný soubor existuje, program ho používá, pokud ne, používá certifikáty
    80 				 * uložené v systému (Javovské).
    81 				 */
    82 			}
    83 
    84 			PostakuvHeslovnik heslovnik = new PostakuvHeslovnik();
    85 			if (nastaveni.getPostovniJmeno() != null && nastaveni.getPostovniJmeno().length() > 0) {
    86 				heslovnik.setJmenoHeslo(nastaveni.getPostovniJmeno(), nastaveni.getPostovniHeslo());
    87 				smtpVlastnosti.put("mail.smtp.auth", "true");
    88 				log.log(Level.FINEST, "Používám pro SMTP jméno a heslo");
    89 			}
    90 
    91 			Session smtpRelace = Session.getInstance(smtpVlastnosti, heslovnik);
    92 
    93 			smtpRelace.setDebug(true);
    94 			smtpRelace.setDebugOut(System.out);
    95 
    96 			/** Sestavení zprávy */
    97 			MimeMessage mimeZprava = new MimeMessage(smtpRelace);
    98 			mimeZprava.setFrom(zprava.getOdesilatel());
    99 			if (zprava.getOdpovedetKomu() != null) {
   100 				Address[] odpovedetKomu = {zprava.getOdpovedetKomu()};
   101 				mimeZprava.setReplyTo(odpovedetKomu);
   102 			}
   103 			naplnPrijemce(mimeZprava, zprava);
   104 			mimeZprava.setSubject(zprava.getPredmet());
   105 			mimeZprava.setHeader("User-Agent", "https://frantovo.cz/projekty/SuperPostak/");
   106 			mimeZprava.setHeader("Precedence", "bulk");
   107 
   108 			switch (zprava.getFormát()) {
   109 				case PROSTÝ_TEXT:
   110 					mimeZprava.setText(zprava.getText(), KÓDOVÁNÍ);
   111 					break;
   112 				case XHTML:
   113 					mimeZprava.setText(zprava.getText(), KÓDOVÁNÍ, "html");
   114 					break;
   115 				case OBOJE:
   116 					MimeMultipart multipart = new MimeMultipart("alternative");
   117 					mimeZprava.setContent(multipart);
   118 
   119 					MimeBodyPart textováČást = new MimeBodyPart();
   120 					MimeBodyPart xhtmlČást = new MimeBodyPart();
   121 
   122 					textováČást.setText(xhtmlNaProstýText(zprava.getText()), KÓDOVÁNÍ);
   123 					xhtmlČást.setContent(zprava.getText(), "text/html; charset=" + KÓDOVÁNÍ);
   124 
   125 					multipart.addBodyPart(textováČást);
   126 					multipart.addBodyPart(xhtmlČást);
   127 					break;
   128 			}
   129 
   130 			/** Vlastní odeslání */
   131 			Transport.send(mimeZprava);
   132 
   133 		} else {
   134 			MessagingException e = new MessagingException("Zpráva nebo nastavení jsou nevyhovující");
   135 			log.log(Level.SEVERE, null, e);
   136 			throw e;
   137 		}
   138 
   139 	}
   140 
   141 	private String xhtmlNaProstýText(String xhtml) throws MessagingException {
   142 		try {
   143 			Transformer textTransformer = transformerFactory.newTransformer(new StreamSource(getClass().getClassLoader().getResourceAsStream("cz/frantovo/postak/odchozíEmailText.xsl")));
   144 
   145 			StringReader input = new StringReader(xhtml);
   146 			StringWriter output = new StringWriter(xhtml.length());
   147 			textTransformer.transform(new StreamSource(input), new StreamResult(output));
   148 
   149 			return output.toString();
   150 		} catch (Exception e) {
   151 			throw new MessagingException("Chyba při XSL transformaci zprávy na prostý text.", e);
   152 		}
   153 	}
   154 
   155 	/**
   156 	 * Nastaví zprávě (MimeMessage) všechny příjemce, které najde ve zprávě a nastavení.
   157 	 * Respektuje typy příjemců: komu, kopie, skrytá kopie.
   158 	 */
   159 	private static void naplnPrijemce(MimeMessage mimeZprava, HromadnaZprava zprava) throws MessagingException {
   160 		/**
   161 		 * Příjemci se budou definovat pouze ve zprávě.
   162 		 * Z nastavení se brát nebudou (výchozí příjemci už ve zprávě budou).
   163 		 */
   164 		ArrayList<InternetAddressKomu> prijemci = zprava.getPrijemci();
   165 		for (InternetAddressKomu komu : prijemci) {
   166 			if (zkontrolujAdresu(komu)) {
   167 				mimeZprava.addRecipient(komu.getTyp(), komu);
   168 			}
   169 		}
   170 	}
   171 
   172 	/** Vypíše do logu seznam příjemců */
   173 	public static void vypisPrijemce(Collection<InternetAddressKomu> prijemci) {
   174 		for (InternetAddressKomu p : prijemci) {
   175 			log.log(Level.INFO, p.toString());
   176 		}
   177 	}
   178 
   179 	/** Veřejná odesílací metoda.
   180 	 * Postará se o rozdělení zpráv na dílčí (které se vejdou do limitu příjemců)
   181 	 */
   182 	public void odesli(HromadnaZprava zprava) throws MessagingException {
   183 		Collection<HromadnaZprava> zpravy = zprava.getDilciZpravy(nastaveni.getLimitZprav());
   184 
   185 		for (HromadnaZprava dilciZprava : zpravy) {
   186 			odesliSMTP(dilciZprava);
   187 		}
   188 	}
   189 
   190 	private static boolean zkontrolujAdresu(InternetAddressKomu a) {
   191 		if (a.getTyp() == null) {
   192 			log.log(Level.WARNING, "Neplatná adresa (typ): {0}", a.getAddress());
   193 			return false;
   194 		}
   195 
   196 		if (a.getAddress() == null || a.getAddress().length() < 1) {
   197 			log.log(Level.WARNING, "Neplatná adresa (address): {0}", a.getPersonal());
   198 			return false;
   199 		}
   200 
   201 		if (!zkontrolujAdresu(a.getAddress())) {
   202 			log.log(Level.WARNING, "Adresa nevyhovuje regulárnímu výrazu: {0}", a.getAddress());
   203 			return false;
   204 		}
   205 
   206 		return true;
   207 	}
   208 
   209 	/** Zkontroluje e-mailovou adresu pomocí regulárního výrazu. */
   210 	public static boolean zkontrolujAdresu(String adresa) {
   211 		return adresa != null && Pattern.matches(REGULARNI_EMAIL, adresa);
   212 	}
   213 
   214 	/** @return Vrací true, pokud je zpráva v pořádku */
   215 	private static boolean zkontrolujZpravu(HromadnaZprava z) {
   216 
   217 		if (z.getPrijemci() == null) {
   218 			log.log(Level.WARNING, "getPrijemci() == null");
   219 			return false;
   220 		}
   221 
   222 		if (z.getPrijemci().size() < 1) {
   223 			log.log(Level.WARNING, "getPrijemci().size() < 1");
   224 			return false;
   225 		}
   226 
   227 		if (z.getOdesilatel() == null) {
   228 			log.log(Level.WARNING, "getOdesilatel() == null");
   229 			return false;
   230 		}
   231 
   232 		if (z.getPredmet() == null) {
   233 			log.log(Level.WARNING, "getPredmet() == null");
   234 			return false;
   235 		}
   236 
   237 		return true;
   238 	}
   239 
   240 	private static boolean zkontrolujNastaveni(Nastaveni n) {
   241 
   242 		if (n.getPostovniServer() == null || n.getPostovniServer().length() < 1) {
   243 			return false;
   244 		}
   245 
   246 		return true;
   247 	}
   248 
   249 	/** Slouží k uložení jména a hesla pro SMTP */
   250 	private static class PostakuvHeslovnik extends Authenticator {
   251 
   252 		private String jmeno = "user";
   253 		private char[] heslo = "luser".toCharArray();
   254 
   255 		@Override
   256 		public PasswordAuthentication getPasswordAuthentication() {
   257 			return new PasswordAuthentication(jmeno, String.valueOf(heslo));
   258 		}
   259 
   260 		public void setJmenoHeslo(String jmeno, char[] heslo) {
   261 			this.jmeno = jmeno;
   262 			this.heslo = heslo;
   263 		}
   264 	}
   265 }